From 9ea6e836ea7eff38c2c30f0a199fc83fbf851722 Mon Sep 17 00:00:00 2001 From: Anant Shrivastava Date: Fri, 26 Jun 2026 01:20:30 +0530 Subject: [PATCH 1/5] workflow core improvements --- .github/workflows/aidc-e2e.yml | 9 ++ .gitignore | 2 +- CHANGELOG.md | 5 + DETAILED_CHANGELOG.md | 55 +++++++++ README.md | 22 +++- lib/aidc.sh | 162 ++++++++++++++++++++++++- templates/AGENTS.md.tmpl | 30 +++++ templates/CHANGELOG.md.tmpl | 23 ++++ templates/CLAUDE.md.tmpl | 30 +++++ templates/DETAILED_CHANGELOG.md.tmpl | 33 +++++ templates/devcontainer/Dockerfile.tmpl | 3 + templates/logs/README.md.tmpl | 28 +++++ 12 files changed, 393 insertions(+), 9 deletions(-) create mode 100644 DETAILED_CHANGELOG.md create mode 100644 templates/CHANGELOG.md.tmpl create mode 100644 templates/DETAILED_CHANGELOG.md.tmpl create mode 100644 templates/logs/README.md.tmpl diff --git a/.github/workflows/aidc-e2e.yml b/.github/workflows/aidc-e2e.yml index 4131bc7..d4a036a 100644 --- a/.github/workflows/aidc-e2e.yml +++ b/.github/workflows/aidc-e2e.yml @@ -191,6 +191,9 @@ jobs: .ai-container/project.env \ CLAUDE.md \ AGENTS.md \ + CHANGELOG.md \ + DETAILED_CHANGELOG.md \ + logs/README.md \ .cursor/rules/00-core-logics.mdc; do [ -f "$work/$f" ] || fail "init did not create $f" done @@ -198,6 +201,12 @@ jobs: grep -q '^AIDC_REPO_SLUG=' "$work/.ai-container/project.env" \ || fail "project.env missing AIDC_REPO_SLUG" + # Documentation seeds are copy-once: a hand-edit must survive re-init. + echo "EDITED-BY-USER" >> "$work/CHANGELOG.md" + ( cd "$work" && aidc init ) || fail "second aidc init failed" + grep -q 'EDITED-BY-USER' "$work/CHANGELOG.md" \ + || fail "re-init clobbered a user edit to CHANGELOG.md" + # Re-running init over an initialized repo must be a clean no-op. ( cd "$work" && aidc init ) || fail "second aidc init was not idempotent" echo "init scaffold OK" diff --git a/.gitignore b/.gitignore index c1f25bd..ad91899 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,7 @@ references/ # the source of truth; the generated copies at the repo root are local-only. /CLAUDE.md /AGENTS.md - +/logs # Editor / OS noise .DS_Store *.swp diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a9fc28..56a2ccf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ All notable changes to aidc are tracked here. Format follows [Keep a Changelog]( ### Added +- **Automatic session sync.** In-container agent transcripts now sync to the host on container start (down→up transition), agent exit, `aidc down`, and `aidc destroy` (synced before `-v` removes the volumes), so `/insights` stays current without a manual `aidc sync-sessions`. The start sync is the recovery path for ungraceful exits (crash / `docker kill`) the on-exit hooks can't cover. Toggle with `AIDC_AUTO_SYNC_SESSIONS` host-wide in `~/.config/aidc/config.env` or per project in `.ai-container/project.env` (per-folder overrides global). The agent's exit code is preserved across the post-run sync. +- **Host-wide config file.** New `~/.config/aidc/config.env` holds universal aidc defaults for every project; `.ai-container/project.env` is sourced after it and overrides per folder. Seeded (fully commented) on first run. +- **`aidc rescan` command.** Re-detects project languages and rebuilds so a repo that started empty (or single-language) picks up the matching toolchains and security scanners once code lands. Prints detected vs. effective (overridden) toolchains before building. +- **`shell` toolchain → shellcheck.** `aidc::detect_toolchains` now detects shell scripts (any `*.sh` file, or an extensionless executable with a shell shebang such as `bin/aidc`) and installs `shellcheck` via a new `shell)` arm in the Dockerfile toolchain block. +- **Project documentation seeds.** `aidc init` now seeds `CHANGELOG.md`, `DETAILED_CHANGELOG.md`, and `logs/README.md` into each project once (never overwritten, and intentionally not git-excluded so they are committed). The scaffolded `CLAUDE.md`/`AGENTS.md` guidance gains **Testing & coverage**, **Documentation & changelog**, **Documentation requirements**, and **Session log convention** sections, plus a `shellcheck` line in the security guardrails. - **Grok Build agent.** `aidc grok` launches xAI's Grok Build CLI inside the container, with a `grok_home` named volume (`~/.grok`), a read-only `/host-seed/grok` seed mount, `sync_grok` seeding in `bootstrap-state.sh`, and `aidc sync-config`/`sync-sessions` support. Grok reads the same generated `AGENTS.md`. - **GPL-3.0 license.** `LICENSE` at repo root. - **Vulnerability disclosure policy.** `SECURITY.md` documents scope, reporting channels, and timelines. diff --git a/DETAILED_CHANGELOG.md b/DETAILED_CHANGELOG.md new file mode 100644 index 0000000..3eff87e --- /dev/null +++ b/DETAILED_CHANGELOG.md @@ -0,0 +1,55 @@ +# Detailed Changelog + +The long-form companion to `CHANGELOG.md`. Where `CHANGELOG.md` says *what* +changed in one line, this file records *why* and *how* — enough for a future +reader to audit, reproduce, or roll back any change without re-deriving it. + +Add a new entry (newest first) for every meaningful change. + +--- + +## 2026-06-25 — Usability hardening: auto-sync, rescan, shell toolchain, doc scaffolding + +**Summary:** Added automatic session-sync, a new `aidc rescan` command, shell-script +detection that installs `shellcheck`, and per-project documentation scaffolding +(`CHANGELOG.md` / `DETAILED_CHANGELOG.md` / `logs/`), driven by six points of +real-world usage feedback. + +**Why:** Daily use surfaced friction: session-sync was easy to forget; shell scripts +had no in-container linter; projects that start empty never picked up language +security tools because detection only ran at first build; and scaffolded projects +lacked enforced test-coverage and documentation discipline. + +**What changed:** +- `lib/aidc.sh`: + - `aidc::auto_sync_sessions` helper (gated on `AIDC_AUTO_SYNC_SESSIONS`, default on), + wired into `run_tool` (exit code preserved), `cmd_down`, `cmd_destroy` + (before `down -v`), and the container-start transition (`cmd_up`, `cmd_rebuild`, + and the lazy-start branch of `ensure_container_running`) as the recovery path for + ungraceful exits the on-exit hooks can't cover. + - `aidc::has_shell_scripts` + a `shell` entry in `detect_toolchains`. + - `aidc::cmd_rescan` + `rescan` dispatch case + help text. + - `AIDC_AUTO_SYNC_SESSIONS` documented in `write_project_env`; three + `copy_template_once` seed calls in `refresh_scaffold`. + - Host-wide config: `AIDC_GLOBAL_CONFIG` (`~/.config/aidc/config.env`), + `aidc::load_global_config` sourced first in `load_project_env` (so per-folder + `project.env` overrides it), and `aidc::ensure_global_config` seeds a commented + template via `ensure_host_config_dirs`. Verified precedence: + project.env > config.env > built-in default. +- `templates/devcontainer/Dockerfile.tmpl`: `shell)` arm installs `shellcheck`. +- `templates/CLAUDE.md.tmpl` + `templates/AGENTS.md.tmpl`: shellcheck guardrail line; + Testing & coverage, Documentation & changelog, Documentation requirements, and + Session log convention sections (inside the aidc-managed markers). +- New seed templates: `templates/CHANGELOG.md.tmpl`, + `templates/DETAILED_CHANGELOG.md.tmpl`, `templates/logs/README.md.tmpl`. +- `README.md` and `CHANGELOG.md` updated; this repo dogfoods the new doc files. +- `.github/workflows/aidc-e2e.yml`: assert the new seed files appear after `aidc init`. + +**How / commands run:** see `logs/2026-06-25-aidc-usability-hardening.md`. + +**Verification:** `shellcheck bin/aidc lib/aidc.sh`; bash-compat check; `aidc init` +scaffold assertions; `semgrep` / `gitleaks` / `trufflehog` on changed files. + +**Notes / follow-ups:** `.devcontainer/`, `CLAUDE.md`, and `AGENTS.md` are generated +artifacts in this repo (listed in `.git/info/exclude`), so only the templates were +edited — the generated copies refresh from them on the next `aidc up`. diff --git a/README.md b/README.md index 0d6e4d5..7b25972 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Make sure `~/.local/bin` is on your `PATH`. ```bash cd /path/to/cloned/repo -aidc init # one-time scaffold; writes .devcontainer/, .ai-container/, CLAUDE.md, AGENTS.md +aidc init # one-time scaffold; writes .devcontainer/, .ai-container/, CLAUDE.md, AGENTS.md, CHANGELOG.md, DETAILED_CHANGELOG.md, logs/ aidc claude # auto-runs `aidc up` if needed, then drops you into Claude Code in the container ``` @@ -47,9 +47,11 @@ Tool commands (`aidc claude` / `codex` / `opencode` / `grok` / `cursor-agent`) a - persists tool state in per-repo Docker volumes instead of mounting whole host homes - seeds selected config from host read-only mounts on first startup - creates one `CORE_LOGICS` git worktree per repo and mounts it at `/opt/CORE_LOGICS` for shared cross-repo notes -- detects the project's language toolchains (Go, Rust, Ruby, Java, PHP, Node, Python) and installs them automatically -- bakes always-on security scanners (`semgrep`, `gitleaks`, `trufflehog`) plus per-toolchain linters (`gosec`, `bandit`, `cargo-audit`, `bundler-audit`) into the image -- seeds a non-negotiable "Security guardrails" block into `CLAUDE.md` / `AGENTS.md` for every project +- detects the project's toolchains (Go, Rust, Ruby, Java, PHP, Node, Python — plus shell scripts) and installs them automatically; `aidc rescan` re-detects later for a repo that started empty +- bakes always-on security scanners (`semgrep`, `gitleaks`, `trufflehog`) plus per-toolchain linters (`gosec`, `bandit`, `cargo-audit`, `bundler-audit`, `shellcheck`) into the image +- seeds non-negotiable guidance into `CLAUDE.md` / `AGENTS.md` for every project — security guardrails, test-coverage discipline, and changelog/session-log conventions +- seeds committed project docs once, never overwriting your edits — `CHANGELOG.md`, `DETAILED_CHANGELOG.md`, and a `logs/` session journal +- auto-syncs in-container agent session transcripts back to the host on container start and exit, so the host's `/insights` stays current - ships SafeDep's `pmg` / `vet` / `gryph` for supply-chain interception and `rtk` for token-saving CLI proxying - offers an opt-in default-deny egress firewall with a sane allowlist @@ -59,7 +61,7 @@ Tool commands (`aidc claude` / `codex` / `opencode` / `grok` / `cursor-agent`) a - [`docs/claude-profiles.md`](docs/claude-profiles.md) — alternate Claude API targets, local-model profiles, one-time OAuth login, session sync - [`docs/security.md`](docs/security.md) — scanners, supply-chain guardrails, agent guardrails (gryph + rtk), opt-in egress firewall - [`docs/clipboard-bridge.md`](docs/clipboard-bridge.md) — host-clipboard → container PNG paste bridge -- [`CHANGELOG.md`](CHANGELOG.md) +- [`CHANGELOG.md`](CHANGELOG.md) — high-level release notes; [`DETAILED_CHANGELOG.md`](DETAILED_CHANGELOG.md) — long-form per-change rationale - [`SECURITY.md`](SECURITY.md) — how to report vulnerabilities in aidc itself ## Commands @@ -69,6 +71,7 @@ aidc init [path] aidc up [--clipboard] [--isolate-vm] aidc down aidc rebuild [--clipboard] [--isolate-vm] +aidc rescan aidc status [--global] aidc destroy [-f] [--purge-worktree] [--purge-scaffold] aidc shell @@ -86,6 +89,12 @@ aidc sync-sessions [claude|codex|opencode|grok|all] `aidc status` shows the container + mounts/config for the current folder. `--global` lists every aidc container on the host with disk/CPU/memory and a totals line. +`aidc rescan` re-detects the project's languages and rebuilds, so a repo that started empty (or single-language) picks up the matching toolchains and security scanners once code lands. `shellcheck` installs automatically when shell scripts are present. + +Session transcripts auto-sync from the container to the host on container start, agent exit, `aidc down`, and `aidc destroy` (before its volumes are removed), so `/insights` on the host stays current without a manual `aidc sync-sessions`. The start sync is the safety net for ungraceful exits (crash / `docker kill`) that the on-exit hooks miss — it catches up anything left in the volume. + +Toggle it with `AIDC_AUTO_SYNC_SESSIONS`: set it host-wide in `~/.config/aidc/config.env` (universal default for every project) or per project in `.ai-container/project.env` (overrides the global default). `0` disables auto-sync; manual `aidc sync-sessions` always works regardless. + `--provider` remains as a compatibility alias for `--profile`. ## Isolation modes @@ -137,7 +146,8 @@ echo "AIDC_ISOLATE_VM=1" >> .ai-container/project.env ## Notes -- Generated files are added to `.git/info/exclude` when the target directory is a git repo, so your project stays clean. +- Generated files are added to `.git/info/exclude` when the target directory is a git repo, so your project stays clean. The seeded project docs (`CHANGELOG.md`, `DETAILED_CHANGELOG.md`, `logs/`) are *not* excluded — they belong to your repo and are meant to be committed. +- Settings can be set host-wide in `~/.config/aidc/config.env` (universal defaults for every project) or per project in `.ai-container/project.env`, which overrides the global default. Both files are sourced for env vars like `AIDC_AUTO_SYNC_SESSIONS`, `AIDC_ENABLE_EGRESS_FIREWALL`, and `AIDC_ISOLATE_VM`. - Container egress is open by default; set `AIDC_ENABLE_EGRESS_FIREWALL=1` in `.ai-container/project.env` for a default-deny allowlist. See [`docs/security.md`](docs/security.md#optional-egress-firewall). - The host-clipboard bridge is **off by default** — no host clipboard socket is mounted into the container. Opt in per (re)create with `aidc up --clipboard` / `aidc rebuild --clipboard`, or persist `AIDC_ENABLE_CLIPBOARD=1` in `.ai-container/project.env`. See [`docs/clipboard-bridge.md`](docs/clipboard-bridge.md). - Per-project VM isolation (`--isolate-vm`) is **off by default** due to resource cost. Opt in per (re)create with `aidc up --isolate-vm` / `aidc rebuild --isolate-vm`, or persist `AIDC_ISOLATE_VM=1` in `.ai-container/project.env`. See [Isolation modes](#isolation-modes) above. diff --git a/lib/aidc.sh b/lib/aidc.sh index 1103e39..0d1a57e 100644 --- a/lib/aidc.sh +++ b/lib/aidc.sh @@ -9,6 +9,7 @@ AIDC_VERSION="${AIDC_VERSION:-0.1.0}" AIDC_CONTAINER_USER="${AIDC_CONTAINER_USER:-vscode}" AIDC_CONTAINER_HOME="${AIDC_CONTAINER_HOME:-/home/vscode}" AIDC_HOST_CONFIG_ROOT="${AIDC_HOST_CONFIG_ROOT:-$HOME/.config/aidc}" +AIDC_GLOBAL_CONFIG="${AIDC_GLOBAL_CONFIG:-$AIDC_HOST_CONFIG_ROOT/config.env}" AIDC_EMPTY_ROOT="${AIDC_EMPTY_ROOT:-$AIDC_HOST_CONFIG_ROOT/empty}" AIDC_PROVIDER_ROOT="${AIDC_PROVIDER_ROOT:-$AIDC_HOST_CONFIG_ROOT/providers/claude}" AIDC_CLAUDE_PROFILE_ROOT="${AIDC_CLAUDE_PROFILE_ROOT:-$AIDC_PROVIDER_ROOT}" @@ -60,6 +61,9 @@ aidc::main() { rebuild) aidc::cmd_rebuild "$@" ;; + rescan) + aidc::cmd_rescan "$@" + ;; status) aidc::cmd_status "$@" ;; @@ -117,6 +121,7 @@ Usage: aidc up [--clipboard] [--isolate-vm] aidc down aidc rebuild [--clipboard] [--isolate-vm] + aidc rescan aidc status [--global] aidc destroy [-f] [--purge-worktree] [--purge-scaffold] aidc shell @@ -138,8 +143,12 @@ Notes: - aidc cursor opens the host Cursor app; reopen the repo in the devcontainer. - aidc destroy removes the container, named volumes, and image by default. Worktree and scaffold removal are opt-in via the listed flags. + - aidc rescan re-detects project languages (handy once a repo that started + empty gains code) and rebuilds so the matching toolchains/scanners install. - aidc sync-sessions pulls in-container session logs back to host - ~/.claude/projects so '/insights' on the host can see them. + ~/.claude/projects so '/insights' on the host can see them. Sessions also + auto-sync on container start, agent exit, 'down', and 'destroy' unless + AIDC_AUTO_SYNC_SESSIONS=0. - The host-clipboard bridge is off by default. Enable it at (re)create time with 'aidc up --clipboard' or 'aidc rebuild --clipboard'. - Per-project VM isolation is off by default due to resource cost. Enable it @@ -197,6 +206,12 @@ aidc::refresh_scaffold() { aidc::copy_template "templates/devcontainer/scripts/init-firewall.sh.tmpl" "$workspace/.devcontainer/scripts/init-firewall.sh" "0755" # User-owned. Created once, never refreshed; edits drive per-project image layers. aidc::copy_template_once "templates/devcontainer/project-setup.sh.tmpl" "$workspace/.devcontainer/project-setup.sh" "0755" + # Project documentation seeds. Created once and never overwritten so the + # project's own history is never clobbered. Intentionally NOT git-excluded — + # these belong to the repo and should be committed. + aidc::copy_template_once "templates/CHANGELOG.md.tmpl" "$workspace/CHANGELOG.md" "0644" + aidc::copy_template_once "templates/DETAILED_CHANGELOG.md.tmpl" "$workspace/DETAILED_CHANGELOG.md" "0644" + aidc::copy_template_once "templates/logs/README.md.tmpl" "$workspace/logs/README.md" "0644" aidc::merge_template "templates/CLAUDE.md.tmpl" "$workspace/CLAUDE.md" aidc::merge_template "templates/AGENTS.md.tmpl" "$workspace/AGENTS.md" aidc::copy_template "templates/cursor-rules/00-core-logics.mdc.tmpl" "$workspace/.cursor/rules/00-core-logics.mdc" "0644" @@ -219,6 +234,9 @@ aidc::cmd_up() { fi aidc::compose "$workspace" up -d --build workspace + # Catch up the host with any transcripts a prior (possibly ungraceful) session + # left in the volume — the recovery case the on-exit hooks can't cover. + aidc::auto_sync_sessions "$workspace" all aidc::log "container is ready for $(basename "$workspace")" } @@ -233,9 +251,37 @@ aidc::cmd_rebuild() { fi aidc::compose "$workspace" up -d --build --force-recreate workspace + aidc::auto_sync_sessions "$workspace" all aidc::log "container rebuilt for $(basename "$workspace")" } +# Re-detect project languages and rebuild so newly-applicable toolchains and +# their security scanners get installed. Useful when a repo that started empty +# (or single-language) later gains code aidc didn't see at first build. +aidc::cmd_rescan() { + local workspace + workspace="$(aidc::default_workspace)" + aidc::ensure_workspace_ready "$workspace" + + local detected effective + detected="$(aidc::detect_toolchains "$workspace")" + effective="$(aidc::compute_toolchains "$workspace")" + + aidc::log "detected toolchains: ${detected:-none}" + if [[ "$effective" != "$detected" ]]; then + # project.env pins AIDC_TOOLCHAINS, so detection is informational only. + aidc::log "effective toolchains (AIDC_TOOLCHAINS override): ${effective:-none}" + fi + + if [[ "${AIDC_ISOLATE_VM:-0}" == "1" ]]; then + aidc::vm_ensure "$workspace" + fi + + # --build picks up the changed AIDC_TOOLCHAINS build arg and reinstalls. + aidc::compose "$workspace" up -d --build workspace + aidc::log "rescan complete for $(basename "$workspace")" +} + # Parse flags shared by 'up' and 'rebuild'. # --clipboard: opt-in host-clipboard bridge. Off by default; can also be # persisted as AIDC_ENABLE_CLIPBOARD=1 in .ai-container/project.env. @@ -260,6 +306,8 @@ aidc::cmd_down() { aidc::die "no aidc project in $workspace (run 'aidc init' first)" fi aidc::load_project_env "$workspace" + # Pull session transcripts to the host while the container is still up. + aidc::auto_sync_sessions "$workspace" all aidc::compose "$workspace" down if [[ "${AIDC_ISOLATE_VM:-0}" == "1" ]]; then @@ -549,6 +597,8 @@ aidc::cmd_destroy() { esac fi + # Last chance to pull session transcripts before '-v' wipes the volumes. + aidc::auto_sync_sessions "$workspace" all aidc::compose "$workspace" down -v --rmi local --remove-orphans if [[ "${AIDC_ISOLATE_VM:-0}" == "1" ]]; then @@ -787,6 +837,37 @@ aidc::sync_session_tool() { aidc::log "synced $tool sessions to $host_dst" } +# Best-effort session sync wired into the agent/lifecycle paths so transcripts +# land on the host without a manual 'aidc sync-sessions'. Opt out by setting +# AIDC_AUTO_SYNC_SESSIONS=0 in .ai-container/project.env. 'tool' is a single +# tool name or 'all'; tools without a session mapping (e.g. cursor-agent) are +# skipped. Never aborts the caller — sync failures are logged, not fatal. +aidc::auto_sync_sessions() { + local workspace="$1" + local tool="$2" + [[ "${AIDC_AUTO_SYNC_SESSIONS:-1}" == "0" ]] && return 0 + + # No container, nothing to pull (e.g. auto-sync after a failed start). + [[ -n "$(aidc::compose_capture "$workspace" ps -q workspace 2>/dev/null)" ]] || return 0 + + if [[ "$tool" == "all" ]]; then + local t + for t in claude codex opencode grok; do + aidc::sync_session_tool "$workspace" "$t" || true + done + return 0 + fi + + case "$tool" in + claude|codex|opencode|grok) + aidc::sync_session_tool "$workspace" "$tool" || true + ;; + *) + # cursor-agent and unknowns have no session volume to sync. + ;; + esac +} + aidc::run_tool() { local tool="$1" local profile="$2" @@ -836,7 +917,12 @@ aidc::run_tool() { command+=("$@") fi - aidc::compose_exec "$workspace" "${command[@]}" + # Run the agent in the foreground; once it exits, pull its session transcripts + # back to the host. Preserve the agent's exit code as our own. + local rc=0 + aidc::compose_exec "$workspace" "${command[@]}" || rc=$? + aidc::auto_sync_sessions "$workspace" "$tool" + return "$rc" } aidc::need_cmd() { @@ -1358,6 +1444,12 @@ AIDC_REPO_SLUG=$repo_slug AIDC_CORE_ROOT=$(aidc::shell_escape "$core_root") AIDC_CORE_BRANCH=$core_branch AIDC_CORE_WORKTREE=$(aidc::shell_escape "$core_worktree") + +# Auto-pull in-container agent transcripts to the host on container start, agent +# exit, 'down', and 'destroy'. Set to 0 to disable and sync manually with +# 'aidc sync-sessions'. Overrides the host-wide default in +# ~/.config/aidc/config.env for this project only. +# AIDC_AUTO_SYNC_SESSIONS=1 EOF } @@ -1430,13 +1522,28 @@ aidc::ensure_container_running() { if [[ -z "$(aidc::compose_capture "$workspace" ps -q workspace)" ]]; then aidc::compose "$workspace" up -d --build workspace + # Only on the down→up transition (not every exec), so we recover prior + # transcripts without adding a sync to each command. + aidc::auto_sync_sessions "$workspace" all fi } +# Source host-wide aidc defaults (~/.config/aidc/config.env) if present. These +# are universal settings shared by every project on this host. A project's +# .ai-container/project.env is sourced afterwards and overrides them, so a +# per-folder setting always wins over the global default. +aidc::load_global_config() { + [[ -f "$AIDC_GLOBAL_CONFIG" ]] || return 0 + # shellcheck disable=SC1090 + . "$AIDC_GLOBAL_CONFIG" +} + aidc::load_project_env() { local workspace="$1" local env_file="$workspace/.ai-container/project.env" [[ -f "$env_file" ]] || aidc::die "missing project env: $env_file" + # Global defaults first; per-folder project.env overrides them. + aidc::load_global_config # shellcheck disable=SC1090 . "$env_file" } @@ -1532,6 +1639,24 @@ aidc::ensure_host_config_dirs() { mkdir -p "$AIDC_HOST_CONFIG_ROOT" "$AIDC_EMPTY_ROOT" "$AIDC_CLAUDE_PROFILE_ROOT" mkdir -p "$AIDC_EMPTY_ROOT/claude" "$AIDC_EMPTY_ROOT/codex" "$AIDC_EMPTY_ROOT/opencode" "$AIDC_EMPTY_ROOT/grok" "$AIDC_EMPTY_ROOT/clipboard" touch "$AIDC_EMPTY_ROOT/gitconfig" + aidc::ensure_global_config +} + +# Seed the host-wide config file once, fully commented so sourcing it is a no-op +# until the user edits it. Lists the settings that make sense as global defaults. +aidc::ensure_global_config() { + [[ -f "$AIDC_GLOBAL_CONFIG" ]] && return 0 + mkdir -p "$(dirname "$AIDC_GLOBAL_CONFIG")" + cat >"$AIDC_GLOBAL_CONFIG" <<'EOF' +# aidc global config — universal defaults for every project on this host. +# A project's .ai-container/project.env is sourced AFTER this file and overrides +# anything set here, so per-folder settings win. Uncomment a line to change a +# host-wide default. + +# Auto-pull in-container agent transcripts to the host on container start, agent +# exit, 'down', and 'destroy'. Set to 0 to disable everywhere by default. +# AIDC_AUTO_SYNC_SESSIONS=1 +EOF } aidc::ensure_claude_profile_examples() { @@ -1672,6 +1797,33 @@ aidc::export_compose_env() { fi } +# True when the workspace contains shell scripts worth linting with shellcheck. +# Fast path is the name-based '*.sh' check; the shebang probe is bounded to +# executable, extensionless files (capped) so big repos stay cheap. Uses only +# POSIX find/head so it works with macOS (BSD) find under bash 3.2. +aidc::has_shell_scripts() { + local workspace="$1" + + # Fast path: any *.sh file, skipping VCS/vendor dirs. + if [[ -n "$(find "$workspace" \ + \( -type d \( -name .git -o -name node_modules -o -name vendor \) -prune \) -o \ + \( -type f -name '*.sh' -print \) 2>/dev/null | head -n1)" ]]; then + return 0 + fi + + # Extensionless executables with a shell shebang (e.g. bin/aidc). + local f first + while IFS= read -r f; do + [[ -n "$f" ]] || continue + first="$(head -n1 "$f" 2>/dev/null)" + [[ "$first" == '#!'*sh* ]] && return 0 + done < <(find "$workspace" \ + \( -type d \( -name .git -o -name node_modules -o -name vendor \) -prune \) -o \ + \( -type f -perm -u+x ! -name '*.*' -print \) 2>/dev/null | head -n 200) + + return 1 +} + aidc::detect_toolchains() { local workspace="$1" local -a detected=() @@ -1690,6 +1842,12 @@ aidc::detect_toolchains() { if [[ -f "$workspace/requirements.txt" || -f "$workspace/uv.lock" || -f "$workspace/pyproject.toml" || -f "$workspace/Pipfile" || -f "$workspace/Pipfile.lock" || -f "$workspace/poetry.lock" ]]; then detected+=("python") fi + # Shell has no manifest file, so detect it from content: any *.sh file or an + # extensionless executable with a shell shebang (e.g. bin/aidc). The 'shell' + # toolchain arm installs the shellcheck linter. + if aidc::has_shell_scripts "$workspace"; then + detected+=("shell") + fi # Join with commas without touching the global IFS. local out="" item for item in ${detected[@]+"${detected[@]}"}; do diff --git a/templates/AGENTS.md.tmpl b/templates/AGENTS.md.tmpl index 506b7e4..62bbebd 100644 --- a/templates/AGENTS.md.tmpl +++ b/templates/AGENTS.md.tmpl @@ -20,7 +20,37 @@ All scanners are pre-installed in the aidc container. - **Rust** (when `Cargo.toml` present): `cargo audit` - **Ruby** (when `Gemfile` present): `bundle-audit check --update` - **Node** (when `package.json` present): `npm audit --omit=dev` (or `pnpm audit` / `yarn npm audit`). +- **Shell** (when shell scripts present): `shellcheck ` on changed `*.sh` and shebang scripts. - **Dependency vetting** (any language): `vet scan -D .` for SCA against the OSV database. When findings exist, the work is not done. Fix them, re-run the scan, and only then report the task as complete. + +## Testing & coverage + +Every code change ships with tests. Aim for full coverage of the lines you add or change — cover the happy path, edge cases, and error handling, not just the obvious case. If a line is genuinely untestable, say why in the change. + +Run the project's coverage tool and confirm the changed code is exercised before declaring work complete: + +- **Go**: `go test -cover ./...` +- **Python**: `pytest --cov` (`coverage run -m pytest` + `coverage report`) +- **Rust**: `cargo llvm-cov` (or `cargo tarpaulin`) +- **Node**: `npm test -- --coverage` (`jest --coverage` / `vitest run --coverage`) +- **Ruby**: SimpleCov via `bundle exec rspec` + +Coverage tools that aren't pre-installed can be added per project in `.devcontainer/project-setup.sh`. + +## Documentation & changelog + +Every meaningful change updates **both** changelog files: + +- `CHANGELOG.md` — one high-level bullet under the right heading, in [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) format. +- `DETAILED_CHANGELOG.md` — a dated long-form entry (what / why / how / commands / verification / notes), newest first. + +## Documentation requirements + +**Meticulously document all activities performed in this project.** Every action — commands run, configuration changed, files transferred, troubleshooting steps, observations — gets recorded: the exact commands and their output, the reasoning behind each action, any errors and how they were resolved, and the design choices made. Treat every session as something that may need to be audited, reproduced, or rolled back. If it was done, write it down. + +## Session log convention + +Every working session writes a log to `logs/YYYY-MM-DD-.md` (date is the session date, slug is a short kebab-case description of the change). Existing entries are the template — match their structure: symptom → diagnosis → change (with diff) → commands → verification → notes. See `logs/README.md`. diff --git a/templates/CHANGELOG.md.tmpl b/templates/CHANGELOG.md.tmpl new file mode 100644 index 0000000..cfc3ab6 --- /dev/null +++ b/templates/CHANGELOG.md.tmpl @@ -0,0 +1,23 @@ +# Changelog + +All notable changes to this project are documented here. +Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and +[Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +Keep this file high-level: one bullet per user-visible change, grouped under the +right heading. Record the blow-by-blow detail (commands, diffs, reasoning) in +`DETAILED_CHANGELOG.md` instead. + +## [Unreleased] + +### Added + +### Changed + +### Deprecated + +### Removed + +### Fixed + +### Security diff --git a/templates/CLAUDE.md.tmpl b/templates/CLAUDE.md.tmpl index 9b633e3..e56eccc 100644 --- a/templates/CLAUDE.md.tmpl +++ b/templates/CLAUDE.md.tmpl @@ -20,7 +20,37 @@ All scanners are pre-installed in the aidc container. - **Rust** (when `Cargo.toml` present): `cargo audit` - **Ruby** (when `Gemfile` present): `bundle-audit check --update` - **Node** (when `package.json` present): `npm audit --omit=dev` (or `pnpm audit` / `yarn npm audit`). +- **Shell** (when shell scripts present): `shellcheck ` on changed `*.sh` and shebang scripts. - **Dependency vetting** (any language): `vet scan -D .` for SCA against the OSV database. When findings exist, the work is not done. Fix them, re-run the scan, and only then report the task as complete. + +## Testing & coverage + +Every code change ships with tests. Aim for full coverage of the lines you add or change — cover the happy path, edge cases, and error handling, not just the obvious case. If a line is genuinely untestable, say why in the change. + +Run the project's coverage tool and confirm the changed code is exercised before declaring work complete: + +- **Go**: `go test -cover ./...` +- **Python**: `pytest --cov` (`coverage run -m pytest` + `coverage report`) +- **Rust**: `cargo llvm-cov` (or `cargo tarpaulin`) +- **Node**: `npm test -- --coverage` (`jest --coverage` / `vitest run --coverage`) +- **Ruby**: SimpleCov via `bundle exec rspec` + +Coverage tools that aren't pre-installed can be added per project in `.devcontainer/project-setup.sh`. + +## Documentation & changelog + +Every meaningful change updates **both** changelog files: + +- `CHANGELOG.md` — one high-level bullet under the right heading, in [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) format. +- `DETAILED_CHANGELOG.md` — a dated long-form entry (what / why / how / commands / verification / notes), newest first. + +## Documentation requirements + +**Meticulously document all activities performed in this project.** Every action — commands run, configuration changed, files transferred, troubleshooting steps, observations — gets recorded: the exact commands and their output, the reasoning behind each action, any errors and how they were resolved, and the design choices made. Treat every session as something that may need to be audited, reproduced, or rolled back. If it was done, write it down. + +## Session log convention + +Every working session writes a log to `logs/YYYY-MM-DD-.md` (date is the session date, slug is a short kebab-case description of the change). Existing entries are the template — match their structure: symptom → diagnosis → change (with diff) → commands → verification → notes. See `logs/README.md`. diff --git a/templates/DETAILED_CHANGELOG.md.tmpl b/templates/DETAILED_CHANGELOG.md.tmpl new file mode 100644 index 0000000..b4e3efe --- /dev/null +++ b/templates/DETAILED_CHANGELOG.md.tmpl @@ -0,0 +1,33 @@ +# Detailed Changelog + +The long-form companion to `CHANGELOG.md`. Where `CHANGELOG.md` says *what* +changed in one line, this file records *why* and *how* — enough for a future +reader to audit, reproduce, or roll back any change without re-deriving it. + +Add a new entry (newest first) for every meaningful change. Use the template +below; drop sections that genuinely don't apply. + +--- + +## YYYY-MM-DD — + +**Summary:** One or two sentences on what changed and the user-facing effect. + +**Why:** The problem, request, or constraint that prompted this. Link the issue +/ ticket / discussion if there is one. + +**What changed:** +- File-by-file or component-by-component list of the edits. + +**How / commands run:** +``` +# exact commands executed, with the relevant output +``` + +**Errors encountered & resolution:** Anything that went wrong and how it was +fixed (or why it was left as-is). + +**Verification:** How the change was proven to work — tests run, scanners, +manual checks, screenshots. + +**Notes / follow-ups:** Design choices, trade-offs, and anything deferred. diff --git a/templates/devcontainer/Dockerfile.tmpl b/templates/devcontainer/Dockerfile.tmpl index c47f458..459ba86 100644 --- a/templates/devcontainer/Dockerfile.tmpl +++ b/templates/devcontainer/Dockerfile.tmpl @@ -249,6 +249,9 @@ RUN if [ -n "$AIDC_TOOLCHAINS" ]; then \ php) \ sudo apt-get update && \ sudo apt-get install -y --no-install-recommends php-cli ;; \ + shell) \ + sudo apt-get update && \ + sudo apt-get install -y --no-install-recommends shellcheck ;; \ node) \ echo "aidc: node 22 already in base (npm/pnpm/yarn/bun via pmg shims; npm audit built-in)" ;; \ python) \ diff --git a/templates/logs/README.md.tmpl b/templates/logs/README.md.tmpl new file mode 100644 index 0000000..6b13308 --- /dev/null +++ b/templates/logs/README.md.tmpl @@ -0,0 +1,28 @@ +# Session logs + +Every working session writes a log here so the project stays auditable, +reproducible, and rollback-friendly. + +## Convention + +One file per session: `logs/YYYY-MM-DD-.md` + +- `YYYY-MM-DD` — the session date. +- `` — a short kebab-case description of the change (e.g. + `fix-login-redirect`, `add-rescan-command`). + +Existing entries are the template — match their structure: + +1. **Symptom / goal** — what prompted the session. +2. **Diagnosis** — what was investigated and found. +3. **Change** — what was done, with the diff or key edits. +4. **Commands** — exact commands run and their output/result. +5. **Verification** — how the change was confirmed (tests, scanners, manual). +6. **Notes** — reasoning, design choices, errors hit, and follow-ups. + +## What to record + +Document all activities: connections made, commands run, configuration changes, +files transferred, troubleshooting steps, observations, the reasoning behind +each action, errors encountered and how they were resolved, and the logical +choices made about the design. If it was done, it should be written down. From 575356639634aa4c6d9bb7885335534eadc97c29 Mon Sep 17 00:00:00 2001 From: Anant Shrivastava Date: Fri, 26 Jun 2026 03:00:44 +0530 Subject: [PATCH 2/5] multiple action items : mostly scafolding updates --- .github/scripts/test-bootstrap-state.sh | 115 ++++++++++++++++++ .github/workflows/shellcheck.yml | 3 + CHANGELOG.md | 4 + DETAILED_CHANGELOG.md | 80 +++++++++++- README.md | 4 +- SECURITY.md | 2 +- docs/install.md | 2 +- docs/security.md | 12 +- templates/devcontainer/Dockerfile.tmpl | 3 - .../scripts/bootstrap-state.sh.tmpl | 105 ++++++++++++---- 10 files changed, 296 insertions(+), 34 deletions(-) create mode 100755 .github/scripts/test-bootstrap-state.sh diff --git a/.github/scripts/test-bootstrap-state.sh b/.github/scripts/test-bootstrap-state.sh new file mode 100755 index 0000000..b961939 --- /dev/null +++ b/.github/scripts/test-bootstrap-state.sh @@ -0,0 +1,115 @@ +#!/usr/bin/env bash +# +# Unit tests for strip_host_hooks() in the devcontainer bootstrap script. +# +# Sources the template (made importable by its exec-guard) and exercises the +# hook-stripping logic on fixture settings.json blobs: gryph/cot commands are +# removed, rtk + user hooks are preserved, emptied events are pruned, the +# transform is idempotent, and malformed/missing input doesn't crash. +# +# Run: .github/scripts/test-bootstrap-state.sh +# CI: wired into .github/workflows/shellcheck.yml +set -euo pipefail + +here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +repo_root="$(cd "$here/../.." && pwd)" +tmpl="$repo_root/templates/devcontainer/scripts/bootstrap-state.sh.tmpl" + +# Source the prod template. Its exec-guard stops the init/sync dispatch from +# firing; we only want the strip_host_hooks() definition. +# shellcheck source=../../../templates/devcontainer/scripts/bootstrap-state.sh.tmpl +# shellcheck disable=SC1090 +source "$tmpl" + +pass=0 +fail=0 +assert_eq() { #