From 722a315218195c8dd42ae55f0ff0b8903bf3f736 Mon Sep 17 00:00:00 2001 From: hwanggg Date: Thu, 12 Mar 2026 18:35:18 +0900 Subject: [PATCH 01/23] Add BATS test suite for all 5 shared-context scripts Set up bats-core as a git submodule and create 47 tests covering bootstrap_repo, prepare_branch, validate_context, summarize_context, and sync_context scripts with both success and error path coverage. Co-Authored-By: Claude Opus 4.6 --- .gitmodules | 3 + tests/bootstrap_repo.bats | 86 ++++++++++++++++++++++++++ tests/lib/bats-core | 1 + tests/prepare_branch.bats | 95 +++++++++++++++++++++++++++++ tests/run_tests.sh | 15 +++++ tests/summarize_context.bats | 115 +++++++++++++++++++++++++++++++++++ tests/sync_context.bats | 98 +++++++++++++++++++++++++++++ tests/test_helper.bash | 74 ++++++++++++++++++++++ tests/validate_context.bats | 113 ++++++++++++++++++++++++++++++++++ 9 files changed, 600 insertions(+) create mode 100644 .gitmodules create mode 100644 tests/bootstrap_repo.bats create mode 160000 tests/lib/bats-core create mode 100644 tests/prepare_branch.bats create mode 100755 tests/run_tests.sh create mode 100644 tests/summarize_context.bats create mode 100644 tests/sync_context.bats create mode 100644 tests/test_helper.bash create mode 100644 tests/validate_context.bats diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..26e79fb --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "tests/lib/bats-core"] + path = tests/lib/bats-core + url = https://github.com/bats-core/bats-core.git diff --git a/tests/bootstrap_repo.bats b/tests/bootstrap_repo.bats new file mode 100644 index 0000000..0fae54e --- /dev/null +++ b/tests/bootstrap_repo.bats @@ -0,0 +1,86 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { setup_tmpdir; } +teardown() { teardown_tmpdir; } + +@test "bootstrap creates CONTEXT.md and TIMELINE.md by default" { + run "$SCRIPTS_DIR/bootstrap_repo.sh" --target "$TEST_TMPDIR" + [ "$status" -eq 0 ] + [ -f "$TEST_TMPDIR/CONTEXT.md" ] + [ -f "$TEST_TMPDIR/TIMELINE.md" ] + [[ "$output" == *"Created"* ]] +} + +@test "bootstrap with --with-handoff creates HANDOFF.md" { + run "$SCRIPTS_DIR/bootstrap_repo.sh" --target "$TEST_TMPDIR" --with-handoff + [ "$status" -eq 0 ] + [ -f "$TEST_TMPDIR/HANDOFF.md" ] +} + +@test "bootstrap with --with-policy creates POLICY.md" { + run "$SCRIPTS_DIR/bootstrap_repo.sh" --target "$TEST_TMPDIR" --with-policy + [ "$status" -eq 0 ] + [ -f "$TEST_TMPDIR/POLICY.md" ] +} + +@test "bootstrap with all optional flags creates all 4 files" { + run "$SCRIPTS_DIR/bootstrap_repo.sh" --target "$TEST_TMPDIR" --with-handoff --with-policy + [ "$status" -eq 0 ] + [ -f "$TEST_TMPDIR/CONTEXT.md" ] + [ -f "$TEST_TMPDIR/TIMELINE.md" ] + [ -f "$TEST_TMPDIR/HANDOFF.md" ] + [ -f "$TEST_TMPDIR/POLICY.md" ] +} + +@test "bootstrap refuses to overwrite existing files without --force" { + "$SCRIPTS_DIR/bootstrap_repo.sh" --target "$TEST_TMPDIR" + run "$SCRIPTS_DIR/bootstrap_repo.sh" --target "$TEST_TMPDIR" + [ "$status" -ne 0 ] + [[ "$output" == *"Refusing to overwrite"* ]] +} + +@test "bootstrap --force overwrites existing files" { + "$SCRIPTS_DIR/bootstrap_repo.sh" --target "$TEST_TMPDIR" + run "$SCRIPTS_DIR/bootstrap_repo.sh" --target "$TEST_TMPDIR" --force + [ "$status" -eq 0 ] +} + +@test "bootstrap creates target directory if it does not exist" { + local new_dir="$TEST_TMPDIR/subdir/nested" + run "$SCRIPTS_DIR/bootstrap_repo.sh" --target "$new_dir" + [ "$status" -eq 0 ] + [ -f "$new_dir/CONTEXT.md" ] +} + +@test "bootstrap warns when target is not a git repo" { + run "$SCRIPTS_DIR/bootstrap_repo.sh" --target "$TEST_TMPDIR" + [ "$status" -eq 0 ] + [[ "$output" == *"does not look like a Git repository"* ]] +} + +@test "bootstrap does not warn when target is a git repo" { + git -C "$TEST_TMPDIR" init -b main >/dev/null 2>&1 + run "$SCRIPTS_DIR/bootstrap_repo.sh" --target "$TEST_TMPDIR" + [ "$status" -eq 0 ] + [[ "$output" != *"does not look like a Git repository"* ]] +} + +@test "bootstrap --help prints usage and exits 0" { + run "$SCRIPTS_DIR/bootstrap_repo.sh" --help + [ "$status" -eq 0 ] + [[ "$output" == *"Usage:"* ]] +} + +@test "bootstrap unknown argument fails" { + run "$SCRIPTS_DIR/bootstrap_repo.sh" --bogus + [ "$status" -ne 0 ] + [[ "$output" == *"Unknown argument"* ]] +} + +@test "bootstrap --target with missing value fails" { + run "$SCRIPTS_DIR/bootstrap_repo.sh" --target + [ "$status" -ne 0 ] + [[ "$output" == *"Missing value"* ]] +} diff --git a/tests/lib/bats-core b/tests/lib/bats-core new file mode 160000 index 0000000..d9faff0 --- /dev/null +++ b/tests/lib/bats-core @@ -0,0 +1 @@ +Subproject commit d9faff0d7bc32e7adebc6552446f978118d3ab3b diff --git a/tests/prepare_branch.bats b/tests/prepare_branch.bats new file mode 100644 index 0000000..a5fdb34 --- /dev/null +++ b/tests/prepare_branch.bats @@ -0,0 +1,95 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { setup_git_repo; } +teardown() { teardown_tmpdir; } + +@test "prepare_branch creates context branch with correct name" { + run "$SCRIPTS_DIR/prepare_branch.sh" --repo "$TEST_TMPDIR" --actor "alice" --slug "feature" --date "2026-01-15" + [ "$status" -eq 0 ] + [[ "$output" == *"Created and switched to context/alice/2026-01-15-feature"* ]] + branch="$(git -C "$TEST_TMPDIR" rev-parse --abbrev-ref HEAD)" + [ "$branch" = "context/alice/2026-01-15-feature" ] +} + +@test "prepare_branch switches to existing branch" { + "$SCRIPTS_DIR/prepare_branch.sh" --repo "$TEST_TMPDIR" --actor "alice" --slug "feat" --date "2026-01-15" + git -C "$TEST_TMPDIR" checkout main >/dev/null 2>&1 + run "$SCRIPTS_DIR/prepare_branch.sh" --repo "$TEST_TMPDIR" --actor "alice" --slug "feat" --date "2026-01-15" + [ "$status" -eq 0 ] + [[ "$output" == *"Switched to existing branch"* ]] +} + +@test "prepare_branch sanitizes actor to lowercase and replaces special chars" { + run "$SCRIPTS_DIR/prepare_branch.sh" --repo "$TEST_TMPDIR" --actor "John Doe" --slug "test" --date "2026-01-15" + [ "$status" -eq 0 ] + branch="$(git -C "$TEST_TMPDIR" rev-parse --abbrev-ref HEAD)" + [ "$branch" = "context/john-doe/2026-01-15-test" ] +} + +@test "prepare_branch sanitizes slug" { + run "$SCRIPTS_DIR/prepare_branch.sh" --repo "$TEST_TMPDIR" --actor "bot" --slug "My Feature!!" --date "2026-01-15" + [ "$status" -eq 0 ] + branch="$(git -C "$TEST_TMPDIR" rev-parse --abbrev-ref HEAD)" + [ "$branch" = "context/bot/2026-01-15-my-feature" ] +} + +@test "prepare_branch fails when actor is missing" { + run "$SCRIPTS_DIR/prepare_branch.sh" --repo "$TEST_TMPDIR" --slug "test" + [ "$status" -ne 0 ] + [[ "$output" == *"--actor is required"* ]] +} + +@test "prepare_branch fails when slug is missing" { + run "$SCRIPTS_DIR/prepare_branch.sh" --repo "$TEST_TMPDIR" --actor "alice" + [ "$status" -ne 0 ] + [[ "$output" == *"--slug is required"* ]] +} + +@test "prepare_branch fails when actor sanitizes to empty" { + run "$SCRIPTS_DIR/prepare_branch.sh" --repo "$TEST_TMPDIR" --actor "!!!" --slug "test" + [ "$status" -ne 0 ] + [[ "$output" == *"at least one letter or digit"* ]] +} + +@test "prepare_branch fails when slug sanitizes to empty" { + run "$SCRIPTS_DIR/prepare_branch.sh" --repo "$TEST_TMPDIR" --actor "alice" --slug "!!!" + [ "$status" -ne 0 ] + [[ "$output" == *"at least one letter or digit"* ]] +} + +@test "prepare_branch fails on dirty working tree" { + echo "dirty" > "$TEST_TMPDIR/dirty.txt" + run "$SCRIPTS_DIR/prepare_branch.sh" --repo "$TEST_TMPDIR" --actor "alice" --slug "test" + [ "$status" -ne 0 ] + [[ "$output" == *"not clean"* ]] +} + +@test "prepare_branch fails when not on base branch" { + git -C "$TEST_TMPDIR" checkout -b other >/dev/null 2>&1 + run "$SCRIPTS_DIR/prepare_branch.sh" --repo "$TEST_TMPDIR" --actor "alice" --slug "test" + [ "$status" -ne 0 ] + [[ "$output" == *"Check out main"* ]] +} + +@test "prepare_branch fails when not a git repo" { + local nongit="$(mktemp -d)" + run "$SCRIPTS_DIR/prepare_branch.sh" --repo "$nongit" --actor "alice" --slug "test" + [ "$status" -ne 0 ] + [[ "$output" == *"Not a Git repository"* ]] + rm -rf "$nongit" +} + +@test "prepare_branch --help prints usage" { + run "$SCRIPTS_DIR/prepare_branch.sh" --help + [ "$status" -eq 0 ] + [[ "$output" == *"Usage:"* ]] +} + +@test "prepare_branch respects custom --base-branch" { + git -C "$TEST_TMPDIR" checkout -b develop >/dev/null 2>&1 + run "$SCRIPTS_DIR/prepare_branch.sh" --repo "$TEST_TMPDIR" --actor "alice" --slug "test" --base-branch "develop" --date "2026-01-15" + [ "$status" -eq 0 ] + [[ "$output" == *"Created and switched to"* ]] +} diff --git a/tests/run_tests.sh b/tests/run_tests.sh new file mode 100755 index 0000000..ae0c29f --- /dev/null +++ b/tests/run_tests.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Run all BATS tests from the project root. + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +project_root="${script_dir%/tests}" +bats_bin="${script_dir}/lib/bats-core/bin/bats" + +if [[ ! -x "$bats_bin" ]]; then + echo "BATS not found. Run: git submodule update --init --recursive" >&2 + exit 1 +fi + +exec "$bats_bin" "$@" "$script_dir"/*.bats diff --git a/tests/summarize_context.bats b/tests/summarize_context.bats new file mode 100644 index 0000000..060daff --- /dev/null +++ b/tests/summarize_context.bats @@ -0,0 +1,115 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { setup_tmpdir; } +teardown() { teardown_tmpdir; } + +@test "summarize_context prints section counts and no-issue hint for template files" { + bootstrap_context + + run "$SCRIPTS_DIR/summarize_context.sh" --repo "$TEST_TMPDIR" + + [ "$status" -eq 0 ] + [[ "$output" == *"# Shared Context Summary"* ]] + [[ "$output" == *"## Overview"* ]] + [[ "$output" == *"- No obvious compaction issues detected."* ]] +} + +@test "summarize_context reports duplicate bullets" { + bootstrap_context + cat > "$TEST_TMPDIR/CONTEXT.md" <<'EOF' +# Shared Context + +## Overview + +- Same bullet + +## Stable Facts + +- Same bullet + +## Active Context + +- Active item + +## Decisions + +- Decision item + +## Open Questions + +- Question item +EOF + + run "$SCRIPTS_DIR/summarize_context.sh" --repo "$TEST_TMPDIR" + + [ "$status" -eq 0 ] + [[ "$output" == *"Duplicate bullets detected in CONTEXT.md"* ]] + [[ "$output" == *"- Same bullet"* ]] +} + +@test "summarize_context warns when TIMELINE.md has more than 20 entries" { + bootstrap_context + # Append 21 ### entries to TIMELINE.md + for i in $(seq 1 21); do + printf '\n### 2026-03-12T10:%02d:00Z | bot\n- Entry %d\n' "$i" "$i" >> "$TEST_TMPDIR/TIMELINE.md" + done + + run "$SCRIPTS_DIR/summarize_context.sh" --repo "$TEST_TMPDIR" + + [ "$status" -eq 0 ] + [[ "$output" == *"more than 20 entries"* ]] +} + +@test "summarize_context warns when CONTEXT.md exceeds 120 lines" { + bootstrap_context + { + echo "# Shared Context" + echo "" + echo "## Overview" + for i in $(seq 1 30); do echo "- Fact $i"; done + echo "" + echo "## Stable Facts" + for i in $(seq 1 30); do echo "- Stable $i"; done + echo "" + echo "## Active Context" + for i in $(seq 1 30); do echo "- Active $i"; done + echo "" + echo "## Decisions" + for i in $(seq 1 15); do echo "- Decision $i"; done + echo "" + echo "## Open Questions" + for i in $(seq 1 15); do echo "- Question $i"; done + } > "$TEST_TMPDIR/CONTEXT.md" + + run "$SCRIPTS_DIR/summarize_context.sh" --repo "$TEST_TMPDIR" + + [ "$status" -eq 0 ] + [[ "$output" == *"longer than 120 lines"* ]] +} + +@test "summarize_context fails when CONTEXT.md is missing" { + cp "$TEMPLATES_DIR/TIMELINE.md" "$TEST_TMPDIR/TIMELINE.md" + + run "$SCRIPTS_DIR/summarize_context.sh" --repo "$TEST_TMPDIR" + + [ "$status" -ne 0 ] + [[ "$output" == *"Missing CONTEXT.md"* ]] +} + +@test "summarize_context fails when TIMELINE.md is missing" { + cp "$TEMPLATES_DIR/CONTEXT.md" "$TEST_TMPDIR/CONTEXT.md" + + run "$SCRIPTS_DIR/summarize_context.sh" --repo "$TEST_TMPDIR" + + [ "$status" -ne 0 ] + [[ "$output" == *"Missing TIMELINE.md"* ]] +} + +@test "summarize_context --help prints usage" { + run "$SCRIPTS_DIR/summarize_context.sh" --help + + [ "$status" -eq 0 ] + [[ "$output" == *"Usage:"* ]] +} diff --git a/tests/sync_context.bats b/tests/sync_context.bats new file mode 100644 index 0000000..8cfbb6b --- /dev/null +++ b/tests/sync_context.bats @@ -0,0 +1,98 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { setup_tracking_repo; } +teardown() { teardown_tmpdir; } + +@test "sync_context fast-forwards when remote is ahead" { + push_remote_commit "remote.txt" "from remote" "remote update" + + run "$SCRIPTS_DIR/sync_context.sh" --repo "$TRACKING_REPO_DIR" + + [ "$status" -eq 0 ] + [[ "$output" == *"Fast-forwarded main to origin/main"* ]] + [ -f "$TRACKING_REPO_DIR/remote.txt" ] +} + +@test "sync_context reports up to date when no changes exist" { + run "$SCRIPTS_DIR/sync_context.sh" --repo "$TRACKING_REPO_DIR" + + [ "$status" -eq 0 ] + [[ "$output" == *"main is already up to date with origin/main"* ]] +} + +@test "sync_context fails on dirty working tree" { + printf 'dirty\n' > "$TRACKING_REPO_DIR/dirty.txt" + + run "$SCRIPTS_DIR/sync_context.sh" --repo "$TRACKING_REPO_DIR" + + [ "$status" -ne 0 ] + [[ "$output" == *"Working tree is not clean"* ]] +} + +@test "sync_context fails when local branch is ahead" { + printf 'local\n' > "$TRACKING_REPO_DIR/local.txt" + git -C "$TRACKING_REPO_DIR" add local.txt + git -C "$TRACKING_REPO_DIR" commit -m "local change" >/dev/null 2>&1 + + run "$SCRIPTS_DIR/sync_context.sh" --repo "$TRACKING_REPO_DIR" + + [ "$status" -ne 0 ] + [[ "$output" == *"Local main is ahead of origin/main"* ]] +} + +@test "sync_context fails when local and remote have diverged" { + push_remote_commit "remote.txt" "from remote" "remote update" + printf 'local\n' > "$TRACKING_REPO_DIR/local.txt" + git -C "$TRACKING_REPO_DIR" add local.txt + git -C "$TRACKING_REPO_DIR" commit -m "local change" >/dev/null 2>&1 + + run "$SCRIPTS_DIR/sync_context.sh" --repo "$TRACKING_REPO_DIR" + + [ "$status" -ne 0 ] + [[ "$output" == *"have diverged"* ]] +} + +@test "sync_context fails when current branch is not base branch" { + git -C "$TRACKING_REPO_DIR" checkout -b feature >/dev/null 2>&1 + + run "$SCRIPTS_DIR/sync_context.sh" --repo "$TRACKING_REPO_DIR" + + [ "$status" -ne 0 ] + [[ "$output" == *"Check out main before syncing"* ]] +} + +@test "sync_context respects custom base branch" { + git -C "$TRACKING_REPO_DIR" checkout -b develop >/dev/null 2>&1 + git -C "$TRACKING_REPO_DIR" push -u origin develop >/dev/null 2>&1 + git -C "$TRACKING_REPO_DIR" checkout develop >/dev/null 2>&1 + + run "$SCRIPTS_DIR/sync_context.sh" --repo "$TRACKING_REPO_DIR" --base-branch develop + + [ "$status" -eq 0 ] + [[ "$output" == *"develop is already up to date with origin/develop"* ]] +} + +@test "sync_context fails when remote is missing" { + run "$SCRIPTS_DIR/sync_context.sh" --repo "$TRACKING_REPO_DIR" --remote upstream + + [ "$status" -ne 0 ] + [[ "$output" == *"Remote not found: upstream"* ]] +} + +@test "sync_context fails when remote base branch is missing" { + git -C "$TRACKING_REPO_DIR" checkout -b develop >/dev/null 2>&1 + + run "$SCRIPTS_DIR/sync_context.sh" --repo "$TRACKING_REPO_DIR" --base-branch develop + + [ "$status" -ne 0 ] + [[ "$output" == *"Remote branch not found: origin/develop"* ]] +} + +@test "sync_context --help prints usage" { + run "$SCRIPTS_DIR/sync_context.sh" --help + + [ "$status" -eq 0 ] + [[ "$output" == *"Usage:"* ]] +} diff --git a/tests/test_helper.bash b/tests/test_helper.bash new file mode 100644 index 0000000..fe1c345 --- /dev/null +++ b/tests/test_helper.bash @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# Common test helper for all BATS test files. + +PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +SCRIPTS_DIR="${PROJECT_ROOT}/scripts" +TEMPLATES_DIR="${PROJECT_ROOT}/assets/templates" + +# Create a fresh temporary directory for each test. +setup_tmpdir() { + TEST_TMPDIR="$(mktemp -d)" +} + +# Remove the temporary directory after each test. +teardown_tmpdir() { + [[ -n "${TEST_TMPDIR:-}" && -d "$TEST_TMPDIR" ]] && rm -rf "$TEST_TMPDIR" +} + +# Initialise a bare-bones Git repo in TEST_TMPDIR with an initial commit on main. +setup_git_repo() { + setup_tmpdir + git -C "$TEST_TMPDIR" init -b main >/dev/null 2>&1 + git -C "$TEST_TMPDIR" config user.email "test@test.com" + git -C "$TEST_TMPDIR" config user.name "Test" + touch "$TEST_TMPDIR/.gitkeep" + git -C "$TEST_TMPDIR" add .gitkeep + git -C "$TEST_TMPDIR" commit -m "initial" >/dev/null 2>&1 +} + +# Bootstrap shared-context files in the given directory (defaults to TEST_TMPDIR). +bootstrap_context() { + local dir="${1:-$TEST_TMPDIR}" + cp "$TEMPLATES_DIR/CONTEXT.md" "$dir/CONTEXT.md" + cp "$TEMPLATES_DIR/TIMELINE.md" "$dir/TIMELINE.md" +} + +setup_tracking_repo() { + setup_tmpdir + + REMOTE_REPO_DIR="$TEST_TMPDIR/remote.git" + TRACKING_REPO_DIR="$TEST_TMPDIR/repo" + local seed_dir="$TEST_TMPDIR/seed" + + git init --bare "$REMOTE_REPO_DIR" >/dev/null 2>&1 + git -C "$REMOTE_REPO_DIR" symbolic-ref HEAD refs/heads/main + git init -b main "$seed_dir" >/dev/null 2>&1 + git -C "$seed_dir" config user.email "test@test.com" + git -C "$seed_dir" config user.name "Test" + touch "$seed_dir/.gitkeep" + git -C "$seed_dir" add .gitkeep + git -C "$seed_dir" commit -m "initial" >/dev/null 2>&1 + git -C "$seed_dir" remote add origin "$REMOTE_REPO_DIR" + git -C "$seed_dir" push -u origin main >/dev/null 2>&1 + + git clone "$REMOTE_REPO_DIR" "$TRACKING_REPO_DIR" >/dev/null 2>&1 + git -C "$TRACKING_REPO_DIR" config user.email "test@test.com" + git -C "$TRACKING_REPO_DIR" config user.name "Test" +} + +push_remote_commit() { + local file_name="$1" + local file_content="$2" + local commit_message="$3" + local worktree_dir="$TEST_TMPDIR/remote-work" + + rm -rf "$worktree_dir" + git clone "$REMOTE_REPO_DIR" "$worktree_dir" >/dev/null 2>&1 + git -C "$worktree_dir" config user.email "test@test.com" + git -C "$worktree_dir" config user.name "Test" + printf '%s\n' "$file_content" > "$worktree_dir/$file_name" + git -C "$worktree_dir" add "$file_name" + git -C "$worktree_dir" commit -m "$commit_message" >/dev/null 2>&1 + git -C "$worktree_dir" push origin main >/dev/null 2>&1 + rm -rf "$worktree_dir" +} diff --git a/tests/validate_context.bats b/tests/validate_context.bats new file mode 100644 index 0000000..091816a --- /dev/null +++ b/tests/validate_context.bats @@ -0,0 +1,113 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { setup_tmpdir; } +teardown() { teardown_tmpdir; } + +@test "validate_context accepts bootstrapped template state" { + bootstrap_context + + run "$SCRIPTS_DIR/validate_context.sh" --repo "$TEST_TMPDIR" + + [ "$status" -eq 0 ] + [[ "$output" == *"Shared context structure is valid"* ]] +} + +@test "validate_context rejects missing required context heading" { + bootstrap_context + python3 - <<'PY' "$TEST_TMPDIR/CONTEXT.md" +from pathlib import Path +import sys + +path = Path(sys.argv[1]) +text = path.read_text() +path.write_text(text.replace("## Decisions\n\n", "", 1)) +PY + + run "$SCRIPTS_DIR/validate_context.sh" --repo "$TEST_TMPDIR" + + [ "$status" -ne 0 ] + [[ "$output" == *"CONTEXT.md is missing required heading matching: ^## Decisions$"* ]] +} + +@test "validate_context rejects timeline entry missing required fields" { + bootstrap_context + cat > "$TEST_TMPDIR/TIMELINE.md" <<'EOF' +# Timeline + +Use this file as an append-only record of meaningful context changes. + +## Entry Template + +```markdown +### 2026-03-12T09:15:00Z | agent-name +- Timestamp: 2026-03-12T09:15:00Z +- Actor: agent-name +- Trigger: What prompted this update +- Applied Changes: + - Summarize the facts, decisions, or context that changed +- Unresolved Items: + - List remaining uncertainty, or write `- None` +``` + +## Entries + +### 2026-03-12T10:00:00Z | bot +- Timestamp: 2026-03-12T10:00:00Z +- Actor: bot +- Trigger: Test broken entry +- Applied Changes: + - Added an invalid test case +EOF + + run "$SCRIPTS_DIR/validate_context.sh" --repo "$TEST_TMPDIR" + + [ "$status" -ne 0 ] + [[ "$output" == *"TIMELINE.md has an entry missing one of"* ]] +} + +@test "validate_context accepts a complete timeline entry" { + bootstrap_context + cat > "$TEST_TMPDIR/TIMELINE.md" <<'EOF' +# Timeline + +Use this file as an append-only record of meaningful context changes. + +## Entry Template + +```markdown +### 2026-03-12T09:15:00Z | agent-name +- Timestamp: 2026-03-12T09:15:00Z +- Actor: agent-name +- Trigger: What prompted this update +- Applied Changes: + - Summarize the facts, decisions, or context that changed +- Unresolved Items: + - List remaining uncertainty, or write `- None` +``` + +## Entries + +### 2026-03-12T10:00:00Z | bot +- Timestamp: 2026-03-12T10:00:00Z +- Actor: bot +- Trigger: Test valid entry +- Applied Changes: + - Added a valid test case +- Unresolved Items: + - None +EOF + + run "$SCRIPTS_DIR/validate_context.sh" --repo "$TEST_TMPDIR" + + [ "$status" -eq 0 ] + [[ "$output" == *"Shared context structure is valid"* ]] +} + +@test "validate_context --help prints usage" { + run "$SCRIPTS_DIR/validate_context.sh" --help + + [ "$status" -eq 0 ] + [[ "$output" == *"Usage:"* ]] +} From 951c8ceb7cc930e7c0512baa7bd60708ef7bfdba Mon Sep 17 00:00:00 2001 From: hwanggg Date: Thu, 12 Mar 2026 18:49:12 +0900 Subject: [PATCH 02/23] Add shared-context validation job to CI pipeline Adds a validate-context job that runs validate_context.sh on PRs when shared-context files (CONTEXT.md, TIMELINE.md, HANDOFF.md, POLICY.md) are modified. BATS test job also gets a display name. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/tests.yml | 50 +++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..ac1bc6e --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,50 @@ +name: Tests + +on: + push: + branches: + - main + pull_request: + +jobs: + bats: + name: BATS Test Suite + runs-on: ubuntu-latest + + steps: + - name: Check out repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Run BATS suite + run: ./tests/run_tests.sh + + validate-context: + name: Validate Shared Context + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Check for shared-context file changes + id: changed + run: | + FILES=$(gh pr diff "${{ github.event.pull_request.number }}" --name-only 2>/dev/null || git diff --name-only "${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }}") + if echo "$FILES" | grep -qE '^(CONTEXT\.md|TIMELINE\.md|HANDOFF\.md|POLICY\.md)$'; then + echo "has_context_files=true" >> "$GITHUB_OUTPUT" + else + echo "has_context_files=false" >> "$GITHUB_OUTPUT" + fi + env: + GH_TOKEN: ${{ github.token }} + + - name: Run validate_context.sh + if: steps.changed.outputs.has_context_files == 'true' + run: ./scripts/validate_context.sh + + - name: Skip validation (no context files changed) + if: steps.changed.outputs.has_context_files != 'true' + run: echo "No shared-context files changed — skipping validation" From 0270d755bbfb4b495e035922ff45e7c6c6d59817 Mon Sep 17 00:00:00 2001 From: hwanggg Date: Thu, 12 Mar 2026 18:55:12 +0900 Subject: [PATCH 03/23] Add --strict mode to validate_context.sh for enhanced field validation Adds ISO 8601 timestamp format validation, non-empty Actor/Trigger field checks behind a --strict flag, maintaining backward compatibility by default. Includes 5 new BATS tests covering strict mode acceptance, rejection of invalid timestamps, empty Actor, empty Trigger, and backward compat. Co-Authored-By: Claude Opus 4.6 --- scripts/validate_context.sh | 47 ++++++++- tests/validate_context.bats | 190 ++++++++++++++++++++++++++++++++++++ 2 files changed, 233 insertions(+), 4 deletions(-) diff --git a/scripts/validate_context.sh b/scripts/validate_context.sh index be0ba74..b03d5f5 100755 --- a/scripts/validate_context.sh +++ b/scripts/validate_context.sh @@ -3,13 +3,18 @@ set -euo pipefail usage() { cat <<'EOF' -Usage: validate_context.sh [--repo DIR] +Usage: validate_context.sh [--repo DIR] [--strict] Validate required shared-context files and basic document structure. + +Options: + --repo DIR Path to the repository directory (default: .) + --strict Enable strict validation (ISO 8601 timestamps, non-empty fields) EOF } repo_dir="." +strict=0 while [[ $# -gt 0 ]]; do case "$1" in @@ -18,6 +23,10 @@ while [[ $# -gt 0 ]]; do repo_dir="$2" shift 2 ;; + --strict) + strict=1 + shift + ;; -h|--help) usage exit 0 @@ -97,9 +106,39 @@ while IFS= read -r line; do [[ "$in_entry" -eq 1 ]] || continue - [[ "$line" =~ ^-\ Timestamp:\ ]] && has_timestamp=1 - [[ "$line" =~ ^-\ Actor:\ ]] && has_actor=1 - [[ "$line" =~ ^-\ Trigger:\ ]] && has_trigger=1 + if [[ "$line" =~ ^-\ Timestamp: ]]; then + has_timestamp=1 + if [[ "$strict" -eq 1 ]]; then + local_val="${line#- Timestamp:}" + local_val="${local_val#"${local_val%%[![:space:]]*}"}" + if [[ -z "$local_val" || ! "$local_val" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2} ]]; then + echo "TIMELINE.md has an entry with invalid ISO 8601 timestamp: '${local_val}'" >&2 + exit 1 + fi + fi + fi + if [[ "$line" =~ ^-\ Actor: ]]; then + has_actor=1 + if [[ "$strict" -eq 1 ]]; then + local_val="${line#- Actor:}" + local_val="${local_val#"${local_val%%[![:space:]]*}"}" + if [[ -z "$local_val" ]]; then + echo "TIMELINE.md has an entry with empty Actor field" >&2 + exit 1 + fi + fi + fi + if [[ "$line" =~ ^-\ Trigger: ]]; then + has_trigger=1 + if [[ "$strict" -eq 1 ]]; then + local_val="${line#- Trigger:}" + local_val="${local_val#"${local_val%%[![:space:]]*}"}" + if [[ -z "$local_val" ]]; then + echo "TIMELINE.md has an entry with empty Trigger field" >&2 + exit 1 + fi + fi + fi [[ "$line" =~ ^-\ Applied\ Changes: ]] && has_applied=1 [[ "$line" =~ ^-\ Unresolved\ Items: ]] && has_unresolved=1 done < TIMELINE.md diff --git a/tests/validate_context.bats b/tests/validate_context.bats index 091816a..57e986a 100644 --- a/tests/validate_context.bats +++ b/tests/validate_context.bats @@ -111,3 +111,193 @@ EOF [ "$status" -eq 0 ] [[ "$output" == *"Usage:"* ]] } + +@test "validate_context --strict accepts valid ISO 8601 timestamp" { + bootstrap_context + cat > "$TEST_TMPDIR/TIMELINE.md" <<'EOF' +# Timeline + +Use this file as an append-only record of meaningful context changes. + +## Entry Template + +```markdown +### 2026-03-12T09:15:00Z | agent-name +- Timestamp: 2026-03-12T09:15:00Z +- Actor: agent-name +- Trigger: What prompted this update +- Applied Changes: + - Summarize the facts, decisions, or context that changed +- Unresolved Items: + - List remaining uncertainty, or write `- None` +``` + +## Entries + +### 2026-03-12T10:00:00Z | bot +- Timestamp: 2026-03-12T10:00:00Z +- Actor: bot +- Trigger: Test valid entry +- Applied Changes: + - Added a valid test case +- Unresolved Items: + - None +EOF + + run "$SCRIPTS_DIR/validate_context.sh" --repo "$TEST_TMPDIR" --strict + + [ "$status" -eq 0 ] + [[ "$output" == *"Shared context structure is valid"* ]] +} + +@test "validate_context --strict rejects invalid timestamp format" { + bootstrap_context + cat > "$TEST_TMPDIR/TIMELINE.md" <<'EOF' +# Timeline + +Use this file as an append-only record of meaningful context changes. + +## Entry Template + +```markdown +### 2026-03-12T09:15:00Z | agent-name +- Timestamp: 2026-03-12T09:15:00Z +- Actor: agent-name +- Trigger: What prompted this update +- Applied Changes: + - Summarize the facts, decisions, or context that changed +- Unresolved Items: + - List remaining uncertainty, or write `- None` +``` + +## Entries + +### bad-timestamp | bot +- Timestamp: not-a-date +- Actor: bot +- Trigger: Test invalid timestamp +- Applied Changes: + - Added a test case +- Unresolved Items: + - None +EOF + + run "$SCRIPTS_DIR/validate_context.sh" --repo "$TEST_TMPDIR" --strict + + [ "$status" -ne 0 ] + [[ "$output" == *"invalid ISO 8601 timestamp"* ]] +} + +@test "validate_context --strict rejects empty Actor field" { + bootstrap_context + cat > "$TEST_TMPDIR/TIMELINE.md" <<'EOF' +# Timeline + +Use this file as an append-only record of meaningful context changes. + +## Entry Template + +```markdown +### 2026-03-12T09:15:00Z | agent-name +- Timestamp: 2026-03-12T09:15:00Z +- Actor: agent-name +- Trigger: What prompted this update +- Applied Changes: + - Summarize the facts, decisions, or context that changed +- Unresolved Items: + - List remaining uncertainty, or write `- None` +``` + +## Entries + +### 2026-03-12T10:00:00Z | +- Timestamp: 2026-03-12T10:00:00Z +- Actor: +- Trigger: Test empty actor +- Applied Changes: + - Added a test case +- Unresolved Items: + - None +EOF + + run "$SCRIPTS_DIR/validate_context.sh" --repo "$TEST_TMPDIR" --strict + + [ "$status" -ne 0 ] + [[ "$output" == *"empty Actor field"* ]] +} + +@test "validate_context --strict rejects empty Trigger field" { + bootstrap_context + cat > "$TEST_TMPDIR/TIMELINE.md" <<'EOF' +# Timeline + +Use this file as an append-only record of meaningful context changes. + +## Entry Template + +```markdown +### 2026-03-12T09:15:00Z | agent-name +- Timestamp: 2026-03-12T09:15:00Z +- Actor: agent-name +- Trigger: What prompted this update +- Applied Changes: + - Summarize the facts, decisions, or context that changed +- Unresolved Items: + - List remaining uncertainty, or write `- None` +``` + +## Entries + +### 2026-03-12T10:00:00Z | bot +- Timestamp: 2026-03-12T10:00:00Z +- Actor: bot +- Trigger: +- Applied Changes: + - Added a test case +- Unresolved Items: + - None +EOF + + run "$SCRIPTS_DIR/validate_context.sh" --repo "$TEST_TMPDIR" --strict + + [ "$status" -ne 0 ] + [[ "$output" == *"empty Trigger field"* ]] +} + +@test "validate_context without --strict passes invalid timestamp (backward compat)" { + bootstrap_context + cat > "$TEST_TMPDIR/TIMELINE.md" <<'EOF' +# Timeline + +Use this file as an append-only record of meaningful context changes. + +## Entry Template + +```markdown +### 2026-03-12T09:15:00Z | agent-name +- Timestamp: 2026-03-12T09:15:00Z +- Actor: agent-name +- Trigger: What prompted this update +- Applied Changes: + - Summarize the facts, decisions, or context that changed +- Unresolved Items: + - List remaining uncertainty, or write `- None` +``` + +## Entries + +### bad-timestamp | bot +- Timestamp: not-a-date +- Actor: bot +- Trigger: Test backward compat +- Applied Changes: + - Added a test case +- Unresolved Items: + - None +EOF + + run "$SCRIPTS_DIR/validate_context.sh" --repo "$TEST_TMPDIR" + + [ "$status" -eq 0 ] + [[ "$output" == *"Shared context structure is valid"* ]] +} From 892eabb89f2330b63b9e16de296c8b31357d66b4 Mon Sep 17 00:00:00 2001 From: hwanggg Date: Thu, 12 Mar 2026 18:57:42 +0900 Subject: [PATCH 04/23] Add compact_timeline.sh for automated TIMELINE.md compaction Moves Applied Changes from old timeline entries (default 30+ days) into CONTEXT.md Stable Facts and removes the compacted entries. Supports --dry-run, --yes (skip prompt), and --days N options. Includes 8 BATS tests. Co-Authored-By: Claude Opus 4.6 --- scripts/compact_timeline.sh | 266 ++++++++++++++++++++++++++++++++++++ tests/compact_timeline.bats | 165 ++++++++++++++++++++++ 2 files changed, 431 insertions(+) create mode 100755 scripts/compact_timeline.sh create mode 100644 tests/compact_timeline.bats diff --git a/scripts/compact_timeline.sh b/scripts/compact_timeline.sh new file mode 100755 index 0000000..77f5f87 --- /dev/null +++ b/scripts/compact_timeline.sh @@ -0,0 +1,266 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: compact_timeline.sh [--repo DIR] [--days N] [--dry-run] [--yes] + +Compact old TIMELINE.md entries by moving their Applied Changes into +CONTEXT.md Stable Facts and removing the original entries. + +Options: + --repo DIR Repository path (default: .) + --days N Compact entries older than N days (default: 30) + --dry-run Show what would change without modifying files + --yes Skip confirmation prompt + -h, --help Show this help message +EOF +} + +repo_dir="." +max_days=30 +dry_run=false +auto_yes=false + +while [[ $# -gt 0 ]]; do + case "$1" in + --repo) + [[ $# -ge 2 ]] || { echo "Missing value for --repo" >&2; exit 1; } + repo_dir="$2" + shift 2 + ;; + --days) + [[ $# -ge 2 ]] || { echo "Missing value for --days" >&2; exit 1; } + max_days="$2" + shift 2 + ;; + --dry-run) + dry_run=true + shift + ;; + --yes) + auto_yes=true + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown argument: $1" >&2 + usage >&2 + exit 1 + ;; + esac +done + +if ! [[ "$max_days" =~ ^[0-9]+$ ]]; then + echo "--days must be a non-negative integer" >&2 + exit 1 +fi + +cd "$repo_dir" + +[[ -f "TIMELINE.md" ]] || { echo "Missing TIMELINE.md" >&2; exit 1; } +[[ -f "CONTEXT.md" ]] || { echo "Missing CONTEXT.md" >&2; exit 1; } + +cutoff_epoch=$(( $(date +%s) - max_days * 86400 )) + +# Parse TIMELINE.md entries and classify as old or recent +old_facts=() +old_entry_headings=() +entries_section_started=0 +in_entry=0 +current_heading="" +current_timestamp="" +in_applied=0 +entry_is_old=0 + +classify_timestamp() { + local ts="$1" + # Extract date part: YYYY-MM-DDTHH:MM:SS... + local date_part="${ts%%T*}" + if [[ ! "$date_part" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then + # Cannot parse — treat as recent (keep it) + return 1 + fi + local entry_epoch + if date --version >/dev/null 2>&1; then + # GNU date + entry_epoch=$(date -d "$date_part" +%s 2>/dev/null) || return 1 + else + # BSD/macOS date + entry_epoch=$(date -j -f "%Y-%m-%d" "$date_part" +%s 2>/dev/null) || return 1 + fi + (( entry_epoch < cutoff_epoch )) +} + +flush_entry() { + if [[ "$in_entry" -eq 1 && "$entry_is_old" -eq 1 ]]; then + old_entry_headings+=("$current_heading") + fi + in_entry=0 + in_applied=0 + entry_is_old=0 + current_heading="" + current_timestamp="" +} + +while IFS= read -r line; do + # Detect ## Entries section + if [[ "$line" == "## Entries" ]]; then + entries_section_started=1 + continue + fi + + [[ "$entries_section_started" -eq 1 ]] || continue + + # Entry heading: ### | + if [[ "$line" =~ ^###\ ]]; then + flush_entry + in_entry=1 + current_heading="$line" + # Extract timestamp from heading + local_ts="${line#\#\#\# }" + local_ts="${local_ts%% |*}" + local_ts="${local_ts%% *}" + current_timestamp="$local_ts" + if classify_timestamp "$current_timestamp"; then + entry_is_old=1 + fi + continue + fi + + [[ "$in_entry" -eq 1 ]] || continue + + # Track Applied Changes section + if [[ "$line" =~ ^-\ Applied\ Changes: ]]; then + in_applied=1 + continue + fi + + # End of Applied Changes when we hit another top-level field + if [[ "$line" =~ ^-\ && ! "$line" =~ ^\ \ ]]; then + in_applied=0 + fi + + # Collect applied change bullets from old entries + if [[ "$in_applied" -eq 1 && "$entry_is_old" -eq 1 && "$line" =~ ^\ \ -\ ]]; then + local_fact="${line# - }" + if [[ -n "$local_fact" ]]; then + old_facts+=("$local_fact") + fi + fi +done < TIMELINE.md + +flush_entry + +if [[ ${#old_entry_headings[@]} -eq 0 ]]; then + echo "No entries older than ${max_days} days to compact." + exit 0 +fi + +echo "Found ${#old_entry_headings[@]} entry/entries older than ${max_days} days." + +if [[ ${#old_facts[@]} -gt 0 ]]; then + echo "Facts to add to Stable Facts:" + for fact in "${old_facts[@]}"; do + echo " - $fact" + done +fi + +echo "" +echo "Entries to remove:" +for heading in "${old_entry_headings[@]}"; do + echo " $heading" +done + +if $dry_run; then + echo "" + echo "[dry-run] No files modified." + exit 0 +fi + +if ! $auto_yes; then + printf '\nProceed? [y/N] ' + read -r answer + if [[ "$answer" != "y" && "$answer" != "Y" ]]; then + echo "Aborted." + exit 0 + fi +fi + +# Step 1: Append facts to CONTEXT.md under ## Stable Facts +if [[ ${#old_facts[@]} -gt 0 ]]; then + tmpfile="$(mktemp)" + found_stable=0 + while IFS= read -r line; do + echo "$line" >> "$tmpfile" + if [[ "$found_stable" -eq 0 && "$line" == "## Stable Facts" ]]; then + found_stable=1 + # Read next line (expect blank or first bullet) + if IFS= read -r next_line; then + echo "$next_line" >> "$tmpfile" + fi + # Append new facts + for fact in "${old_facts[@]}"; do + echo "- $fact" >> "$tmpfile" + done + fi + done < CONTEXT.md + mv "$tmpfile" CONTEXT.md +fi + +# Step 2: Remove old entries from TIMELINE.md +tmpfile="$(mktemp)" +entries_section_started=0 +in_old_entry=0 +skip_until_next=0 +current_heading="" + +while IFS= read -r line; do + if [[ "$line" == "## Entries" ]]; then + entries_section_started=1 + echo "$line" >> "$tmpfile" + continue + fi + + if [[ "$entries_section_started" -eq 1 && "$line" =~ ^###\ ]]; then + # Check if this heading is in old_entry_headings + skip_until_next=0 + for old_heading in "${old_entry_headings[@]}"; do + if [[ "$line" == "$old_heading" ]]; then + skip_until_next=1 + break + fi + done + if [[ "$skip_until_next" -eq 1 ]]; then + continue + fi + fi + + if [[ "$skip_until_next" -eq 1 ]]; then + # Skip lines until next ### heading or EOF + if [[ "$line" =~ ^###\ ]]; then + # New entry — check if it's also old + skip_until_next=0 + for old_heading in "${old_entry_headings[@]}"; do + if [[ "$line" == "$old_heading" ]]; then + skip_until_next=1 + break + fi + done + if [[ "$skip_until_next" -eq 1 ]]; then + continue + fi + else + continue + fi + fi + + echo "$line" >> "$tmpfile" +done < TIMELINE.md + +mv "$tmpfile" TIMELINE.md + +echo "Compacted ${#old_entry_headings[@]} entry/entries. ${#old_facts[@]} fact(s) added to Stable Facts." diff --git a/tests/compact_timeline.bats b/tests/compact_timeline.bats new file mode 100644 index 0000000..ab68f3d --- /dev/null +++ b/tests/compact_timeline.bats @@ -0,0 +1,165 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { setup_tmpdir; } +teardown() { teardown_tmpdir; } + +# Helper: create a TIMELINE.md with entries at specific dates +create_timeline_with_entries() { + local old_date="$1" + local recent_date="$2" + cat > "$TEST_TMPDIR/TIMELINE.md" < "$TEST_TMPDIR/TIMELINE.md" <<'EOF' +# Timeline + +## Entry Template + +## Entries +EOF + + run "$SCRIPTS_DIR/compact_timeline.sh" --repo "$TEST_TMPDIR" + + [ "$status" -ne 0 ] + [[ "$output" == *"Missing CONTEXT.md"* ]] +} + +@test "compact_timeline rejects invalid --days value" { + run "$SCRIPTS_DIR/compact_timeline.sh" --days abc + + [ "$status" -ne 0 ] + [[ "$output" == *"non-negative integer"* ]] +} + +@test "compact_timeline preserves TIMELINE.md structure after compaction" { + bootstrap_context + local old_date recent_date + old_date="$(date -v-60d +%Y-%m-%d 2>/dev/null || date -d '60 days ago' +%Y-%m-%d)" + recent_date="$(date +%Y-%m-%d)" + create_timeline_with_entries "$old_date" "$recent_date" + + run "$SCRIPTS_DIR/compact_timeline.sh" --repo "$TEST_TMPDIR" --days 30 --yes + [ "$status" -eq 0 ] + + # Validate the resulting TIMELINE.md still passes validation + run "$SCRIPTS_DIR/validate_context.sh" --repo "$TEST_TMPDIR" + [ "$status" -eq 0 ] + [[ "$output" == *"Shared context structure is valid"* ]] +} From 4df77fedf5f12dc9ef225f1b451f9b847a804304 Mon Sep 17 00:00:00 2001 From: hwanggg Date: Thu, 12 Mar 2026 18:59:39 +0900 Subject: [PATCH 05/23] Add check_divergence.sh for context branch divergence detection Reports ahead/behind commit counts for context/* branches relative to the base branch. Flags branches exceeding --threshold (default 10) and stale branches with no commits in --stale-days (default 30). Non-zero exit code enables CI integration. Includes 10 BATS tests. Co-Authored-By: Claude Opus 4.6 --- scripts/check_divergence.sh | 134 ++++++++++++++++++++++++++++++++++++ tests/check_divergence.bats | 129 ++++++++++++++++++++++++++++++++++ 2 files changed, 263 insertions(+) create mode 100755 scripts/check_divergence.sh create mode 100644 tests/check_divergence.bats diff --git a/scripts/check_divergence.sh b/scripts/check_divergence.sh new file mode 100755 index 0000000..dbbb0a7 --- /dev/null +++ b/scripts/check_divergence.sh @@ -0,0 +1,134 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: check_divergence.sh [--repo DIR] [--base-branch NAME] [--threshold N] [--stale-days N] + +Report divergence of context/* branches from the base branch. + +Options: + --repo DIR Repository path (default: .) + --base-branch NAME Base branch to compare against (default: main) + --threshold N Warn if a branch is N+ commits ahead or behind (default: 10) + --stale-days N Report branches with no commits in N+ days (default: 30) + -h, --help Show this help message + +Exit codes: + 0 No warnings + 1 One or more branches exceed the divergence threshold or are stale +EOF +} + +repo_dir="." +base_branch="main" +threshold=10 +stale_days=30 + +while [[ $# -gt 0 ]]; do + case "$1" in + --repo) + [[ $# -ge 2 ]] || { echo "Missing value for --repo" >&2; exit 1; } + repo_dir="$2" + shift 2 + ;; + --base-branch) + [[ $# -ge 2 ]] || { echo "Missing value for --base-branch" >&2; exit 1; } + base_branch="$2" + shift 2 + ;; + --threshold) + [[ $# -ge 2 ]] || { echo "Missing value for --threshold" >&2; exit 1; } + threshold="$2" + shift 2 + ;; + --stale-days) + [[ $# -ge 2 ]] || { echo "Missing value for --stale-days" >&2; exit 1; } + stale_days="$2" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown argument: $1" >&2 + usage >&2 + exit 1 + ;; + esac +done + +if ! [[ "$threshold" =~ ^[0-9]+$ ]]; then + echo "--threshold must be a non-negative integer" >&2 + exit 1 +fi + +if ! [[ "$stale_days" =~ ^[0-9]+$ ]]; then + echo "--stale-days must be a non-negative integer" >&2 + exit 1 +fi + +cd "$repo_dir" + +git rev-parse --is-inside-work-tree >/dev/null 2>&1 || { + echo "Not a Git repository: $repo_dir" >&2 + exit 1 +} + +git rev-parse --verify --quiet "refs/heads/${base_branch}" >/dev/null 2>&1 || { + echo "Base branch '${base_branch}' does not exist" >&2 + exit 1 +} + +cutoff_epoch=$(( $(date +%s) - stale_days * 86400 )) +has_warnings=0 +branch_count=0 + +while IFS= read -r branch; do + [[ -n "$branch" ]] || continue + branch="${branch#"${branch%%[![:space:]]*}"}" + branch_count=$((branch_count + 1)) + + merge_base="$(git merge-base "$base_branch" "$branch" 2>/dev/null)" || continue + ahead="$(git rev-list --count "${merge_base}..${branch}" 2>/dev/null)" || ahead=0 + behind="$(git rev-list --count "${merge_base}..${base_branch}" 2>/dev/null)" || behind=0 + + commit_epoch="$(git log -1 --format='%ct' "$branch" 2>/dev/null)" || commit_epoch=0 + + diverged=0 + stale=0 + + if (( ahead >= threshold || behind >= threshold )); then + diverged=1 + has_warnings=1 + fi + + if (( commit_epoch > 0 && commit_epoch < cutoff_epoch )); then + stale=1 + has_warnings=1 + fi + + if (( diverged || stale )); then + printf '%s: ahead=%d behind=%d' "$branch" "$ahead" "$behind" + if (( stale )); then + printf ' [stale]' + fi + printf '\n' + else + printf '%s: ahead=%d behind=%d (ok)\n' "$branch" "$ahead" "$behind" + fi +done < <(git branch --list 'context/*' --format='%(refname:short)') + +if (( branch_count == 0 )); then + echo "No context/* branches found." + exit 0 +fi + +if (( has_warnings )); then + echo "WARNING: One or more context branches are diverged or stale." + exit 1 +else + echo "All context branches are within acceptable divergence." + exit 0 +fi diff --git a/tests/check_divergence.bats b/tests/check_divergence.bats new file mode 100644 index 0000000..0ef4ab1 --- /dev/null +++ b/tests/check_divergence.bats @@ -0,0 +1,129 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { setup_git_repo; } +teardown() { teardown_tmpdir; } + +# Helper: create a context branch with N commits ahead of main +create_context_branch() { + local branch_name="$1" + local num_commits="${2:-1}" + + git -C "$TEST_TMPDIR" checkout -b "$branch_name" >/dev/null 2>&1 + for i in $(seq 1 "$num_commits"); do + echo "change $i" > "$TEST_TMPDIR/file-${i}.txt" + git -C "$TEST_TMPDIR" add "file-${i}.txt" + git -C "$TEST_TMPDIR" commit -m "commit $i on $branch_name" >/dev/null 2>&1 + done + git -C "$TEST_TMPDIR" checkout main >/dev/null 2>&1 +} + +# Helper: create a context branch with an old commit date +create_stale_context_branch() { + local branch_name="$1" + local days_ago="$2" + local old_date + old_date="$(date -v-${days_ago}d +%Y-%m-%dT12:00:00 2>/dev/null || date -d "${days_ago} days ago" +%Y-%m-%dT12:00:00)" + + git -C "$TEST_TMPDIR" checkout -b "$branch_name" >/dev/null 2>&1 + echo "stale content" > "$TEST_TMPDIR/stale.txt" + git -C "$TEST_TMPDIR" add stale.txt + GIT_AUTHOR_DATE="$old_date" GIT_COMMITTER_DATE="$old_date" \ + git -C "$TEST_TMPDIR" commit -m "stale commit" >/dev/null 2>&1 + git -C "$TEST_TMPDIR" checkout main >/dev/null 2>&1 +} + +@test "check_divergence --help prints usage" { + run "$SCRIPTS_DIR/check_divergence.sh" --help + + [ "$status" -eq 0 ] + [[ "$output" == *"Usage:"* ]] +} + +@test "check_divergence reports no branches when none exist" { + run "$SCRIPTS_DIR/check_divergence.sh" --repo "$TEST_TMPDIR" + + [ "$status" -eq 0 ] + [[ "$output" == *"No context/* branches found"* ]] +} + +@test "check_divergence reports ok for branch within threshold" { + create_context_branch "context/bot/2026-03-12-test" 3 + + run "$SCRIPTS_DIR/check_divergence.sh" --repo "$TEST_TMPDIR" --threshold 10 + + [ "$status" -eq 0 ] + [[ "$output" == *"context/bot/2026-03-12-test: ahead=3 behind=0 (ok)"* ]] + [[ "$output" == *"All context branches are within acceptable divergence"* ]] +} + +@test "check_divergence warns when branch exceeds threshold" { + create_context_branch "context/bot/2026-03-12-diverged" 12 + + run "$SCRIPTS_DIR/check_divergence.sh" --repo "$TEST_TMPDIR" --threshold 10 + + [ "$status" -eq 1 ] + [[ "$output" == *"context/bot/2026-03-12-diverged: ahead=12 behind=0"* ]] + [[ "$output" == *"WARNING"* ]] +} + +@test "check_divergence detects stale branches" { + create_stale_context_branch "context/bot/2026-01-01-old" 60 + + run "$SCRIPTS_DIR/check_divergence.sh" --repo "$TEST_TMPDIR" --stale-days 30 + + [ "$status" -eq 1 ] + [[ "$output" == *"[stale]"* ]] + [[ "$output" == *"WARNING"* ]] +} + +@test "check_divergence exits 0 when stale branch is within stale-days" { + create_stale_context_branch "context/bot/2026-03-10-recent" 5 + + run "$SCRIPTS_DIR/check_divergence.sh" --repo "$TEST_TMPDIR" --stale-days 30 + + [ "$status" -eq 0 ] + [[ "$output" == *"(ok)"* ]] +} + +@test "check_divergence reports behind count when main has advanced" { + create_context_branch "context/bot/2026-03-12-behind" 1 + + # Add commits to main after branching + for i in $(seq 1 5); do + echo "main change $i" > "$TEST_TMPDIR/main-${i}.txt" + git -C "$TEST_TMPDIR" add "main-${i}.txt" + git -C "$TEST_TMPDIR" commit -m "main commit $i" >/dev/null 2>&1 + done + + run "$SCRIPTS_DIR/check_divergence.sh" --repo "$TEST_TMPDIR" --threshold 10 + + [ "$status" -eq 0 ] + [[ "$output" == *"ahead=1 behind=5 (ok)"* ]] +} + +@test "check_divergence rejects invalid --threshold" { + run "$SCRIPTS_DIR/check_divergence.sh" --threshold abc + + [ "$status" -ne 0 ] + [[ "$output" == *"non-negative integer"* ]] +} + +@test "check_divergence rejects invalid --stale-days" { + run "$SCRIPTS_DIR/check_divergence.sh" --stale-days xyz + + [ "$status" -ne 0 ] + [[ "$output" == *"non-negative integer"* ]] +} + +@test "check_divergence fails when not a git repo" { + local non_repo + non_repo="$(mktemp -d)" + + run "$SCRIPTS_DIR/check_divergence.sh" --repo "$non_repo" + + [ "$status" -ne 0 ] + [[ "$output" == *"Not a Git repository"* ]] + rm -rf "$non_repo" +} From 7b04e77fca7de95e72d8721214a8b84426f81b1a Mon Sep 17 00:00:00 2001 From: hwanggg Date: Thu, 12 Mar 2026 19:05:36 +0900 Subject: [PATCH 06/23] Add Claude and Codex agent configs, enhance OpenAI config - Add agents/claude.yaml with default_actor, branch_prefix, workflow_hints - Add agents/codex.yaml with matching structure for Codex - Extend agents/openai.yaml with parameters, workflow_hints, skill_paths Co-Authored-By: Claude Opus 4.6 --- agents/claude.yaml | 25 +++++++++++++++++++++++++ agents/codex.yaml | 25 +++++++++++++++++++++++++ agents/openai.yaml | 18 ++++++++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 agents/claude.yaml create mode 100644 agents/codex.yaml diff --git a/agents/claude.yaml b/agents/claude.yaml new file mode 100644 index 0000000..d5aa5df --- /dev/null +++ b/agents/claude.yaml @@ -0,0 +1,25 @@ +interface: + display_name: "Shared Context Git" + short_description: "Run a Git-backed shared memory workflow" + default_prompt: "Use $shared-context-git-skill to bootstrap or operate a Git-backed shared context repository for a multi-agent project." + +policy: + allow_implicit_invocation: true + +parameters: + default_actor: "claude" + branch_prefix: "context/claude" + auto_validate: true + +workflow_hints: + - "Run scripts/sync_context.sh before reading or editing any shared document." + - "Always read CONTEXT.md and TIMELINE.md before making changes." + - "Use scripts/prepare_branch.sh --actor claude --slug to create update branches." + - "Run scripts/validate_context.sh after edits and before committing." + - "Commit only when changes are meaningful — avoid noisy timeline entries." + +skill_paths: + skill_definition: "SKILL.md" + scripts: "scripts/" + templates: "assets/templates/" + references: "references/" diff --git a/agents/codex.yaml b/agents/codex.yaml new file mode 100644 index 0000000..018f76b --- /dev/null +++ b/agents/codex.yaml @@ -0,0 +1,25 @@ +interface: + display_name: "Shared Context Git" + short_description: "Run a Git-backed shared memory workflow" + default_prompt: "Use $shared-context-git-skill to bootstrap or operate a Git-backed shared context repository for a multi-agent project." + +policy: + allow_implicit_invocation: true + +parameters: + default_actor: "codex" + branch_prefix: "context/codex" + auto_validate: true + +workflow_hints: + - "Run scripts/sync_context.sh before reading or editing any shared document." + - "Always read CONTEXT.md and TIMELINE.md before making changes." + - "Use scripts/prepare_branch.sh --actor codex --slug to create update branches." + - "Run scripts/validate_context.sh after edits and before committing." + - "Commit only when changes are meaningful — avoid noisy timeline entries." + +skill_paths: + skill_definition: "SKILL.md" + scripts: "scripts/" + templates: "assets/templates/" + references: "references/" diff --git a/agents/openai.yaml b/agents/openai.yaml index c1e5ccf..144f15f 100644 --- a/agents/openai.yaml +++ b/agents/openai.yaml @@ -5,3 +5,21 @@ interface: policy: allow_implicit_invocation: true + +parameters: + default_actor: "openai" + branch_prefix: "context/openai" + auto_validate: true + +workflow_hints: + - "Run scripts/sync_context.sh before reading or editing any shared document." + - "Always read CONTEXT.md and TIMELINE.md before making changes." + - "Use scripts/prepare_branch.sh --actor openai --slug to create update branches." + - "Run scripts/validate_context.sh after edits and before committing." + - "Commit only when changes are meaningful - avoid noisy timeline entries." + +skill_paths: + skill_definition: "SKILL.md" + scripts: "scripts/" + templates: "assets/templates/" + references: "references/" From ba89f3755fe6acff82fd680ab7f0dbb09726760a Mon Sep 17 00:00:00 2001 From: hwanggg Date: Thu, 12 Mar 2026 19:05:41 +0900 Subject: [PATCH 07/23] Add cleanup_branches.sh and BATS tests for branch cleanup - cleanup_branches.sh: remove merged context/* branches older than N days - Supports --dry-run, --remote, --days, --base-branch options - BATS test suite covers merge/skip/dry-run/remote/validation scenarios Co-Authored-By: Claude Opus 4.6 --- scripts/cleanup_branches.sh | 150 ++++++++++++++++++++++++++++++++++++ tests/cleanup_branches.bats | 114 +++++++++++++++++++++++++++ 2 files changed, 264 insertions(+) create mode 100755 scripts/cleanup_branches.sh create mode 100644 tests/cleanup_branches.bats diff --git a/scripts/cleanup_branches.sh b/scripts/cleanup_branches.sh new file mode 100755 index 0000000..eeda8b0 --- /dev/null +++ b/scripts/cleanup_branches.sh @@ -0,0 +1,150 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: cleanup_branches.sh [--repo DIR] [--base-branch NAME] [--days N] [--dry-run] [--remote] + +Delete merged context/* branches from the local repository. + +Options: + --repo DIR Repository path (default: .) + --base-branch NAME Base branch to check merge status against (default: main) + --days N Only target branches older than N days (default: 30) + --dry-run Print branches that would be deleted without deleting + --remote Also delete matching remote branches (origin) + -h, --help Show this help message +EOF +} + +repo_dir="." +base_branch="main" +max_days=30 +dry_run=false +delete_remote=false + +while [[ $# -gt 0 ]]; do + case "$1" in + --repo) + [[ $# -ge 2 ]] || { echo "Missing value for --repo" >&2; exit 1; } + repo_dir="$2" + shift 2 + ;; + --base-branch) + [[ $# -ge 2 ]] || { echo "Missing value for --base-branch" >&2; exit 1; } + base_branch="$2" + shift 2 + ;; + --days) + [[ $# -ge 2 ]] || { echo "Missing value for --days" >&2; exit 1; } + max_days="$2" + shift 2 + ;; + --dry-run) + dry_run=true + shift + ;; + --remote) + delete_remote=true + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown argument: $1" >&2 + usage >&2 + exit 1 + ;; + esac +done + +if ! [[ "$max_days" =~ ^[0-9]+$ ]]; then + echo "--days must be a non-negative integer" >&2 + exit 1 +fi + +cd "$repo_dir" + +git rev-parse --is-inside-work-tree >/dev/null 2>&1 || { + echo "Not a Git repository: $repo_dir" >&2 + exit 1 +} + +git rev-parse --verify --quiet "refs/heads/${base_branch}" >/dev/null 2>&1 || { + echo "Base branch '${base_branch}' does not exist" >&2 + exit 1 +} + +current_branch="$(git rev-parse --abbrev-ref HEAD)" +cutoff_epoch=$(( $(date +%s) - max_days * 86400 )) +deleted_count=0 + +# Get local context/* branches that are fully merged into base_branch +while IFS= read -r branch; do + [[ -n "$branch" ]] || continue + branch="${branch#"${branch%%[![:space:]]*}"}" + + # Never delete the current branch or the base branch + [[ "$branch" != "$current_branch" ]] || continue + [[ "$branch" != "$base_branch" ]] || continue + + # Check branch age via last commit date + commit_epoch="$(git log -1 --format='%ct' "$branch" 2>/dev/null)" || continue + if (( commit_epoch > cutoff_epoch )); then + continue + fi + + if $dry_run; then + echo "[dry-run] Would delete: $branch" + else + git branch -d "$branch" >/dev/null 2>&1 + echo "Deleted local branch: $branch" + fi + deleted_count=$((deleted_count + 1)) +done < <(git branch --merged "$base_branch" --list 'context/*' --format='%(refname:short)') + +# Handle remote branches if --remote is set +if $delete_remote; then + remote="origin" + while IFS= read -r ref; do + [[ -n "$ref" ]] || continue + ref="${ref#"${ref%%[![:space:]]*}"}" + # ref looks like "origin/context/actor/date-slug" + branch="${ref#${remote}/}" + + # Never delete remote tracking for current or base branch + [[ "$branch" != "$current_branch" ]] || continue + [[ "$branch" != "$base_branch" ]] || continue + + # Check age + commit_epoch="$(git log -1 --format='%ct' "$ref" 2>/dev/null)" || continue + if (( commit_epoch > cutoff_epoch )); then + continue + fi + + # Check if merged into base + if ! git merge-base --is-ancestor "$ref" "$base_branch" 2>/dev/null; then + continue + fi + + if $dry_run; then + echo "[dry-run] Would delete remote: ${remote}/${branch}" + else + git push "$remote" --delete "$branch" >/dev/null 2>&1 + echo "Deleted remote branch: ${remote}/${branch}" + fi + deleted_count=$((deleted_count + 1)) + done < <(git branch -r --list "${remote}/context/*" --format='%(refname:short)') +fi + +if (( deleted_count == 0 )); then + echo "No context branches to clean up." +else + if $dry_run; then + echo "Total: ${deleted_count} branch(es) would be deleted." + else + echo "Total: ${deleted_count} branch(es) deleted." + fi +fi diff --git a/tests/cleanup_branches.bats b/tests/cleanup_branches.bats new file mode 100644 index 0000000..f9a4551 --- /dev/null +++ b/tests/cleanup_branches.bats @@ -0,0 +1,114 @@ +#!/usr/bin/env bats + +load test_helper + +OLD_DATE="2020-01-15T12:00:00Z" + +setup() { setup_tracking_repo; } +teardown() { teardown_tmpdir; } + +create_context_branch_commit() { + local branch_name="$1" + local file_name="$2" + local file_content="$3" + local commit_date="${4:-}" + + git -C "$TRACKING_REPO_DIR" checkout -b "$branch_name" >/dev/null 2>&1 + printf '%s\n' "$file_content" > "$TRACKING_REPO_DIR/$file_name" + git -C "$TRACKING_REPO_DIR" add "$file_name" + + if [[ -n "$commit_date" ]]; then + GIT_AUTHOR_DATE="$commit_date" GIT_COMMITTER_DATE="$commit_date" \ + git -C "$TRACKING_REPO_DIR" commit -m "add $branch_name" >/dev/null 2>&1 + else + git -C "$TRACKING_REPO_DIR" commit -m "add $branch_name" >/dev/null 2>&1 + fi +} + +merge_context_branch_into_main() { + local branch_name="$1" + + git -C "$TRACKING_REPO_DIR" checkout main >/dev/null 2>&1 + git -C "$TRACKING_REPO_DIR" merge --ff-only "$branch_name" >/dev/null 2>&1 +} + +@test "cleanup_branches deletes merged local context branches older than threshold" { + create_context_branch_commit "context/alice/2020-01-15-old-local" "old-local.txt" "old local" "$OLD_DATE" + merge_context_branch_into_main "context/alice/2020-01-15-old-local" + + run "$SCRIPTS_DIR/cleanup_branches.sh" --repo "$TRACKING_REPO_DIR" + + [ "$status" -eq 0 ] + [[ "$output" == *"Deleted local branch: context/alice/2020-01-15-old-local"* ]] + [[ "$output" == *"Total: 1 branch(es) deleted."* ]] + run git -C "$TRACKING_REPO_DIR" rev-parse --verify "context/alice/2020-01-15-old-local" + [ "$status" -ne 0 ] +} + +@test "cleanup_branches dry-run reports deletions without removing branches" { + create_context_branch_commit "context/alice/2020-01-15-dry-run" "dry-run.txt" "dry run" "$OLD_DATE" + merge_context_branch_into_main "context/alice/2020-01-15-dry-run" + + run "$SCRIPTS_DIR/cleanup_branches.sh" --repo "$TRACKING_REPO_DIR" --dry-run + + [ "$status" -eq 0 ] + [[ "$output" == *"[dry-run] Would delete: context/alice/2020-01-15-dry-run"* ]] + [[ "$output" == *"Total: 1 branch(es) would be deleted."* ]] + run git -C "$TRACKING_REPO_DIR" rev-parse --verify "context/alice/2020-01-15-dry-run" + [ "$status" -eq 0 ] +} + +@test "cleanup_branches skips recent merged context branches" { + create_context_branch_commit "context/alice/2026-03-12-recent" "recent.txt" "recent branch" + merge_context_branch_into_main "context/alice/2026-03-12-recent" + + run "$SCRIPTS_DIR/cleanup_branches.sh" --repo "$TRACKING_REPO_DIR" --days 30 + + [ "$status" -eq 0 ] + [[ "$output" == *"No context branches to clean up."* ]] + run git -C "$TRACKING_REPO_DIR" rev-parse --verify "context/alice/2026-03-12-recent" + [ "$status" -eq 0 ] +} + +@test "cleanup_branches skips unmerged context branches" { + create_context_branch_commit "context/alice/2020-01-15-unmerged" "unmerged.txt" "unmerged branch" "$OLD_DATE" + git -C "$TRACKING_REPO_DIR" checkout main >/dev/null 2>&1 + + run "$SCRIPTS_DIR/cleanup_branches.sh" --repo "$TRACKING_REPO_DIR" --days 0 + + [ "$status" -eq 0 ] + [[ "$output" == *"No context branches to clean up."* ]] + run git -C "$TRACKING_REPO_DIR" rev-parse --verify "context/alice/2020-01-15-unmerged" + [ "$status" -eq 0 ] +} + +@test "cleanup_branches deletes merged remote context branches when requested" { + create_context_branch_commit "context/alice/2020-01-15-old-remote" "old-remote.txt" "old remote" "$OLD_DATE" + git -C "$TRACKING_REPO_DIR" push -u origin "context/alice/2020-01-15-old-remote" >/dev/null 2>&1 + merge_context_branch_into_main "context/alice/2020-01-15-old-remote" + git -C "$TRACKING_REPO_DIR" push origin main >/dev/null 2>&1 + git -C "$TRACKING_REPO_DIR" branch -D "context/alice/2020-01-15-old-remote" >/dev/null 2>&1 + + run "$SCRIPTS_DIR/cleanup_branches.sh" --repo "$TRACKING_REPO_DIR" --remote + + [ "$status" -eq 0 ] + [[ "$output" == *"Deleted remote branch: origin/context/alice/2020-01-15-old-remote"* ]] + [[ "$output" == *"Total: 1 branch(es) deleted."* ]] + run git -C "$TRACKING_REPO_DIR" ls-remote --heads origin "context/alice/2020-01-15-old-remote" + [ "$status" -eq 0 ] + [ -z "$output" ] +} + +@test "cleanup_branches rejects invalid --days values" { + run "$SCRIPTS_DIR/cleanup_branches.sh" --repo "$TRACKING_REPO_DIR" --days nope + + [ "$status" -ne 0 ] + [[ "$output" == *"--days must be a non-negative integer"* ]] +} + +@test "cleanup_branches --help prints usage" { + run "$SCRIPTS_DIR/cleanup_branches.sh" --help + + [ "$status" -eq 0 ] + [[ "$output" == *"Usage:"* ]] +} From 9182d5a0dfd525eabd999df5002b003a0db720d7 Mon Sep 17 00:00:00 2001 From: hwanggg Date: Thu, 12 Mar 2026 19:05:45 +0900 Subject: [PATCH 08/23] Update docs with new scripts, tests, and agent configs - README: add cleanup_branches.sh, test section, agent config table - SKILL.md: add cleanup_branches.sh to script guide Co-Authored-By: Claude Opus 4.6 --- README.md | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- SKILL.md | 1 + 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4b9a46d..23d0fe6 100644 --- a/README.md +++ b/README.md @@ -15,13 +15,21 @@ ``` ├── SKILL.md # 스킬 정의 및 핵심 규칙 ├── agents/ -│ └── openai.yaml # OpenAI 에이전트 연동 설정 +│ ├── openai.yaml # OpenAI 에이전트 연동 설정 +│ ├── claude.yaml # Claude Code 에이전트 연동 설정 +│ └── codex.yaml # OpenAI Codex 에이전트 연동 설정 ├── scripts/ # 자동화 Bash 스크립트 │ ├── bootstrap_repo.sh # 템플릿으로 초기 문서 생성 +│ ├── cleanup_branches.sh # 오래된 병합 완료 컨텍스트 브랜치 정리 │ ├── sync_context.sh # 원격 변경 fetch 및 fast-forward │ ├── prepare_branch.sh # 컨텍스트 브랜치 생성 │ ├── validate_context.sh # 문서 구조 검증 │ └── summarize_context.sh # 상태 요약 및 압축 힌트 출력 +├── tests/ # BATS 기반 회귀 테스트 +│ ├── *.bats # 스크립트별 동작 검증 +│ ├── test_helper.bash # 테스트 공통 헬퍼 +│ ├── run_tests.sh # 전체 테스트 실행기 +│ └── lib/bats-core/ # Git submodule로 관리되는 BATS 실행기 ├── assets/ │ └── templates/ # 문서 시작 템플릿 │ ├── CONTEXT.md # 공유 상태 문서 @@ -93,11 +101,22 @@ scripts/summarize_context.sh | 스크립트 | 설명 | |---------|------| | `bootstrap_repo.sh` | 템플릿에서 초기 문서 세트를 생성합니다 | +| `cleanup_branches.sh` | 병합된 오래된 `context/*` 브랜치를 로컬 또는 원격에서 정리합니다 | | `sync_context.sh` | 원격 변경을 fetch하고 기본 브랜치를 안전하게 fast-forward합니다 | | `prepare_branch.sh` | `context//-` 형식의 브랜치를 생성하거나 전환합니다 | | `validate_context.sh` | 필수 파일, 제목, 타임라인 항목 형식을 검사합니다 | | `summarize_context.sh` | 간결한 상태 요약과 압축 힌트를 출력합니다 | +## 테스트 + +```bash +git submodule update --init --recursive +./tests/run_tests.sh +``` + +- 테스트는 BATS를 사용해 `scripts/` 아래 워크플로 스크립트의 정상/오류 경로를 검증합니다. +- `tests/run_tests.sh`는 포함된 `tests/lib/bats-core` submodule을 사용해 전체 `.bats` 스위트를 실행합니다. + ## 협업 모드 - **로컬 초안**: 동기화, 읽기, 로컬 편집, 검증 후 커밋 없이 중단 @@ -112,6 +131,42 @@ scripts/summarize_context.sh - [conflict-policy.md](references/conflict-policy.md) — 충돌 처리 정책 - [handoff-guidelines.md](references/handoff-guidelines.md) — 인수인계 가이드라인 +## 에이전트별 설정 + +각 에이전트 프레임워크에 맞는 설정 파일이 `agents/` 디렉터리에 포함되어 있습니다. + +| 설정 파일 | 에이전트 | 기본 액터명 | 브랜치 접두사 | +|-----------|---------|------------|--------------| +| `agents/openai.yaml` | OpenAI Agents | `openai` | `context/openai` | +| `agents/claude.yaml` | Claude Code | `claude` | `context/claude` | +| `agents/codex.yaml` | OpenAI Codex | `codex` | `context/codex` | + +각 설정에는 스킬 참조 경로(`skill_paths`), 기본 파라미터(`parameters`), 워크플로 힌트(`workflow_hints`)가 포함되어 있습니다. + +### OpenAI Agents 사용법 + +```bash +# 스킬 설정 파일 경로: agents/openai.yaml +# 기본 브랜치 생성 예시: +scripts/prepare_branch.sh --actor openai --slug my-topic +``` + +### Claude Code 사용법 + +```bash +# 스킬 설정 파일 경로: agents/claude.yaml +# 기본 브랜치 생성 예시: +scripts/prepare_branch.sh --actor claude --slug my-topic +``` + +### Codex 사용법 + +```bash +# 스킬 설정 파일 경로: agents/codex.yaml +# 기본 브랜치 생성 예시: +scripts/prepare_branch.sh --actor codex --slug my-topic +``` + ## 요구 사항 - Git CLI diff --git a/SKILL.md b/SKILL.md index a7ed491..fe612da 100644 --- a/SKILL.md +++ b/SKILL.md @@ -66,6 +66,7 @@ Use the templates in `assets/templates/` and the detailed rules in the reference ## Script Guide - `scripts/bootstrap_repo.sh`: create the initial document set from templates +- `scripts/cleanup_branches.sh`: remove merged, older `context/*` branches locally and optionally on `origin` - `scripts/sync_context.sh`: fetch remote changes and fast-forward the base branch when safe - `scripts/prepare_branch.sh`: create or switch to a branch named `context//-` - `scripts/validate_context.sh`: check required files, headings, and timeline entry shape From e2811bc004a8e6a876c09590f04a2ec9e8b0835a Mon Sep 17 00:00:00 2001 From: hwanggg Date: Thu, 12 Mar 2026 19:56:34 +0900 Subject: [PATCH 09/23] Fix compact_timeline.sh date parsing on both GNU and BSD/macOS Append T00:00:00 suffix to date strings before parsing to avoid using the current wall-clock time on macOS BSD date. Co-Authored-By: Claude Sonnet 4.6 --- scripts/compact_timeline.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/compact_timeline.sh b/scripts/compact_timeline.sh index 77f5f87..3349189 100755 --- a/scripts/compact_timeline.sh +++ b/scripts/compact_timeline.sh @@ -87,10 +87,10 @@ classify_timestamp() { local entry_epoch if date --version >/dev/null 2>&1; then # GNU date - entry_epoch=$(date -d "$date_part" +%s 2>/dev/null) || return 1 + entry_epoch=$(date -d "${date_part}T00:00:00" +%s 2>/dev/null) || return 1 else - # BSD/macOS date - entry_epoch=$(date -j -f "%Y-%m-%d" "$date_part" +%s 2>/dev/null) || return 1 + # BSD/macOS date — must specify time to avoid using current wall-clock time + entry_epoch=$(date -j -f "%Y-%m-%dT%H:%M:%S" "${date_part}T00:00:00" +%s 2>/dev/null) || return 1 fi (( entry_epoch < cutoff_epoch )) } From e06ac387b610dfbc038c0d11aeb87d5aec5c2d27 Mon Sep 17 00:00:00 2001 From: hwanggg Date: Thu, 12 Mar 2026 19:56:39 +0900 Subject: [PATCH 10/23] Use force-delete (git branch -D) in cleanup_branches.sh Merged branches may not be fully reachable from HEAD via a fast-forward check in all cases; -D ensures deletion proceeds without errors on branches that are already merged but not recognized by -d. Co-Authored-By: Claude Sonnet 4.6 --- scripts/cleanup_branches.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/cleanup_branches.sh b/scripts/cleanup_branches.sh index eeda8b0..364768f 100755 --- a/scripts/cleanup_branches.sh +++ b/scripts/cleanup_branches.sh @@ -99,7 +99,7 @@ while IFS= read -r branch; do if $dry_run; then echo "[dry-run] Would delete: $branch" else - git branch -d "$branch" >/dev/null 2>&1 + git branch -D "$branch" >/dev/null 2>&1 echo "Deleted local branch: $branch" fi deleted_count=$((deleted_count + 1)) From b46baab20d0989c5c4b33eeaf1ed12b0860abfed Mon Sep 17 00:00:00 2001 From: hwanggg Date: Thu, 12 Mar 2026 19:56:44 +0900 Subject: [PATCH 11/23] Expand BATS test coverage across all scripts Add argument-validation tests (unknown flags, missing values), edge-case tests (multi-branch runs, empty Applied Changes, unparseable timestamps, stale+diverged detection), and structural validation tests (missing headings in CONTEXT.md / TIMELINE.md). Total test count: 78 (up from 51). Co-Authored-By: Claude Sonnet 4.6 --- tests/check_divergence.bats | 94 +++++++++++++++++++++++ tests/cleanup_branches.bats | 86 +++++++++++++++++++++ tests/compact_timeline.bats | 143 +++++++++++++++++++++++++++++++++++ tests/summarize_context.bats | 48 ++++++++++++ tests/validate_context.bats | 85 +++++++++++++++++++++ 5 files changed, 456 insertions(+) diff --git a/tests/check_divergence.bats b/tests/check_divergence.bats index 0ef4ab1..02200b1 100644 --- a/tests/check_divergence.bats +++ b/tests/check_divergence.bats @@ -127,3 +127,97 @@ create_stale_context_branch() { [[ "$output" == *"Not a Git repository"* ]] rm -rf "$non_repo" } + +@test "check_divergence rejects unknown argument" { + run "$SCRIPTS_DIR/check_divergence.sh" --unknown-flag + + [ "$status" -ne 0 ] + [[ "$output" == *"Unknown argument"* ]] +} + +@test "check_divergence fails when base branch does not exist" { + run "$SCRIPTS_DIR/check_divergence.sh" --repo "$TEST_TMPDIR" --base-branch nonexistent + + [ "$status" -ne 0 ] + [[ "$output" == *"does not exist"* ]] +} + +@test "check_divergence respects custom --base-branch" { + # Create a develop branch and a context branch off of it + git -C "$TEST_TMPDIR" checkout -b develop >/dev/null 2>&1 + git -C "$TEST_TMPDIR" checkout -b "context/bot/2026-03-12-feature" >/dev/null 2>&1 + echo "change" > "$TEST_TMPDIR/feat.txt" + git -C "$TEST_TMPDIR" add feat.txt + git -C "$TEST_TMPDIR" commit -m "feat commit" >/dev/null 2>&1 + git -C "$TEST_TMPDIR" checkout develop >/dev/null 2>&1 + + run "$SCRIPTS_DIR/check_divergence.sh" --repo "$TEST_TMPDIR" --base-branch develop --threshold 10 + + [ "$status" -eq 0 ] + [[ "$output" == *"context/bot/2026-03-12-feature: ahead=1 behind=0 (ok)"* ]] +} + +@test "check_divergence rejects --repo with missing value" { + run "$SCRIPTS_DIR/check_divergence.sh" --repo + + [ "$status" -ne 0 ] + [[ "$output" == *"Missing value for --repo"* ]] +} + +@test "check_divergence rejects --base-branch with missing value" { + run "$SCRIPTS_DIR/check_divergence.sh" --base-branch + + [ "$status" -ne 0 ] + [[ "$output" == *"Missing value for --base-branch"* ]] +} + +@test "check_divergence rejects --threshold with missing value" { + run "$SCRIPTS_DIR/check_divergence.sh" --threshold + + [ "$status" -ne 0 ] + [[ "$output" == *"Missing value for --threshold"* ]] +} + +@test "check_divergence rejects --stale-days with missing value" { + run "$SCRIPTS_DIR/check_divergence.sh" --stale-days + + [ "$status" -ne 0 ] + [[ "$output" == *"Missing value for --stale-days"* ]] +} + +@test "check_divergence warns when behind count exceeds threshold" { + create_context_branch "context/bot/2026-03-12-behind-warn" 1 + + # Add many commits to main so the context branch is far behind + for i in $(seq 1 12); do + echo "main change $i" > "$TEST_TMPDIR/main-extra-${i}.txt" + git -C "$TEST_TMPDIR" add "main-extra-${i}.txt" + git -C "$TEST_TMPDIR" commit -m "main commit $i" >/dev/null 2>&1 + done + + run "$SCRIPTS_DIR/check_divergence.sh" --repo "$TEST_TMPDIR" --threshold 10 + + [ "$status" -eq 1 ] + [[ "$output" == *"ahead=1 behind=12"* ]] + [[ "$output" == *"WARNING"* ]] +} + +@test "check_divergence reports branch that is both stale and diverged" { + local old_date + old_date="$(date -v-60d +%Y-%m-%dT12:00:00 2>/dev/null || date -d "60 days ago" +%Y-%m-%dT12:00:00)" + + git -C "$TEST_TMPDIR" checkout -b "context/bot/2026-01-01-stale-diverged" >/dev/null 2>&1 + for i in $(seq 1 12); do + echo "change $i" > "$TEST_TMPDIR/sd-${i}.txt" + git -C "$TEST_TMPDIR" add "sd-${i}.txt" + GIT_AUTHOR_DATE="$old_date" GIT_COMMITTER_DATE="$old_date" \ + git -C "$TEST_TMPDIR" commit -m "old commit $i" >/dev/null 2>&1 + done + git -C "$TEST_TMPDIR" checkout main >/dev/null 2>&1 + + run "$SCRIPTS_DIR/check_divergence.sh" --repo "$TEST_TMPDIR" --threshold 10 --stale-days 30 + + [ "$status" -eq 1 ] + [[ "$output" == *"[stale]"* ]] + [[ "$output" == *"WARNING"* ]] +} diff --git a/tests/cleanup_branches.bats b/tests/cleanup_branches.bats index f9a4551..d942a60 100644 --- a/tests/cleanup_branches.bats +++ b/tests/cleanup_branches.bats @@ -112,3 +112,89 @@ merge_context_branch_into_main() { [ "$status" -eq 0 ] [[ "$output" == *"Usage:"* ]] } + +@test "cleanup_branches fails when not a git repo" { + local non_repo + non_repo="$(mktemp -d)" + + run "$SCRIPTS_DIR/cleanup_branches.sh" --repo "$non_repo" + + [ "$status" -ne 0 ] + [[ "$output" == *"Not a Git repository"* ]] + rm -rf "$non_repo" +} + +@test "cleanup_branches rejects unknown argument" { + run "$SCRIPTS_DIR/cleanup_branches.sh" --repo "$TRACKING_REPO_DIR" --bogus + + [ "$status" -ne 0 ] + [[ "$output" == *"Unknown argument"* ]] +} + +@test "cleanup_branches respects custom --base-branch" { + # Create a develop branch and make it the base + git -C "$TRACKING_REPO_DIR" checkout -b develop >/dev/null 2>&1 + git -C "$TRACKING_REPO_DIR" checkout main >/dev/null 2>&1 + + # Create an old context branch and merge it into develop + create_context_branch_commit "context/alice/2020-01-15-dev-branch" "dev-branch.txt" "dev branch" "$OLD_DATE" + git -C "$TRACKING_REPO_DIR" checkout develop >/dev/null 2>&1 + git -C "$TRACKING_REPO_DIR" merge --ff-only "context/alice/2020-01-15-dev-branch" >/dev/null 2>&1 + git -C "$TRACKING_REPO_DIR" checkout main >/dev/null 2>&1 + + run "$SCRIPTS_DIR/cleanup_branches.sh" --repo "$TRACKING_REPO_DIR" --base-branch develop + + [ "$status" -eq 0 ] + [[ "$output" == *"Deleted local branch: context/alice/2020-01-15-dev-branch"* ]] +} + +@test "cleanup_branches rejects --repo with missing value" { + run "$SCRIPTS_DIR/cleanup_branches.sh" --repo + + [ "$status" -ne 0 ] + [[ "$output" == *"Missing value for --repo"* ]] +} + +@test "cleanup_branches rejects --base-branch with missing value" { + run "$SCRIPTS_DIR/cleanup_branches.sh" --base-branch + + [ "$status" -ne 0 ] + [[ "$output" == *"Missing value for --base-branch"* ]] +} + +@test "cleanup_branches rejects --days with missing value" { + run "$SCRIPTS_DIR/cleanup_branches.sh" --days + + [ "$status" -ne 0 ] + [[ "$output" == *"Missing value for --days"* ]] +} + +@test "cleanup_branches skips the currently checked-out context branch" { + create_context_branch_commit "context/alice/2020-01-15-active-ctx" "active-ctx.txt" "active ctx" "$OLD_DATE" + merge_context_branch_into_main "context/alice/2020-01-15-active-ctx" + # Check out the merged (old) context branch as the current branch + git -C "$TRACKING_REPO_DIR" checkout "context/alice/2020-01-15-active-ctx" >/dev/null 2>&1 + + run "$SCRIPTS_DIR/cleanup_branches.sh" --repo "$TRACKING_REPO_DIR" + + [ "$status" -eq 0 ] + [[ "$output" == *"No context branches to clean up."* ]] + run git -C "$TRACKING_REPO_DIR" rev-parse --verify "context/alice/2020-01-15-active-ctx" + [ "$status" -eq 0 ] +} + +@test "cleanup_branches deletes multiple merged context branches in one run" { + create_context_branch_commit "context/alice/2020-01-15-multi-1" "multi1.txt" "multi 1" "$OLD_DATE" + merge_context_branch_into_main "context/alice/2020-01-15-multi-1" + create_context_branch_commit "context/bob/2020-01-15-multi-2" "multi2.txt" "multi 2" "$OLD_DATE" + merge_context_branch_into_main "context/bob/2020-01-15-multi-2" + + run "$SCRIPTS_DIR/cleanup_branches.sh" --repo "$TRACKING_REPO_DIR" + + [ "$status" -eq 0 ] + [[ "$output" == *"Total: 2 branch(es) deleted."* ]] + run git -C "$TRACKING_REPO_DIR" rev-parse --verify "context/alice/2020-01-15-multi-1" + [ "$status" -ne 0 ] + run git -C "$TRACKING_REPO_DIR" rev-parse --verify "context/bob/2020-01-15-multi-2" + [ "$status" -ne 0 ] +} diff --git a/tests/compact_timeline.bats b/tests/compact_timeline.bats index ab68f3d..fe38759 100644 --- a/tests/compact_timeline.bats +++ b/tests/compact_timeline.bats @@ -163,3 +163,146 @@ EOF [ "$status" -eq 0 ] [[ "$output" == *"Shared context structure is valid"* ]] } + +@test "compact_timeline rejects unknown argument" { + run "$SCRIPTS_DIR/compact_timeline.sh" --bogus-flag + + [ "$status" -ne 0 ] + [[ "$output" == *"Unknown argument"* ]] +} + +@test "compact_timeline rejects --days with missing value" { + run "$SCRIPTS_DIR/compact_timeline.sh" --days + + [ "$status" -ne 0 ] +} + +@test "compact_timeline with --days 0 compacts all entries" { + bootstrap_context + local today + today="$(date +%Y-%m-%d)" + create_timeline_with_entries "$today" "$today" + + run "$SCRIPTS_DIR/compact_timeline.sh" --repo "$TEST_TMPDIR" --days 0 --dry-run + + [ "$status" -eq 0 ] + [[ "$output" == *"2 entry/entries older than 0 days"* ]] +} + +@test "compact_timeline entry with no Applied Changes bullets compacts cleanly" { + bootstrap_context + local old_date + old_date="$(date -v-60d +%Y-%m-%d 2>/dev/null || date -d '60 days ago' +%Y-%m-%d)" + cat > "$TEST_TMPDIR/TIMELINE.md" < "$TEST_TMPDIR/TIMELINE.md" <<'EOF' +# Timeline + +Use this file as an append-only record of meaningful context changes. + +## Entry Template + +```markdown +### 2026-03-12T09:15:00Z | agent-name +- Timestamp: 2026-03-12T09:15:00Z +- Actor: agent-name +- Trigger: What prompted this update +- Applied Changes: + - Summarize the facts, decisions, or context that changed +- Unresolved Items: + - List remaining uncertainty, or write `- None` +``` + +## Entries + +### not-a-date | some-bot +- Timestamp: not-a-date +- Actor: some-bot +- Trigger: Unparseable timestamp test +- Applied Changes: + - Some important fact +- Unresolved Items: + - None +EOF + + run "$SCRIPTS_DIR/compact_timeline.sh" --repo "$TEST_TMPDIR" --days 30 --dry-run + + [ "$status" -eq 0 ] + [[ "$output" == *"No entries older than 30 days to compact"* ]] +} diff --git a/tests/summarize_context.bats b/tests/summarize_context.bats index 060daff..7ee0725 100644 --- a/tests/summarize_context.bats +++ b/tests/summarize_context.bats @@ -113,3 +113,51 @@ EOF [ "$status" -eq 0 ] [[ "$output" == *"Usage:"* ]] } + +@test "summarize_context rejects unknown argument" { + run "$SCRIPTS_DIR/summarize_context.sh" --bogus-flag + + [ "$status" -ne 0 ] + [[ "$output" == *"Unknown argument"* ]] +} + +@test "summarize_context rejects --repo with missing value" { + run "$SCRIPTS_DIR/summarize_context.sh" --repo + + [ "$status" -ne 0 ] +} + +@test "summarize_context emits all three hints when all conditions are met" { + bootstrap_context + { + echo "# Shared Context" + echo "" + echo "## Overview" + echo "- Same bullet" + for i in $(seq 1 30); do echo "- Fact $i"; done + echo "" + echo "## Stable Facts" + echo "- Same bullet" + for i in $(seq 1 30); do echo "- Stable $i"; done + echo "" + echo "## Active Context" + for i in $(seq 1 30); do echo "- Active $i"; done + echo "" + echo "## Decisions" + for i in $(seq 1 15); do echo "- Decision $i"; done + echo "" + echo "## Open Questions" + for i in $(seq 1 15); do echo "- Question $i"; done + } > "$TEST_TMPDIR/CONTEXT.md" + + for i in $(seq 1 21); do + printf '\n### 2026-03-12T10:%02d:00Z | bot\n- Entry %d\n' "$i" "$i" >> "$TEST_TMPDIR/TIMELINE.md" + done + + run "$SCRIPTS_DIR/summarize_context.sh" --repo "$TEST_TMPDIR" + + [ "$status" -eq 0 ] + [[ "$output" == *"longer than 120 lines"* ]] + [[ "$output" == *"more than 20 entries"* ]] + [[ "$output" == *"Duplicate bullets detected in CONTEXT.md"* ]] +} diff --git a/tests/validate_context.bats b/tests/validate_context.bats index 57e986a..ac0c93d 100644 --- a/tests/validate_context.bats +++ b/tests/validate_context.bats @@ -264,6 +264,91 @@ EOF [[ "$output" == *"empty Trigger field"* ]] } +@test "validate_context rejects unknown argument" { + run "$SCRIPTS_DIR/validate_context.sh" --unknown-flag + + [ "$status" -ne 0 ] + [[ "$output" == *"Unknown argument"* ]] +} + +@test "validate_context fails when CONTEXT.md is missing" { + setup_tmpdir + cp "$TEMPLATES_DIR/TIMELINE.md" "$TEST_TMPDIR/TIMELINE.md" + + run "$SCRIPTS_DIR/validate_context.sh" --repo "$TEST_TMPDIR" + + [ "$status" -ne 0 ] + [[ "$output" == *"Missing CONTEXT.md"* ]] +} + +@test "validate_context fails when TIMELINE.md is missing" { + setup_tmpdir + cp "$TEMPLATES_DIR/CONTEXT.md" "$TEST_TMPDIR/CONTEXT.md" + + run "$SCRIPTS_DIR/validate_context.sh" --repo "$TEST_TMPDIR" + + [ "$status" -ne 0 ] + [[ "$output" == *"Missing TIMELINE.md"* ]] +} + +@test "validate_context fails when TIMELINE.md is missing '# Timeline' heading" { + bootstrap_context + # Remove the top-level heading + sed 's/^# Timeline$//' "$TEST_TMPDIR/TIMELINE.md" > "$TEST_TMPDIR/TIMELINE.md.tmp" + mv "$TEST_TMPDIR/TIMELINE.md.tmp" "$TEST_TMPDIR/TIMELINE.md" + + run "$SCRIPTS_DIR/validate_context.sh" --repo "$TEST_TMPDIR" + + [ "$status" -ne 0 ] + [[ "$output" == *"missing '# Timeline'"* ]] +} + +@test "validate_context fails when TIMELINE.md is missing '## Entries' section" { + bootstrap_context + sed 's/^## Entries$//' "$TEST_TMPDIR/TIMELINE.md" > "$TEST_TMPDIR/TIMELINE.md.tmp" + mv "$TEST_TMPDIR/TIMELINE.md.tmp" "$TEST_TMPDIR/TIMELINE.md" + + run "$SCRIPTS_DIR/validate_context.sh" --repo "$TEST_TMPDIR" + + [ "$status" -ne 0 ] + [[ "$output" == *"missing '## Entries'"* ]] +} + +@test "validate_context rejects --repo with missing value" { + run "$SCRIPTS_DIR/validate_context.sh" --repo + + [ "$status" -ne 0 ] + [[ "$output" == *"Missing value for --repo"* ]] +} + +@test "validate_context rejects missing Stable Facts heading" { + bootstrap_context + python3 - <<'PY' "$TEST_TMPDIR/CONTEXT.md" +from pathlib import Path +import sys + +path = Path(sys.argv[1]) +text = path.read_text() +path.write_text(text.replace("## Stable Facts\n\n", "", 1)) +PY + + run "$SCRIPTS_DIR/validate_context.sh" --repo "$TEST_TMPDIR" + + [ "$status" -ne 0 ] + [[ "$output" == *"CONTEXT.md is missing required heading matching: ^## Stable Facts$"* ]] +} + +@test "validate_context fails when TIMELINE.md is missing Entry Template section" { + bootstrap_context + sed 's/^## Entry Template$//' "$TEST_TMPDIR/TIMELINE.md" > "$TEST_TMPDIR/TIMELINE.md.tmp" + mv "$TEST_TMPDIR/TIMELINE.md.tmp" "$TEST_TMPDIR/TIMELINE.md" + + run "$SCRIPTS_DIR/validate_context.sh" --repo "$TEST_TMPDIR" + + [ "$status" -ne 0 ] + [[ "$output" == *"missing '## Entry Template'"* ]] +} + @test "validate_context without --strict passes invalid timestamp (backward compat)" { bootstrap_context cat > "$TEST_TMPDIR/TIMELINE.md" <<'EOF' From 2e9efc5a3fb3ea4ac203313774605f9dd136d348 Mon Sep 17 00:00:00 2001 From: hwanggg Date: Thu, 12 Mar 2026 19:56:48 +0900 Subject: [PATCH 12/23] Add CTO-CMO collaboration process reference document Documents the sync cadence, escalation paths, and decision ownership between CTO and CMO roles. Co-Authored-By: Claude Sonnet 4.6 --- references/cto-cmo-sync-process.md | 140 +++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 references/cto-cmo-sync-process.md diff --git a/references/cto-cmo-sync-process.md b/references/cto-cmo-sync-process.md new file mode 100644 index 0000000..815fdad --- /dev/null +++ b/references/cto-cmo-sync-process.md @@ -0,0 +1,140 @@ +# CTO-CMO Technical Marketing Sync Process + +This document defines how the CTO and CMO collaborate to turn technical updates into marketing content and maintain alignment on messaging. + +## 1. Tech Update → Marketing Content Workflow + +``` +CTO writes technical update + → Posts to shared context branch (context/cto/*) + → CMO reads and extracts marketable highlights + → CMO drafts content (blog post, social, release note) + → CTO reviews for technical accuracy + → CMO publishes +``` + +### Handoff Format + +When the CTO produces a technical update intended for marketing, use this structure in a `HANDOFF.md` entry: + +| Field | Description | +| -------------------- | ------------------------------------------------------------ | +| **What Changed** | Technical summary of the feature, fix, or improvement | +| **Why It Matters** | User-facing impact in plain language | +| **Key Terms** | Jargon that needs translation or careful framing | +| **Suggested Angle** | CTO's recommendation for how to position the update | +| **Audience** | Primary target (developers, end-users, enterprise, etc.) | +| **Assets Available** | Screenshots, diagrams, benchmarks, demo links | + +### Content Types by Update Category + +| Technical Update | Marketing Output | Lead | +| ---------------------- | --------------------------------- | ------- | +| New feature release | Blog post + social thread | CMO | +| Bug fix / patch | Release note (changelog only) | CTO | +| Architecture change | Technical blog post | CTO + CMO | +| Performance improvement| Benchmark post + social highlight | CMO | +| Breaking change | Migration guide + announcement | CTO + CMO | + +## 2. Biweekly Sync Meeting Agenda Template + +**Cadence:** Every other week (격주), 30 minutes +**Participants:** CTO, CMO +**Channel:** Paperclip issue thread or dedicated sync issue + +### Agenda + +1. **Tech Pipeline Review** (10 min) + - CTO shares upcoming releases and technical milestones + - Flag items with marketing potential + - Estimate release dates + +2. **Content Pipeline Review** (10 min) + - CMO shares content in draft, in review, or scheduled + - CTO flags any accuracy concerns + - Align on publishing timeline + +3. **Shared Context Sync** (5 min) + - Review open items in `CONTEXT.md` Active Context section + - Update or archive stale decisions + - Identify any diverged context branches needing merge + +4. **Action Items** (5 min) + - Each party leaves with no more than 2-3 concrete next steps + - Record actions as Paperclip subtasks when trackable + +### Post-Sync Ritual + +After each sync, the CTO updates the shared context repo: +```bash +scripts/prepare_branch.sh --actor cto --slug cto-cmo-sync +# Update CONTEXT.md with decisions and active state +# Update TIMELINE.md with sync summary entry +scripts/validate_context.sh +``` + +## 3. Shared Context Document (DECISIONS.md) Usage + +### Where to Record Decisions + +Use `CONTEXT.md > Decisions` section for cross-functional decisions between CTO and CMO. Each entry follows this format: + +```markdown +- **[YYYY-MM-DD] Decision title** — Brief rationale. + Decided by: CTO + CMO. Revisit by: [date or "when X changes"]. +``` + +### Decision Categories to Track + +| Category | Examples | +| --------------------- | --------------------------------------------------------- | +| Brand voice for tech | "We say 'AI agents' not 'bots'" | +| Release cadence | "Announce features on Tuesdays" | +| Content ownership | "CTO owns API docs, CMO owns blog" | +| Tool choices | "Use shared-context-git-skill for all cross-team context" | +| Audience targeting | "Primary audience is developer tooling teams" | + +### Escalation + +If CTO and CMO disagree on a decision, escalate to CEO via Paperclip issue with: +- Both positions summarized +- Impact of each option +- Recommended path forward + +## 4. Tech Blog / Release Notes Collaboration Procedure + +### Release Notes (every release) + +1. CTO drafts release notes in a context branch with the changelog +2. CMO reviews for clarity and adds user-facing framing if needed +3. CTO merges after approval +4. CMO extracts highlights for social/newsletter if warranted + +### Technical Blog Posts (as needed) + +1. **Proposal:** Either party creates a Paperclip issue with: + - Proposed topic and angle + - Target audience + - Estimated effort (draft, review, publish) + +2. **Drafting:** + - CTO writes technical substance + - CMO adds introduction, conclusion, and accessibility edits + - Both use a shared context branch for the draft + +3. **Review cycle:** + - CTO reviews for accuracy + - CMO reviews for readability and brand alignment + - Maximum 2 review rounds before publish decision + +4. **Publishing:** + - CMO handles scheduling and distribution + - CTO updates project docs/README if the post references shipped features + +### Naming Convention for Collaboration Branches + +``` +context/cto-cmo/YYYY-MM-DD- +``` + +Example: `context/cto-cmo/2026-03-12-v2-release-blog` From 56649d1f68b2118933890d2693452e5ec262598b Mon Sep 17 00:00:00 2001 From: hwanggg Date: Thu, 12 Mar 2026 19:57:02 +0900 Subject: [PATCH 13/23] Add GitHub Actions CI workflow with ShellCheck and BATS test reporting - Triggers on push to main and all pull requests - ShellCheck job: static analysis for all scripts/*.sh - BATS job: runs full test suite with TAP formatter, uploads results artifact - Validate-context job: checks shared-context files on PRs Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/ci.yml | 75 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..767274f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,75 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + +jobs: + shellcheck: + name: ShellCheck + runs-on: ubuntu-latest + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Install ShellCheck + run: sudo apt-get install -y shellcheck + + - name: Run ShellCheck on scripts + run: shellcheck scripts/*.sh + + bats: + name: BATS Test Suite + runs-on: ubuntu-latest + + steps: + - name: Check out repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Run BATS suite (TAP output) + run: | + ./tests/run_tests.sh --formatter tap | tee test-results.tap + echo "exit_code=${PIPESTATUS[0]}" >> "$GITHUB_ENV" + + - name: Fail if tests failed + if: env.exit_code != '0' + run: exit 1 + + - name: Upload TAP test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: bats-tap-results + path: test-results.tap + + validate-context: + name: Validate Shared Context + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Check for shared-context file changes + id: changed + run: | + FILES=$(git diff --name-only "${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }}") + if echo "$FILES" | grep -qE '^(CONTEXT\.md|TIMELINE\.md|HANDOFF\.md|POLICY\.md)$'; then + echo "has_context_files=true" >> "$GITHUB_OUTPUT" + else + echo "has_context_files=false" >> "$GITHUB_OUTPUT" + fi + + - name: Run validate_context.sh + if: steps.changed.outputs.has_context_files == 'true' + run: ./scripts/validate_context.sh + + - name: Skip validation (no context files changed) + if: steps.changed.outputs.has_context_files != 'true' + run: echo "No shared-context files changed — skipping validation" From 8742b0e4abd88ddc754a9b812b5b6d237f55e803 Mon Sep 17 00:00:00 2001 From: hwanggg Date: Thu, 12 Mar 2026 20:04:02 +0900 Subject: [PATCH 14/23] Add edge-case and validation tests across all BATS suites Cover overwrite-guard in bootstrap, argument validation in check_divergence and cleanup_branches, empty-section and unparseable- timestamp handling in compact_timeline, heading-structure checks in summarize and validate scripts. Co-Authored-By: Claude Opus 4.6 --- tests/bootstrap_repo.bats | 12 ++++ tests/check_divergence.bats | 13 ++++ tests/cleanup_branches.bats | 17 ++++++ tests/compact_timeline.bats | 112 +++++++++++++++++++++++++++++++++++ tests/summarize_context.bats | 24 ++++++++ tests/validate_context.bats | 58 ++++++++++++++++++ 6 files changed, 236 insertions(+) diff --git a/tests/bootstrap_repo.bats b/tests/bootstrap_repo.bats index 0fae54e..277d52f 100644 --- a/tests/bootstrap_repo.bats +++ b/tests/bootstrap_repo.bats @@ -84,3 +84,15 @@ teardown() { teardown_tmpdir; } [ "$status" -ne 0 ] [[ "$output" == *"Missing value"* ]] } + +@test "bootstrap refuses to overwrite existing CONTEXT.md even when TIMELINE.md is absent" { + # Only place CONTEXT.md in the target — TIMELINE.md is not there yet + cp "$TEMPLATES_DIR/CONTEXT.md" "$TEST_TMPDIR/CONTEXT.md" + + run "$SCRIPTS_DIR/bootstrap_repo.sh" --target "$TEST_TMPDIR" + + [ "$status" -ne 0 ] + [[ "$output" == *"Refusing to overwrite"* ]] + # TIMELINE.md should NOT have been created + [ ! -f "$TEST_TMPDIR/TIMELINE.md" ] +} diff --git a/tests/check_divergence.bats b/tests/check_divergence.bats index 02200b1..a64abca 100644 --- a/tests/check_divergence.bats +++ b/tests/check_divergence.bats @@ -202,6 +202,19 @@ create_stale_context_branch() { [[ "$output" == *"WARNING"* ]] } +@test "check_divergence reports all branches when mix of ok and warned" { + create_context_branch "context/bot/2026-03-12-ok-branch" 3 + create_context_branch "context/bot/2026-03-12-over-threshold" 15 + + run "$SCRIPTS_DIR/check_divergence.sh" --repo "$TEST_TMPDIR" --threshold 10 + + [ "$status" -eq 1 ] + [[ "$output" == *"context/bot/2026-03-12-ok-branch"* ]] + [[ "$output" == *"(ok)"* ]] + [[ "$output" == *"context/bot/2026-03-12-over-threshold"* ]] + [[ "$output" == *"WARNING"* ]] +} + @test "check_divergence reports branch that is both stale and diverged" { local old_date old_date="$(date -v-60d +%Y-%m-%dT12:00:00 2>/dev/null || date -d "60 days ago" +%Y-%m-%dT12:00:00)" diff --git a/tests/cleanup_branches.bats b/tests/cleanup_branches.bats index d942a60..4ecb265 100644 --- a/tests/cleanup_branches.bats +++ b/tests/cleanup_branches.bats @@ -183,6 +183,23 @@ merge_context_branch_into_main() { [ "$status" -eq 0 ] } +@test "cleanup_branches dry-run with --remote reports would-delete without modifying" { + create_context_branch_commit "context/alice/2020-01-15-remote-dryrun" "remote-dryrun.txt" "remote dry run" "$OLD_DATE" + git -C "$TRACKING_REPO_DIR" push -u origin "context/alice/2020-01-15-remote-dryrun" >/dev/null 2>&1 + merge_context_branch_into_main "context/alice/2020-01-15-remote-dryrun" + git -C "$TRACKING_REPO_DIR" push origin main >/dev/null 2>&1 + git -C "$TRACKING_REPO_DIR" fetch origin >/dev/null 2>&1 + + run "$SCRIPTS_DIR/cleanup_branches.sh" --repo "$TRACKING_REPO_DIR" --dry-run --remote + + [ "$status" -eq 0 ] + [[ "$output" == *"dry-run"* ]] + # Branch should still exist on remote + run git -C "$TRACKING_REPO_DIR" ls-remote --heads origin "context/alice/2020-01-15-remote-dryrun" + [ "$status" -eq 0 ] + [ -n "$output" ] +} + @test "cleanup_branches deletes multiple merged context branches in one run" { create_context_branch_commit "context/alice/2020-01-15-multi-1" "multi1.txt" "multi 1" "$OLD_DATE" merge_context_branch_into_main "context/alice/2020-01-15-multi-1" diff --git a/tests/compact_timeline.bats b/tests/compact_timeline.bats index fe38759..c53e9b1 100644 --- a/tests/compact_timeline.bats +++ b/tests/compact_timeline.bats @@ -269,6 +269,118 @@ EOF [ "$status" -eq 0 ] } +@test "compact_timeline appends facts after existing Stable Facts content" { + bootstrap_context + local old_date + old_date="$(date -v-60d +%Y-%m-%d 2>/dev/null || date -d '60 days ago' +%Y-%m-%d)" + + # Add an existing fact to Stable Facts section + python3 - < "$TEST_TMPDIR/TIMELINE.md" < "$TEST_TMPDIR/TIMELINE.md" < "$TEST_TMPDIR/TIMELINE.md" <<'EOF' diff --git a/tests/summarize_context.bats b/tests/summarize_context.bats index 7ee0725..ce8a0af 100644 --- a/tests/summarize_context.bats +++ b/tests/summarize_context.bats @@ -127,6 +127,30 @@ EOF [ "$status" -ne 0 ] } +@test "summarize_context handles CONTEXT.md with no bullets in any section" { + bootstrap_context + cat > "$TEST_TMPDIR/CONTEXT.md" <<'EOF' +# Shared Context + +## Overview + +## Stable Facts + +## Active Context + +## Decisions + +## Open Questions +EOF + + run "$SCRIPTS_DIR/summarize_context.sh" --repo "$TEST_TMPDIR" + + [ "$status" -eq 0 ] + [[ "$output" == *"# Shared Context Summary"* ]] + # Should not report duplicate bullets (there are none) + [[ "$output" != *"Duplicate bullets"* ]] +} + @test "summarize_context emits all three hints when all conditions are met" { bootstrap_context { diff --git a/tests/validate_context.bats b/tests/validate_context.bats index ac0c93d..2a24151 100644 --- a/tests/validate_context.bats +++ b/tests/validate_context.bats @@ -349,6 +349,64 @@ PY [[ "$output" == *"missing '## Entry Template'"* ]] } +@test "validate_context passes when CONTEXT.md has duplicate required heading" { + bootstrap_context + # Add a second ## Decisions heading — grep -Eq still finds the pattern, so should pass + printf '\n## Decisions\n\n- Duplicate section\n' >> "$TEST_TMPDIR/CONTEXT.md" + + run "$SCRIPTS_DIR/validate_context.sh" --repo "$TEST_TMPDIR" + + [ "$status" -eq 0 ] + [[ "$output" == *"Shared context structure is valid"* ]] +} + +@test "validate_context --strict fails on invalid entry even when a valid entry precedes it" { + bootstrap_context + cat > "$TEST_TMPDIR/TIMELINE.md" <<'EOF' +# Timeline + +Use this file as an append-only record of meaningful context changes. + +## Entry Template + +```markdown +### 2026-03-12T09:15:00Z | agent-name +- Timestamp: 2026-03-12T09:15:00Z +- Actor: agent-name +- Trigger: What prompted this update +- Applied Changes: + - Summarize the facts, decisions, or context that changed +- Unresolved Items: + - List remaining uncertainty, or write `- None` +``` + +## Entries + +### 2026-03-12T10:00:00Z | valid-bot +- Timestamp: 2026-03-12T10:00:00Z +- Actor: valid-bot +- Trigger: Valid entry +- Applied Changes: + - Something valid +- Unresolved Items: + - None + +### bad-timestamp | invalid-bot +- Timestamp: not-a-date +- Actor: invalid-bot +- Trigger: Invalid entry +- Applied Changes: + - Something invalid +- Unresolved Items: + - None +EOF + + run "$SCRIPTS_DIR/validate_context.sh" --repo "$TEST_TMPDIR" --strict + + [ "$status" -ne 0 ] + [[ "$output" == *"invalid ISO 8601 timestamp"* ]] +} + @test "validate_context without --strict passes invalid timestamp (backward compat)" { bootstrap_context cat > "$TEST_TMPDIR/TIMELINE.md" <<'EOF' From 8156b8abcd57642dc08d09d86b0d197b476f4c8d Mon Sep 17 00:00:00 2001 From: hwanggg Date: Thu, 12 Mar 2026 20:04:08 +0900 Subject: [PATCH 15/23] Add competitive tools analysis reference document Compare shared-context-git-skill positioning against git-branchless, Git Town, Graphite CLI, Jujutsu, and other adjacent tools to clarify differentiation as a Git-native coordination layer for AI agents. Co-Authored-By: Claude Opus 4.6 --- references/competitive-tools-analysis.md | 203 +++++++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 references/competitive-tools-analysis.md diff --git a/references/competitive-tools-analysis.md b/references/competitive-tools-analysis.md new file mode 100644 index 0000000..3eb3de8 --- /dev/null +++ b/references/competitive-tools-analysis.md @@ -0,0 +1,203 @@ +# Competitive Tools Analysis + +This report compares `shared-context-git-skill` with adjacent Git workflow and stacked-change tools to clarify where the project should differentiate. + +## Scope + +Analyzed tools: + +- `git-branchless` +- `Git Town` +- `git-machete` +- `git-stack` +- `ghstack` +- `Graphite CLI` +- `Jujutsu (jj)` +- `Sapling` + +These are not perfect substitutes. Most focus on branch stacking, commit graph editing, or pull request automation. `shared-context-git-skill` is closer to a Git-native coordination layer for AI agents that need durable, reviewable shared memory. + +## Quick Comparison + +| Tool | Primary job | Workflow shape | Hosting dependency | Strengths | Weaknesses vs `shared-context-git-skill` | +| --- | --- | --- | --- | --- | --- | +| `shared-context-git-skill` | Shared project context for multiple AI agents | Markdown memory + branch-first updates | None required | Durable context, human-readable docs, provider-agnostic, safe sync/validation | Not a full stacked-PR or commit-rewrite tool | +| `git-branchless` | Commit graph manipulation and patch-stack workflows | Commit-centric | Low | Smartlog, undo, restack, high performance, monorepo friendly | Optimizes code history, not shared knowledge artifacts | +| `Git Town` | Branch workflow automation | Branch-centric | Medium | Works across common Git flows, automates sync/ship/propose, stacked branch helpers | Oriented to human branch operations, not shared memory or agent handoff | +| `git-machete` | Branch stack organization and traversal | Branch hierarchy file + traversal | Medium | Clear branch tree, rebase/merge/push automation, PR integration | Requires branch layout management, little support for persistent narrative context | +| `git-stack` | Lightweight stacked branch management | Branch stack + rebase automation | Low | Parent autodetection, undo support, minimal workflow intrusion | Focused on stack maintenance, not documentation or collaboration memory | +| `ghstack` | Submit stacked diffs to GitHub | Commit stack to PR stack | High (GitHub) | Efficient stacked PR submission, strong fit for GitHub review | GitHub-specific, token/config heavy, not useful for local/shared context capture | +| `Graphite CLI` | Stack PR workflow plus cloud review product | Branch/PR stack | Very high (Graphite SaaS) | Smooth stacked PR UX, submission/sync tooling, adjacent review product | SaaS-centric, not Git-host neutral, not built for agent-readable context docs | +| `Jujutsu (jj)` | Alternative Git-compatible VCS UX | Change/operation-centric | Low | Automatic rebases, operation log, no staging pain, powerful history editing | Requires adopting a new CLI/model; overkill when teams only need shared context | +| `Sapling` | Scalable source control system with stacked workflows | VCS/platform-scale | Medium | Strong UX at scale, smartlog, large-repo ergonomics | Broader SCM migration story; much heavier than a portable Git skill | + +## Competitor Notes + +### `git-branchless` + +- Positioning: high-velocity Git workflow suite for patch stacks and large repos. +- Notable capabilities: `git undo`, `git smartlog`, `git restack`, anonymous branching, in-memory graph operations, strong monorepo performance claims. +- Strongest overlap: branch-first / stack-oriented collaboration and safe history manipulation. +- Gap relative to us: it manages commits and branches, not durable project state in Markdown. It helps you reshape work, but it does not define how multiple agents preserve facts, decisions, questions, and handoffs. + +### `Git Town` + +- Positioning: high-level CLI that automates common Git branch workflows across GitHub Flow, Git Flow, GitLab Flow, and trunk-based development. +- Notable capabilities: branch creation, sync, ship, stacked branch commands, undo/continue flows, PR proposal helpers. +- Strongest overlap: opinionated automation on top of plain Git. +- Gap relative to us: centered on developer branch hygiene and shipping, not on shared memory or AI-to-AI coordination. + +### `git-machete` + +- Positioning: branch stack organizer with a bird's-eye status view and traversal helpers. +- Notable capabilities: `.git/machete` branch layout, status/traverse, GitHub/GitLab PR helpers, focus on small stacked PRs. +- Strongest overlap: explicit stack structure and review-friendly workflows. +- Gap relative to us: the source of truth is branch topology, not shared narrative state. Teams still need separate docs or conventions for decisions and handoffs. + +### `git-stack` + +- Positioning: unobtrusive stacked branch management for Git. +- Notable capabilities: upstream parent auto-detection, sync/rebase helpers, stack navigation, branch-state undo backup. +- Strongest overlap: lightweight Git-native workflow augmentation. +- Gap relative to us: no opinionated schema for context, no handoff artifacts, no validation around project memory quality. + +### `ghstack` + +- Positioning: turn a local stack of commits into separate GitHub pull requests. +- Notable capabilities: per-commit PR submission, reland/update flow, GitHub landing command. +- Strongest overlap: stack-aware collaboration through Git primitives. +- Gap relative to us: tightly coupled to GitHub PR mechanics; not a context-sharing solution and not host-neutral. + +### `Graphite CLI` + +- Positioning: polished CLI for stacked PRs, backed by a broader review/inbox/merge-queue platform. +- Notable capabilities: `gt create`, `gt log`, `gt submit`, `gt modify`, `gt sync`; integrated PR inbox and merge queue. +- Strongest overlap: strong developer UX around stacked changes. +- Gap relative to us: value concentrates in hosted review workflow and PR operations. It does not solve persistent cross-agent project context in a portable repo artifact. + +### `Jujutsu (jj)` + +- Positioning: Git-compatible VCS with a simpler, more powerful workflow model. +- Notable capabilities: automatic working-copy commits, operation log, automatic rebase, first-class conflict handling, colocated Git compatibility. +- Strongest overlap: making history safer and easier for iterative work. +- Gap relative to us: it is a new version-control interface, not a lightweight skill teams can layer onto existing Git repos and AI workflows immediately. + +### `Sapling` + +- Positioning: scalable, user-friendly source control system with Git compatibility and stack-oriented UX. +- Notable capabilities: smartlog-style UI, large-repo ergonomics, interactive smartlog UI, deeper ecosystem around Meta-scale source control. +- Strongest overlap: stacked work and large-scale collaboration ergonomics. +- Gap relative to us: much broader SCM/runtime scope. Adoption cost is materially higher than a shell-script + Markdown skill for shared context. + +## Strengths And Weaknesses By Category + +### Where competitors are stronger + +- **PR and branch automation:** `Git Town`, `git-machete`, `git-stack`, `ghstack`, and `Graphite` are stronger when the main job is syncing stacks, submitting PRs, and shipping code branches quickly. +- **History rewriting UX:** `git-branchless` and `jj` are stronger for commit surgery, restacking, undo, and graph manipulation. +- **Large-scale repo ergonomics:** `git-branchless`, `jj`, and `Sapling` make stronger claims around monorepo scale and advanced VCS ergonomics. + +### Where `shared-context-git-skill` is stronger + +- **Durable shared memory:** The product explicitly stores stable facts, active context, decisions, open questions, and handoffs as first-class repo artifacts. +- **Agent collaboration model:** The workflow is naturally suited to multiple AI agents or humans + agents who need to read before writing and leave a reviewable trail. +- **Provider neutrality:** It works with standard Git CLI and Markdown instead of requiring GitHub-specific APIs, SaaS accounts, or a new VCS. +- **Low adoption cost:** Teams can layer it onto an existing repository or separate context repo without retraining everyone on a new source control model. +- **Governance and safety:** The built-in sync, validation, conflict-stop behavior, and branch-first update rules reduce the risk of silent context corruption. + +## Differentiation For `shared-context-git-skill` + +The clearest differentiation is: + +> Most competing tools optimize code history. `shared-context-git-skill` optimizes shared understanding. + +That distinction matters because AI-agent teams often fail not from branch complexity alone, but from missing or stale context: + +- what is true now +- what was decided and why +- what remains uncertain +- what the next agent needs to know + +Competitors generally assume this context exists somewhere else (chat logs, issue trackers, PR descriptions, wikis, or human memory). `shared-context-git-skill` makes that context explicit, versioned, reviewable, and portable. + +Additional differentiators: + +- **Git-backed knowledge schema:** structured Markdown templates rather than free-form notes. +- **Context-specific validation:** checks document presence and shape instead of just branch state. +- **Handoff support:** explicit `HANDOFF.md` and timeline patterns make asynchronous relay easier. +- **Clean boundary with provider tooling:** keeps context management generic while letting PR creation happen elsewhere. +- **Works for non-code coordination:** can capture product, research, operations, or cross-functional alignment, not just code diffs. + +## Recommended Market Positioning + +### Core positioning + +`shared-context-git-skill` should be positioned as a **Git-native shared memory layer for AI agent collaboration**. + +Suggested framing: + +- "Shared context for multi-agent software work, stored where engineers already trust history: Git." +- "A durable, reviewable memory repo for AI agents and humans." +- "Not another stacked-PR tool - a way to keep project understanding synchronized." + +### Ideal buyer / adopter profile + +- Teams experimenting with multiple coding agents on one project +- Engineering orgs that need auditable AI handoffs +- AI-native tooling teams building agent orchestration workflows +- Small teams that want process discipline without adopting a new VCS or heavyweight platform + +### Best adjacent category + +Do not market it as a direct replacement for `Graphite`, `ghstack`, or `git-branchless`. + +Instead, market it as complementary to them: + +- Use `git-branchless`/`Git Town`/`Graphite` to manage code stacks. +- Use `shared-context-git-skill` to manage the durable project context those stacks depend on. + +### Messaging angles worth testing + +1. **Multi-agent reliability** - "Prevent duplicated work and lost context across AI agents." +2. **Auditability** - "Track facts, decisions, and handoffs with Git history." +3. **Portability** - "Works with GitHub, GitLab, Bitbucket, or a plain remote." +4. **Low-friction adoption** - "Bash + Markdown + Git, no platform migration required." +5. **Human-agent collaboration** - "Make AI work legible to humans and vice versa." + +## Strategic Recommendations + +### Product + +- Double down on the **schema + validation + handoff** story; that is the least commoditized part of the product. +- Add more examples showing **multiple agents collaborating asynchronously** on one project. +- Consider lightweight adapters into issue trackers or PR templates, but keep the Git/Markdown core independent. + +### GTM + +- Target content toward the emerging "AI engineering workflow" category rather than classic Git power-user audiences alone. +- Publish comparisons that explain why stacked-PR tools do not solve context durability. +- Show complementarity with existing Git tools instead of framing all of them as enemies. + +### Packaging + +- Offer a starter template for common use cases: engineering execution, research, incident response, and cross-functional handoff. +- Highlight that the skill can live in a **separate context repo** or alongside the main product repo depending on governance needs. + +## Bottom Line + +`shared-context-git-skill` should not try to win by out-featured branch automation or commit rewriting. That market already has strong tools. + +It should win by owning a narrower but increasingly important problem: + +**making project context durable, reviewable, and shareable across multiple AI agents and humans using ordinary Git workflows.** + +## Sources + +- `git-branchless`: GitHub README and wiki related tools page +- `Git Town`: docs site and GitHub README +- `git-machete`: GitHub README +- `git-stack`: GitHub README +- `ghstack`: GitHub README +- `Graphite CLI`: product site CLI page +- `Jujutsu (jj)`: docs site and GitHub README +- `Sapling`: docs site and GitHub README From a88d7467b347f01397dc17e701028385e2bde4d3 Mon Sep 17 00:00:00 2001 From: hwanggg Date: Thu, 12 Mar 2026 20:23:26 +0900 Subject: [PATCH 16/23] Add i18n README translations and update docs - Translate README.md from Korean to English as the primary language - Add language selector (EN/KO/JA/ZH) with localized README files - Add missing script entries (check_divergence, compact_timeline) to SKILL.md Co-Authored-By: Claude Opus 4.6 --- README.ja.md | 182 +++++++++++++++++++++++++++++++++++++++++++ README.ko.md | 182 +++++++++++++++++++++++++++++++++++++++++++ README.md | 212 ++++++++++++++++++++++++++------------------------- README.zh.md | 182 +++++++++++++++++++++++++++++++++++++++++++ SKILL.md | 2 + 5 files changed, 657 insertions(+), 103 deletions(-) create mode 100644 README.ja.md create mode 100644 README.ko.md create mode 100644 README.zh.md diff --git a/README.ja.md b/README.ja.md new file mode 100644 index 0000000..a4275db --- /dev/null +++ b/README.ja.md @@ -0,0 +1,182 @@ +# Shared Context Git Skill + +[English](README.md) | [한국어](README.ko.md) | [日本語](README.ja.md) | [中文](README.zh.md) + +複数のAIエージェントがGitベースのリモートリポジトリを通じてプロジェクトのコンテキストを共有するための標準化されたワークフローを提供するスキルです。エージェントはMarkdownファイルで構成された共有メモリを読み書きし、Gitの履歴をレビュートレイルとして活用します。 + +## 主な機能 + +- 標準ドキュメントテンプレートを使った共有コンテキストリポジトリの初期化 +- 読み書き前のローカルクローンの安全な同期 +- 安定した事実、アクティブコンテキスト、意思決定、未解決の質問の記録 +- ブランチベースのコンテキスト更新によるdiffレビューのサポート +- ローカル状態が古くなっているか競合している場合の安全な停止 + +## リポジトリ構造 + +``` +├── SKILL.md # スキル定義とコアルール +├── agents/ +│ ├── openai.yaml # OpenAIエージェント連携設定 +│ ├── claude.yaml # Claude Codeエージェント連携設定 +│ └── codex.yaml # OpenAI Codexエージェント連携設定 +├── scripts/ # 自動化Bashスクリプト +│ ├── bootstrap_repo.sh # テンプレートから初期ドキュメントを生成 +│ ├── check_divergence.sh # コンテキストブランチの乖離と古いブランチを報告 +│ ├── cleanup_branches.sh # 古いマージ済みcontext/*ブランチを削除 +│ ├── compact_timeline.sh # 古いタイムラインエントリをCONTEXT.mdの安定した事実に昇格 +│ ├── sync_context.sh # リモート変更をfetchしてfast-forward +│ ├── prepare_branch.sh # コンテキストブランチを作成 +│ ├── validate_context.sh # ドキュメント構造を検証 +│ └── summarize_context.sh # ステータスサマリーと圧縮ヒントを出力 +├── tests/ # BATSによるリグレッションテスト +│ ├── *.bats # スクリプト別の動作テスト +│ ├── test_helper.bash # 共有テストヘルパー +│ ├── run_tests.sh # フルテストランナー +│ └── lib/bats-core/ # Gitサブモジュールで管理されるBATSランナー +├── assets/ +│ └── templates/ # ドキュメントスタータテンプレート +│ ├── CONTEXT.md # 共有状態ドキュメント +│ ├── TIMELINE.md # 追記専用の変更履歴 +│ ├── HANDOFF.md # 引き継ぎノート(任意) +│ └── POLICY.md # 協働ポリシー(任意) +└── references/ # 詳細リファレンスドキュメント + ├── schema.md # ドキュメント構造仕様 + ├── update-rules.md # 更新ルール + ├── git-workflows.md # Gitワークフローパターン + ├── conflict-policy.md # 競合処理ポリシー + └── handoff-guidelines.md # 引き継ぎガイドライン +``` + +## ドキュメント構成 + +### 必須ドキュメント + +| ドキュメント | 説明 | +|-------------|------| +| `CONTEXT.md` | 現在のプロジェクト状態を要約するコアドキュメント。概要、安定した事実、アクティブコンテキスト、意思決定、未解決の質問セクションで構成。 | +| `TIMELINE.md` | 意味のあるコンテキスト変更の追記専用履歴。 | + +### 任意ドキュメント + +| ドキュメント | 説明 | +|-------------|------| +| `HANDOFF.md` | 次のエージェントへの引き継ぎノート。 | +| `POLICY.md` | チームの協働ポリシーとガイドライン。 | + +## コアルール + +1. **読み優先**: fetchまたはsync後、`CONTEXT.md`と`TIMELINE.md`を読んでから編集します。 +2. **共有メモリはリポジトリに保存**: セッションローカルのノートではなく、リポジトリに共有メモリを保管します。 +3. **ブランチベースの更新を優先**: デフォルトブランチへの直接pushは避け、ブランチを通じて更新します。 +4. **競合の自動解決禁止**: リポジトリがdirtyまたはブランチが乖離している場合は、停止して調整します。 +5. **事実と推論を分離**: 検証済みの事実は安定セクションに、不確実な内容は未解決の質問に記録します。 +6. **意味のあるタイムラインエントリのみ**: 些細な変更ではなく、意味のある変更のみタイムラインに記録します。 + +## 使い方 + +### ワークフロー + +```bash +# 1. リポジトリが存在しない場合は初期化 +scripts/bootstrap_repo.sh + +# 2. ローカルクローンで同期 +scripts/sync_context.sh + +# 3. CONTEXT.md、TIMELINE.md、HANDOFF.md(存在する場合)を読む + +# 4. 更新を共有する場合はブランチを作成 +scripts/prepare_branch.sh --actor --slug + +# 5. Markdownファイルを更新 + +# 6. ドキュメント構造を検証 +scripts/validate_context.sh + +# 7. diffを確認して現在の状態を要約 +scripts/summarize_context.sh + +# 8. 変更が意味のある正確なものであればコミット & プッシュ +``` + +### スクリプトガイド + +| スクリプト | 説明 | +|-----------|------| +| `bootstrap_repo.sh` | テンプレートから初期ドキュメントセットを作成します。 | +| `check_divergence.sh` | `context/*`ブランチのベースブランチからの乖離と古いブランチを報告します。 | +| `cleanup_branches.sh` | マージ済みの古い`context/*`ブランチをローカルおよびオプションで`origin`から削除します。 | +| `compact_timeline.sh` | 古いTIMELINE.mdエントリを圧縮し、適用済み変更をCONTEXT.mdの安定した事実に昇格します。 | +| `sync_context.sh` | リモート変更をfetchし、安全な場合にベースブランチをfast-forwardします。 | +| `prepare_branch.sh` | `context//-`という名前のブランチを作成または切り替えます。 | +| `validate_context.sh` | 必須ファイル、見出し、タイムラインエントリの形式を確認します。 | +| `summarize_context.sh` | コンパクトなステータスサマリーと圧縮ヒントを出力します。 | + +## テスト + +```bash +git submodule update --init --recursive +./tests/run_tests.sh +``` + +- テストはBATSを使用して`scripts/`配下のワークフロースクリプトの正常/エラーパスを検証します。 +- `tests/run_tests.sh`はバンドルされた`tests/lib/bats-core`サブモジュールを使って`.bats`スイート全体を実行します。 + +## 協働モード + +- **ローカル草稿のみ**: 同期、読み込み、ローカル編集、検証後にコミットなしで停止。 +- **ブランチへのコミット & プッシュ**: コンテキストブランチを作成し、ドキュメントを更新、検証、コミット、プッシュ。 +- **PRプロポーザル**: Gitの作業を行い、PR作成はこのスキルの外のプロバイダー固有ツールに委任。 + +## リファレンスドキュメント + +- [schema.md](references/schema.md) — ドキュメント構造仕様 +- [update-rules.md](references/update-rules.md) — 更新ルール +- [git-workflows.md](references/git-workflows.md) — Gitワークフローパターン +- [conflict-policy.md](references/conflict-policy.md) — 競合処理ポリシー +- [handoff-guidelines.md](references/handoff-guidelines.md) — 引き継ぎガイドライン + +## エージェント設定 + +各エージェントフレームワーク向けの設定ファイルが`agents/`ディレクトリに含まれています。 + +| 設定ファイル | エージェント | デフォルトアクター名 | ブランチプレフィックス | +|-------------|-------------|-------------------|-------------------| +| `agents/openai.yaml` | OpenAI Agents | `openai` | `context/openai` | +| `agents/claude.yaml` | Claude Code | `claude` | `context/claude` | +| `agents/codex.yaml` | OpenAI Codex | `codex` | `context/codex` | + +各設定にはスキル参照パス(`skill_paths`)、デフォルトパラメータ(`parameters`)、ワークフローヒント(`workflow_hints`)が含まれています。 + +### OpenAI Agentsの使い方 + +```bash +# 設定ファイルパス: agents/openai.yaml +# ブランチ作成例: +scripts/prepare_branch.sh --actor openai --slug my-topic +``` + +### Claude Codeの使い方 + +```bash +# 設定ファイルパス: agents/claude.yaml +# ブランチ作成例: +scripts/prepare_branch.sh --actor claude --slug my-topic +``` + +### Codexの使い方 + +```bash +# 設定ファイルパス: agents/codex.yaml +# ブランチ作成例: +scripts/prepare_branch.sh --actor codex --slug my-topic +``` + +## 要件 + +- Git CLI +- Bashシェル +- 標準Unixユーティリティ(grep、awkなど) + +外部パッケージやライブラリの依存関係はありません。 diff --git a/README.ko.md b/README.ko.md new file mode 100644 index 0000000..3f8f32b --- /dev/null +++ b/README.ko.md @@ -0,0 +1,182 @@ +# Shared Context Git Skill + +[English](README.md) | [한국어](README.ko.md) | [日本語](README.ja.md) | [中文](README.zh.md) + +여러 AI 에이전트가 Git 기반 원격 저장소를 통해 프로젝트 컨텍스트를 공유할 수 있도록 표준화된 워크플로를 제공하는 스킬입니다. 에이전트는 Markdown 파일로 구성된 공유 메모리를 읽고, 업데이트하며, Git 히스토리를 리뷰 트레일로 활용합니다. + +## 주요 기능 + +- 표준 문서 템플릿으로 공유 컨텍스트 저장소 초기화 +- 읽기/쓰기 전 로컬 클론 안전 동기화 +- 안정적인 사실, 활성 컨텍스트, 의사결정, 미해결 질문 기록 +- 브랜치 기반 컨텍스트 업데이트로 diff 리뷰 지원 +- 로컬 상태가 오래되었거나 충돌 시 안전 중단 + +## 저장소 구조 + +``` +├── SKILL.md # 스킬 정의 및 핵심 규칙 +├── agents/ +│ ├── openai.yaml # OpenAI 에이전트 연동 설정 +│ ├── claude.yaml # Claude Code 에이전트 연동 설정 +│ └── codex.yaml # OpenAI Codex 에이전트 연동 설정 +├── scripts/ # 자동화 Bash 스크립트 +│ ├── bootstrap_repo.sh # 템플릿으로 초기 문서 생성 +│ ├── check_divergence.sh # 컨텍스트 브랜치 분기 및 오래된 브랜치 보고 +│ ├── cleanup_branches.sh # 오래된 병합 완료 컨텍스트 브랜치 정리 +│ ├── compact_timeline.sh # 오래된 타임라인 항목을 CONTEXT.md 안정 사실로 승격 +│ ├── sync_context.sh # 원격 변경 fetch 및 fast-forward +│ ├── prepare_branch.sh # 컨텍스트 브랜치 생성 +│ ├── validate_context.sh # 문서 구조 검증 +│ └── summarize_context.sh # 상태 요약 및 압축 힌트 출력 +├── tests/ # BATS 기반 회귀 테스트 +│ ├── *.bats # 스크립트별 동작 검증 +│ ├── test_helper.bash # 테스트 공통 헬퍼 +│ ├── run_tests.sh # 전체 테스트 실행기 +│ └── lib/bats-core/ # Git submodule로 관리되는 BATS 실행기 +├── assets/ +│ └── templates/ # 문서 시작 템플릿 +│ ├── CONTEXT.md # 공유 상태 문서 +│ ├── TIMELINE.md # 변경 이력 (추가 전용) +│ ├── HANDOFF.md # 인수인계 노트 (선택) +│ └── POLICY.md # 협업 정책 (선택) +└── references/ # 상세 참조 문서 + ├── schema.md # 문서 구조 명세 + ├── update-rules.md # 업데이트 규칙 + ├── git-workflows.md # Git 워크플로 패턴 + ├── conflict-policy.md # 충돌 처리 정책 + └── handoff-guidelines.md # 인수인계 가이드라인 +``` + +## 문서 구성 + +### 필수 문서 + +| 문서 | 설명 | +|------|------| +| `CONTEXT.md` | 현재 프로젝트 상태를 요약하는 핵심 문서. 개요, 안정적 사실, 활성 컨텍스트, 의사결정, 미해결 질문 섹션으로 구성 | +| `TIMELINE.md` | 의미 있는 컨텍스트 변경의 추가 전용(append-only) 이력 | + +### 선택 문서 + +| 문서 | 설명 | +|------|------| +| `HANDOFF.md` | 다음 에이전트를 위한 인수인계 노트 | +| `POLICY.md` | 팀 협업 정책 및 가이드라인 | + +## 핵심 규칙 + +1. **읽기 우선**: fetch 또는 sync 후 `CONTEXT.md`와 `TIMELINE.md`를 먼저 읽은 뒤 편집합니다. +2. **공유 메모리 활용**: 세션 로컬 노트가 아닌 저장소에 공유 메모리를 보관합니다. +3. **브랜치 기반 업데이트**: 기본 브랜치에 직접 push하지 않고 브랜치를 통해 업데이트합니다. +4. **충돌 자동 해결 금지**: 저장소가 dirty하거나 브랜치가 분기된 경우 중단하고 조정합니다. +5. **사실과 추론 분리**: 검증된 사실은 안정 섹션에, 불확실한 내용은 미해결 질문에 기록합니다. +6. **의미 있는 타임라인 항목**: 사소한 변경이 아닌 의미 있는 변경만 타임라인에 기록합니다. + +## 사용 방법 + +### 워크플로 + +```bash +# 1. 저장소가 없으면 초기화 +scripts/bootstrap_repo.sh + +# 2. 로컬 클론에서 동기화 +scripts/sync_context.sh + +# 3. CONTEXT.md, TIMELINE.md, HANDOFF.md(있으면) 읽기 + +# 4. 업데이트 공유가 필요하면 브랜치 생성 +scripts/prepare_branch.sh --actor --slug + +# 5. Markdown 파일 업데이트 + +# 6. 문서 구조 검증 +scripts/validate_context.sh + +# 7. diff 확인 및 상태 요약 +scripts/summarize_context.sh + +# 8. 변경이 의미 있고 정확하면 커밋 & 푸시 +``` + +### 스크립트 가이드 + +| 스크립트 | 설명 | +|---------|------| +| `bootstrap_repo.sh` | 템플릿에서 초기 문서 세트를 생성합니다 | +| `check_divergence.sh` | `context/*` 브랜치의 분기 상태 및 오래된 브랜치를 보고합니다 | +| `cleanup_branches.sh` | 병합된 오래된 `context/*` 브랜치를 로컬 또는 원격에서 정리합니다 | +| `compact_timeline.sh` | 오래된 TIMELINE.md 항목을 CONTEXT.md 안정 사실로 승격하고 정리합니다 | +| `sync_context.sh` | 원격 변경을 fetch하고 기본 브랜치를 안전하게 fast-forward합니다 | +| `prepare_branch.sh` | `context//-` 형식의 브랜치를 생성하거나 전환합니다 | +| `validate_context.sh` | 필수 파일, 제목, 타임라인 항목 형식을 검사합니다 | +| `summarize_context.sh` | 간결한 상태 요약과 압축 힌트를 출력합니다 | + +## 테스트 + +```bash +git submodule update --init --recursive +./tests/run_tests.sh +``` + +- 테스트는 BATS를 사용해 `scripts/` 아래 워크플로 스크립트의 정상/오류 경로를 검증합니다. +- `tests/run_tests.sh`는 포함된 `tests/lib/bats-core` submodule을 사용해 전체 `.bats` 스위트를 실행합니다. + +## 협업 모드 + +- **로컬 초안**: 동기화, 읽기, 로컬 편집, 검증 후 커밋 없이 중단 +- **브랜치 커밋 & 푸시**: 컨텍스트 브랜치 생성, 문서 업데이트, 검증, 커밋, 푸시 +- **PR 제안**: Git 작업 수행 후 PR 생성은 별도 도구에 위임 + +## 참조 문서 + +- [schema.md](references/schema.md) — 문서 구조 명세 +- [update-rules.md](references/update-rules.md) — 업데이트 규칙 +- [git-workflows.md](references/git-workflows.md) — Git 워크플로 패턴 +- [conflict-policy.md](references/conflict-policy.md) — 충돌 처리 정책 +- [handoff-guidelines.md](references/handoff-guidelines.md) — 인수인계 가이드라인 + +## 에이전트별 설정 + +각 에이전트 프레임워크에 맞는 설정 파일이 `agents/` 디렉터리에 포함되어 있습니다. + +| 설정 파일 | 에이전트 | 기본 액터명 | 브랜치 접두사 | +|-----------|---------|------------|--------------| +| `agents/openai.yaml` | OpenAI Agents | `openai` | `context/openai` | +| `agents/claude.yaml` | Claude Code | `claude` | `context/claude` | +| `agents/codex.yaml` | OpenAI Codex | `codex` | `context/codex` | + +각 설정에는 스킬 참조 경로(`skill_paths`), 기본 파라미터(`parameters`), 워크플로 힌트(`workflow_hints`)가 포함되어 있습니다. + +### OpenAI Agents 사용법 + +```bash +# 스킬 설정 파일 경로: agents/openai.yaml +# 기본 브랜치 생성 예시: +scripts/prepare_branch.sh --actor openai --slug my-topic +``` + +### Claude Code 사용법 + +```bash +# 스킬 설정 파일 경로: agents/claude.yaml +# 기본 브랜치 생성 예시: +scripts/prepare_branch.sh --actor claude --slug my-topic +``` + +### Codex 사용법 + +```bash +# 스킬 설정 파일 경로: agents/codex.yaml +# 기본 브랜치 생성 예시: +scripts/prepare_branch.sh --actor codex --slug my-topic +``` + +## 요구 사항 + +- Git CLI +- Bash 셸 +- 표준 Unix 유틸리티 (grep, awk 등) + +외부 패키지나 라이브러리 의존성은 없습니다. diff --git a/README.md b/README.md index 23d0fe6..670eb90 100644 --- a/README.md +++ b/README.md @@ -1,176 +1,182 @@ # Shared Context Git Skill -여러 AI 에이전트가 Git 기반 원격 저장소를 통해 프로젝트 컨텍스트를 공유할 수 있도록 표준화된 워크플로를 제공하는 스킬입니다. 에이전트는 Markdown 파일로 구성된 공유 메모리를 읽고, 업데이트하며, Git 히스토리를 리뷰 트레일로 활용합니다. +[English](README.md) | [한국어](README.ko.md) | [日本語](README.ja.md) | [中文](README.zh.md) -## 주요 기능 +A skill that provides a standardized workflow for multiple AI agents to share project context through a Git-backed remote repository. Agents read and update a shared memory made of Markdown files, using Git history as the review trail. -- 표준 문서 템플릿으로 공유 컨텍스트 저장소 초기화 -- 읽기/쓰기 전 로컬 클론 안전 동기화 -- 안정적인 사실, 활성 컨텍스트, 의사결정, 미해결 질문 기록 -- 브랜치 기반 컨텍스트 업데이트로 diff 리뷰 지원 -- 로컬 상태가 오래되었거나 충돌 시 안전 중단 +## Features -## 저장소 구조 +- Bootstrap a shared context repository with standard document templates +- Safely sync a local clone before reading or writing +- Record stable facts, active context, decisions, open questions, and handoffs +- Support branch-first context updates reviewable as diffs +- Stop safely when the local state is stale, dirty, or diverged + +## Repository Structure ``` -├── SKILL.md # 스킬 정의 및 핵심 규칙 +├── SKILL.md # Skill definition and core rules ├── agents/ -│ ├── openai.yaml # OpenAI 에이전트 연동 설정 -│ ├── claude.yaml # Claude Code 에이전트 연동 설정 -│ └── codex.yaml # OpenAI Codex 에이전트 연동 설정 -├── scripts/ # 자동화 Bash 스크립트 -│ ├── bootstrap_repo.sh # 템플릿으로 초기 문서 생성 -│ ├── cleanup_branches.sh # 오래된 병합 완료 컨텍스트 브랜치 정리 -│ ├── sync_context.sh # 원격 변경 fetch 및 fast-forward -│ ├── prepare_branch.sh # 컨텍스트 브랜치 생성 -│ ├── validate_context.sh # 문서 구조 검증 -│ └── summarize_context.sh # 상태 요약 및 압축 힌트 출력 -├── tests/ # BATS 기반 회귀 테스트 -│ ├── *.bats # 스크립트별 동작 검증 -│ ├── test_helper.bash # 테스트 공통 헬퍼 -│ ├── run_tests.sh # 전체 테스트 실행기 -│ └── lib/bats-core/ # Git submodule로 관리되는 BATS 실행기 +│ ├── openai.yaml # OpenAI agent integration config +│ ├── claude.yaml # Claude Code agent integration config +│ └── codex.yaml # OpenAI Codex agent integration config +├── scripts/ # Automation Bash scripts +│ ├── bootstrap_repo.sh # Create initial documents from templates +│ ├── check_divergence.sh # Report context branch divergence and stale branches +│ ├── cleanup_branches.sh # Remove old merged context/* branches +│ ├── compact_timeline.sh # Promote old timeline entries to CONTEXT.md stable facts +│ ├── sync_context.sh # Fetch remote changes and fast-forward +│ ├── prepare_branch.sh # Create a context branch +│ ├── validate_context.sh # Validate document structure +│ └── summarize_context.sh # Print status summary and compaction hints +├── tests/ # BATS regression tests +│ ├── *.bats # Per-script behavior tests +│ ├── test_helper.bash # Shared test helpers +│ ├── run_tests.sh # Full test runner +│ └── lib/bats-core/ # BATS runner managed as a Git submodule ├── assets/ -│ └── templates/ # 문서 시작 템플릿 -│ ├── CONTEXT.md # 공유 상태 문서 -│ ├── TIMELINE.md # 변경 이력 (추가 전용) -│ ├── HANDOFF.md # 인수인계 노트 (선택) -│ └── POLICY.md # 협업 정책 (선택) -└── references/ # 상세 참조 문서 - ├── schema.md # 문서 구조 명세 - ├── update-rules.md # 업데이트 규칙 - ├── git-workflows.md # Git 워크플로 패턴 - ├── conflict-policy.md # 충돌 처리 정책 - └── handoff-guidelines.md # 인수인계 가이드라인 +│ └── templates/ # Starter document templates +│ ├── CONTEXT.md # Shared state document +│ ├── TIMELINE.md # Append-only change history +│ ├── HANDOFF.md # Handoff notes (optional) +│ └── POLICY.md # Collaboration policy (optional) +└── references/ # Detailed reference documents + ├── schema.md # Document structure specification + ├── update-rules.md # Update rules + ├── git-workflows.md # Git workflow patterns + ├── conflict-policy.md # Conflict handling policy + └── handoff-guidelines.md # Handoff guidelines ``` -## 문서 구성 +## Documents -### 필수 문서 +### Required -| 문서 | 설명 | -|------|------| -| `CONTEXT.md` | 현재 프로젝트 상태를 요약하는 핵심 문서. 개요, 안정적 사실, 활성 컨텍스트, 의사결정, 미해결 질문 섹션으로 구성 | -| `TIMELINE.md` | 의미 있는 컨텍스트 변경의 추가 전용(append-only) 이력 | +| Document | Description | +|----------|-------------| +| `CONTEXT.md` | Core document summarizing current project state. Contains overview, stable facts, active context, decisions, and open questions sections. | +| `TIMELINE.md` | Append-only history of meaningful context changes. | -### 선택 문서 +### Optional -| 문서 | 설명 | -|------|------| -| `HANDOFF.md` | 다음 에이전트를 위한 인수인계 노트 | -| `POLICY.md` | 팀 협업 정책 및 가이드라인 | +| Document | Description | +|----------|-------------| +| `HANDOFF.md` | Handoff notes for the next agent. | +| `POLICY.md` | Team collaboration policies and guidelines. | -## 핵심 규칙 +## Core Rules -1. **읽기 우선**: fetch 또는 sync 후 `CONTEXT.md`와 `TIMELINE.md`를 먼저 읽은 뒤 편집합니다. -2. **공유 메모리 활용**: 세션 로컬 노트가 아닌 저장소에 공유 메모리를 보관합니다. -3. **브랜치 기반 업데이트**: 기본 브랜치에 직접 push하지 않고 브랜치를 통해 업데이트합니다. -4. **충돌 자동 해결 금지**: 저장소가 dirty하거나 브랜치가 분기된 경우 중단하고 조정합니다. -5. **사실과 추론 분리**: 검증된 사실은 안정 섹션에, 불확실한 내용은 미해결 질문에 기록합니다. -6. **의미 있는 타임라인 항목**: 사소한 변경이 아닌 의미 있는 변경만 타임라인에 기록합니다. +1. **Read before write.** Fetch or sync first, then read `CONTEXT.md` and `TIMELINE.md` before editing. +2. **Keep shared memory in the repo**, not only in session-local notes. +3. **Prefer branch-first updates.** Direct pushes to the default branch should be rare and explicitly justified. +4. **Never overwrite conflicts automatically.** If the repo is dirty or the branch has diverged, stop and reconcile. +5. **Separate facts from inferences.** Verified facts belong in the stable sections; uncertainty stays visible in open questions or clearly labeled hypotheses. +6. **Add timeline entries only for meaningful changes.** Avoid noisy logging for every minor thought. -## 사용 방법 +## Usage -### 워크플로 +### Workflow ```bash -# 1. 저장소가 없으면 초기화 +# 1. Bootstrap if the repo does not exist yet scripts/bootstrap_repo.sh -# 2. 로컬 클론에서 동기화 +# 2. Sync in a local clone scripts/sync_context.sh -# 3. CONTEXT.md, TIMELINE.md, HANDOFF.md(있으면) 읽기 +# 3. Read CONTEXT.md, TIMELINE.md, and HANDOFF.md if present -# 4. 업데이트 공유가 필요하면 브랜치 생성 +# 4. Create a branch if you expect to share updates scripts/prepare_branch.sh --actor --slug -# 5. Markdown 파일 업데이트 +# 5. Update the Markdown files -# 6. 문서 구조 검증 +# 6. Validate document structure scripts/validate_context.sh -# 7. diff 확인 및 상태 요약 +# 7. Review the diff and summarize current state scripts/summarize_context.sh -# 8. 변경이 의미 있고 정확하면 커밋 & 푸시 +# 8. Commit and push only when the change is meaningful and accurate ``` -### 스크립트 가이드 +### Script Guide -| 스크립트 | 설명 | -|---------|------| -| `bootstrap_repo.sh` | 템플릿에서 초기 문서 세트를 생성합니다 | -| `cleanup_branches.sh` | 병합된 오래된 `context/*` 브랜치를 로컬 또는 원격에서 정리합니다 | -| `sync_context.sh` | 원격 변경을 fetch하고 기본 브랜치를 안전하게 fast-forward합니다 | -| `prepare_branch.sh` | `context//-` 형식의 브랜치를 생성하거나 전환합니다 | -| `validate_context.sh` | 필수 파일, 제목, 타임라인 항목 형식을 검사합니다 | -| `summarize_context.sh` | 간결한 상태 요약과 압축 힌트를 출력합니다 | +| Script | Description | +|--------|-------------| +| `bootstrap_repo.sh` | Create the initial document set from templates. | +| `check_divergence.sh` | Report divergence of `context/*` branches from the base branch and flag stale branches. | +| `cleanup_branches.sh` | Remove merged, older `context/*` branches locally and optionally on `origin`. | +| `compact_timeline.sh` | Compact old TIMELINE.md entries by promoting applied changes to CONTEXT.md stable facts. | +| `sync_context.sh` | Fetch remote changes and fast-forward the base branch when safe. | +| `prepare_branch.sh` | Create or switch to a branch named `context//-`. | +| `validate_context.sh` | Check required files, headings, and timeline entry shape. | +| `summarize_context.sh` | Print a compact status summary and compaction hints. | -## 테스트 +## Tests ```bash git submodule update --init --recursive ./tests/run_tests.sh ``` -- 테스트는 BATS를 사용해 `scripts/` 아래 워크플로 스크립트의 정상/오류 경로를 검증합니다. -- `tests/run_tests.sh`는 포함된 `tests/lib/bats-core` submodule을 사용해 전체 `.bats` 스위트를 실행합니다. +- Tests use BATS to validate normal and error paths for workflow scripts under `scripts/`. +- `tests/run_tests.sh` runs the full `.bats` suite using the bundled `tests/lib/bats-core` submodule. -## 협업 모드 +## Collaboration Modes -- **로컬 초안**: 동기화, 읽기, 로컬 편집, 검증 후 커밋 없이 중단 -- **브랜치 커밋 & 푸시**: 컨텍스트 브랜치 생성, 문서 업데이트, 검증, 커밋, 푸시 -- **PR 제안**: Git 작업 수행 후 PR 생성은 별도 도구에 위임 +- **Local draft only:** sync, read, edit locally, validate, and stop without committing. +- **Commit to branch and push:** create a context branch, update docs, validate, commit, and push. +- **PR proposal:** do the Git work here, then hand off PR creation to provider-specific tooling outside this skill. -## 참조 문서 +## Reference Documents -- [schema.md](references/schema.md) — 문서 구조 명세 -- [update-rules.md](references/update-rules.md) — 업데이트 규칙 -- [git-workflows.md](references/git-workflows.md) — Git 워크플로 패턴 -- [conflict-policy.md](references/conflict-policy.md) — 충돌 처리 정책 -- [handoff-guidelines.md](references/handoff-guidelines.md) — 인수인계 가이드라인 +- [schema.md](references/schema.md) — Document structure specification +- [update-rules.md](references/update-rules.md) — Update rules +- [git-workflows.md](references/git-workflows.md) — Git workflow patterns +- [conflict-policy.md](references/conflict-policy.md) — Conflict handling policy +- [handoff-guidelines.md](references/handoff-guidelines.md) — Handoff guidelines -## 에이전트별 설정 +## Agent Configuration -각 에이전트 프레임워크에 맞는 설정 파일이 `agents/` 디렉터리에 포함되어 있습니다. +Configuration files for each agent framework are included in the `agents/` directory. -| 설정 파일 | 에이전트 | 기본 액터명 | 브랜치 접두사 | -|-----------|---------|------------|--------------| +| Config file | Agent | Default actor name | Branch prefix | +|-------------|-------|--------------------|---------------| | `agents/openai.yaml` | OpenAI Agents | `openai` | `context/openai` | | `agents/claude.yaml` | Claude Code | `claude` | `context/claude` | | `agents/codex.yaml` | OpenAI Codex | `codex` | `context/codex` | -각 설정에는 스킬 참조 경로(`skill_paths`), 기본 파라미터(`parameters`), 워크플로 힌트(`workflow_hints`)가 포함되어 있습니다. +Each config includes skill reference paths (`skill_paths`), default parameters (`parameters`), and workflow hints (`workflow_hints`). -### OpenAI Agents 사용법 +### OpenAI Agents ```bash -# 스킬 설정 파일 경로: agents/openai.yaml -# 기본 브랜치 생성 예시: +# Config file path: agents/openai.yaml +# Example branch creation: scripts/prepare_branch.sh --actor openai --slug my-topic ``` -### Claude Code 사용법 +### Claude Code ```bash -# 스킬 설정 파일 경로: agents/claude.yaml -# 기본 브랜치 생성 예시: +# Config file path: agents/claude.yaml +# Example branch creation: scripts/prepare_branch.sh --actor claude --slug my-topic ``` -### Codex 사용법 +### Codex ```bash -# 스킬 설정 파일 경로: agents/codex.yaml -# 기본 브랜치 생성 예시: +# Config file path: agents/codex.yaml +# Example branch creation: scripts/prepare_branch.sh --actor codex --slug my-topic ``` -## 요구 사항 +## Requirements - Git CLI -- Bash 셸 -- 표준 Unix 유틸리티 (grep, awk 등) +- Bash shell +- Standard Unix utilities (grep, awk, etc.) -외부 패키지나 라이브러리 의존성은 없습니다. +No external package or library dependencies. diff --git a/README.zh.md b/README.zh.md new file mode 100644 index 0000000..7c8a9fa --- /dev/null +++ b/README.zh.md @@ -0,0 +1,182 @@ +# Shared Context Git Skill + +[English](README.md) | [한국어](README.ko.md) | [日本語](README.ja.md) | [中文](README.zh.md) + +一个为多个AI智能体提供标准化工作流的技能,通过Git托管的远程仓库共享项目上下文。智能体读取和更新由Markdown文件组成的共享记忆,并将Git历史用作审查轨迹。 + +## 主要功能 + +- 使用标准文档模板初始化共享上下文仓库 +- 在读写前安全同步本地克隆 +- 记录稳定事实、活跃上下文、决策、未解决问题和交接内容 +- 支持基于分支的上下文更新,可作为diff进行审查 +- 当本地状态过时、脏或分叉时安全停止 + +## 仓库结构 + +``` +├── SKILL.md # 技能定义和核心规则 +├── agents/ +│ ├── openai.yaml # OpenAI智能体集成配置 +│ ├── claude.yaml # Claude Code智能体集成配置 +│ └── codex.yaml # OpenAI Codex智能体集成配置 +├── scripts/ # 自动化Bash脚本 +│ ├── bootstrap_repo.sh # 从模板创建初始文档 +│ ├── check_divergence.sh # 报告上下文分支的分叉和过期分支 +│ ├── cleanup_branches.sh # 删除旧的已合并context/*分支 +│ ├── compact_timeline.sh # 将旧时间线条目提升为CONTEXT.md中的稳定事实 +│ ├── sync_context.sh # 获取远程变更并快进 +│ ├── prepare_branch.sh # 创建上下文分支 +│ ├── validate_context.sh # 验证文档结构 +│ └── summarize_context.sh # 输出状态摘要和压缩提示 +├── tests/ # 基于BATS的回归测试 +│ ├── *.bats # 每个脚本的行为测试 +│ ├── test_helper.bash # 共享测试辅助程序 +│ ├── run_tests.sh # 完整测试运行器 +│ └── lib/bats-core/ # 以Git子模块管理的BATS运行器 +├── assets/ +│ └── templates/ # 文档起始模板 +│ ├── CONTEXT.md # 共享状态文档 +│ ├── TIMELINE.md # 仅追加的变更历史 +│ ├── HANDOFF.md # 交接笔记(可选) +│ └── POLICY.md # 协作策略(可选) +└── references/ # 详细参考文档 + ├── schema.md # 文档结构规范 + ├── update-rules.md # 更新规则 + ├── git-workflows.md # Git工作流模式 + ├── conflict-policy.md # 冲突处理策略 + └── handoff-guidelines.md # 交接指南 +``` + +## 文档组成 + +### 必需文档 + +| 文档 | 说明 | +|------|------| +| `CONTEXT.md` | 汇总当前项目状态的核心文档,包含概述、稳定事实、活跃上下文、决策和未解决问题等部分。 | +| `TIMELINE.md` | 重要上下文变更的仅追加历史记录。 | + +### 可选文档 + +| 文档 | 说明 | +|------|------| +| `HANDOFF.md` | 给下一个智能体的交接笔记。 | +| `POLICY.md` | 团队协作策略和指南。 | + +## 核心规则 + +1. **先读后写**:先获取或同步,再读取`CONTEXT.md`和`TIMELINE.md`,然后再编辑。 +2. **将共享记忆保存在仓库中**,而不仅仅在会话本地笔记中。 +3. **优先使用基于分支的更新**:应避免直接推送到默认分支,若需直接推送须有明确理由。 +4. **永不自动解决冲突**:若仓库脏或分支已分叉,停止并进行协调。 +5. **区分事实和推断**:已验证的事实放在稳定部分;不确定性在未解决问题中保持可见。 +6. **只为有意义的变更添加时间线条目**:避免为每个细微想法记录噪音日志。 + +## 使用方法 + +### 工作流 + +```bash +# 1. 若仓库不存在则初始化 +scripts/bootstrap_repo.sh + +# 2. 在本地克隆中同步 +scripts/sync_context.sh + +# 3. 读取CONTEXT.md、TIMELINE.md,以及HANDOFF.md(如有) + +# 4. 若需共享更新则创建分支 +scripts/prepare_branch.sh --actor --slug + +# 5. 更新Markdown文件 + +# 6. 验证文档结构 +scripts/validate_context.sh + +# 7. 检查diff并汇总当前状态 +scripts/summarize_context.sh + +# 8. 仅在变更有意义且准确时提交并推送 +``` + +### 脚本指南 + +| 脚本 | 说明 | +|------|------| +| `bootstrap_repo.sh` | 从模板创建初始文档集。 | +| `check_divergence.sh` | 报告`context/*`分支与基础分支的分叉情况,并标记过期分支。 | +| `cleanup_branches.sh` | 从本地和可选的`origin`上删除已合并的旧`context/*`分支。 | +| `compact_timeline.sh` | 压缩旧的TIMELINE.md条目,将已应用的变更提升为CONTEXT.md的稳定事实。 | +| `sync_context.sh` | 获取远程变更,并在安全时快进基础分支。 | +| `prepare_branch.sh` | 创建或切换到名为`context//-`的分支。 | +| `validate_context.sh` | 检查必需文件、标题和时间线条目格式。 | +| `summarize_context.sh` | 输出紧凑的状态摘要和压缩提示。 | + +## 测试 + +```bash +git submodule update --init --recursive +./tests/run_tests.sh +``` + +- 测试使用BATS验证`scripts/`下工作流脚本的正常和错误路径。 +- `tests/run_tests.sh`使用捆绑的`tests/lib/bats-core`子模块运行完整的`.bats`测试套件。 + +## 协作模式 + +- **仅本地草稿**:同步、读取、本地编辑、验证后不提交即停止。 +- **提交到分支并推送**:创建上下文分支,更新文档,验证,提交,推送。 +- **PR提案**:在此完成Git工作,然后将PR创建交给此技能之外的特定提供商工具。 + +## 参考文档 + +- [schema.md](references/schema.md) — 文档结构规范 +- [update-rules.md](references/update-rules.md) — 更新规则 +- [git-workflows.md](references/git-workflows.md) — Git工作流模式 +- [conflict-policy.md](references/conflict-policy.md) — 冲突处理策略 +- [handoff-guidelines.md](references/handoff-guidelines.md) — 交接指南 + +## 智能体配置 + +`agents/`目录中包含适用于各智能体框架的配置文件。 + +| 配置文件 | 智能体 | 默认执行者名称 | 分支前缀 | +|---------|--------|--------------|---------| +| `agents/openai.yaml` | OpenAI Agents | `openai` | `context/openai` | +| `agents/claude.yaml` | Claude Code | `claude` | `context/claude` | +| `agents/codex.yaml` | OpenAI Codex | `codex` | `context/codex` | + +每个配置包含技能参考路径(`skill_paths`)、默认参数(`parameters`)和工作流提示(`workflow_hints`)。 + +### OpenAI Agents用法 + +```bash +# 配置文件路径: agents/openai.yaml +# 分支创建示例: +scripts/prepare_branch.sh --actor openai --slug my-topic +``` + +### Claude Code用法 + +```bash +# 配置文件路径: agents/claude.yaml +# 分支创建示例: +scripts/prepare_branch.sh --actor claude --slug my-topic +``` + +### Codex用法 + +```bash +# 配置文件路径: agents/codex.yaml +# 分支创建示例: +scripts/prepare_branch.sh --actor codex --slug my-topic +``` + +## 系统要求 + +- Git CLI +- Bash shell +- 标准Unix工具(grep、awk等) + +无外部包或库依赖。 diff --git a/SKILL.md b/SKILL.md index fe612da..8392dde 100644 --- a/SKILL.md +++ b/SKILL.md @@ -66,7 +66,9 @@ Use the templates in `assets/templates/` and the detailed rules in the reference ## Script Guide - `scripts/bootstrap_repo.sh`: create the initial document set from templates +- `scripts/check_divergence.sh`: report divergence of `context/*` branches from the base branch and flag stale branches - `scripts/cleanup_branches.sh`: remove merged, older `context/*` branches locally and optionally on `origin` +- `scripts/compact_timeline.sh`: compact old TIMELINE.md entries by promoting applied changes to CONTEXT.md stable facts - `scripts/sync_context.sh`: fetch remote changes and fast-forward the base branch when safe - `scripts/prepare_branch.sh`: create or switch to a branch named `context//-` - `scripts/validate_context.sh`: check required files, headings, and timeline entry shape From 6fe70f4fa1691677ace08df72ec672b3ba173bf2 Mon Sep 17 00:00:00 2001 From: hwanggg Date: Thu, 12 Mar 2026 20:28:15 +0900 Subject: [PATCH 17/23] Fix ShellCheck CI failures (SC2295, SC2034) - cleanup_branches.sh: quote inner expansion in parameter substitution to fix SC2295 - compact_timeline.sh: remove unused variable in_old_entry to fix SC2034 Co-Authored-By: Claude Sonnet 4.6 --- scripts/cleanup_branches.sh | 2 +- scripts/compact_timeline.sh | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/cleanup_branches.sh b/scripts/cleanup_branches.sh index 364768f..1088b66 100755 --- a/scripts/cleanup_branches.sh +++ b/scripts/cleanup_branches.sh @@ -112,7 +112,7 @@ if $delete_remote; then [[ -n "$ref" ]] || continue ref="${ref#"${ref%%[![:space:]]*}"}" # ref looks like "origin/context/actor/date-slug" - branch="${ref#${remote}/}" + branch="${ref#"${remote}"/}" # Never delete remote tracking for current or base branch [[ "$branch" != "$current_branch" ]] || continue diff --git a/scripts/compact_timeline.sh b/scripts/compact_timeline.sh index 3349189..7337915 100755 --- a/scripts/compact_timeline.sh +++ b/scripts/compact_timeline.sh @@ -214,7 +214,6 @@ fi # Step 2: Remove old entries from TIMELINE.md tmpfile="$(mktemp)" entries_section_started=0 -in_old_entry=0 skip_until_next=0 current_heading="" From 86b0b8629e8726ef6ecec032e0d22ac8e9eaf7b1 Mon Sep 17 00:00:00 2001 From: hwanggg Date: Thu, 12 Mar 2026 20:37:39 +0900 Subject: [PATCH 18/23] Add MIT LICENSE file for open-source release Co-Authored-By: Claude Opus 4.6 --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3241968 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 kuil09 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 45f3f8ea3a9425c88fb534208dac6de887fcd893 Mon Sep 17 00:00:00 2001 From: hwanggg Date: Thu, 12 Mar 2026 20:39:19 +0900 Subject: [PATCH 19/23] Add Installation and Quick Start sections to README Adds npx skills add install command and a Quick Start guide to make the skill discoverable and usable from the skills.sh ecosystem. Co-Authored-By: Claude Sonnet 4.6 --- README.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/README.md b/README.md index 670eb90..ad7b80c 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,35 @@ A skill that provides a standardized workflow for multiple AI agents to share project context through a Git-backed remote repository. Agents read and update a shared memory made of Markdown files, using Git history as the review trail. +## Installation + +```bash +npx skills add kuil09/shared-context-git-skill +``` + +After installation, the skill files are available locally. See [Quick Start](#quick-start) below to begin. + +## Quick Start + +```bash +# 1. Bootstrap a new shared context repo (first time only) +scripts/bootstrap_repo.sh + +# 2. Sync before reading or writing +scripts/sync_context.sh + +# 3. Create a context branch for your update +scripts/prepare_branch.sh --actor --slug + +# 4. Edit CONTEXT.md and/or TIMELINE.md + +# 5. Validate, review, and commit +scripts/validate_context.sh +scripts/summarize_context.sh +git add -p && git commit -m "context: " +git push +``` + ## Features - Bootstrap a shared context repository with standard document templates From d25966757287a70f43482912f3ce0cf633515226 Mon Sep 17 00:00:00 2001 From: hwanggg Date: Thu, 12 Mar 2026 20:41:15 +0900 Subject: [PATCH 20/23] Add .claude-plugin/marketplace.json for Claude Code marketplace discovery Enables installation via Claude Code's built-in marketplace in addition to npx skills add. Co-Authored-By: Claude Sonnet 4.6 --- .claude-plugin/marketplace.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .claude-plugin/marketplace.json diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json new file mode 100644 index 0000000..f7ff62e --- /dev/null +++ b/.claude-plugin/marketplace.json @@ -0,0 +1,8 @@ +{ + "metadata": { "pluginRoot": "." }, + "plugins": [{ + "name": "shared-context-git-skill", + "source": "shared-context-git-skill", + "skills": ["./"] + }] +} From 5d8567c51161975d791238327d97c402eff4ffd2 Mon Sep 17 00:00:00 2001 From: hwanggg Date: Thu, 12 Mar 2026 21:07:08 +0900 Subject: [PATCH 21/23] =?UTF-8?q?context:=20TIMELINE.md=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=20=EB=B0=8F=20=EC=BB=A4=EB=B0=8B=20=EB=A9=94=EC=8B=9C?= =?UTF-8?q?=EC=A7=80=20=EC=BB=A8=EB=B2=A4=EC=85=98=20=EB=8F=84=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Trigger: CTO 지시 — TIMELINE.md를 스킬에서 완전히 제거하고 커밋 메시지로 변경 이력 관리 Applied: compact_timeline.sh 삭제, assets/templates/TIMELINE.md 삭제, SKILL.md·schema.md·update-rules.md에서 TIMELINE.md 참조 제거, git-workflows.md에 커밋 메시지 컨벤션 추가, validate_context.sh·summarize_context.sh·bootstrap_repo.sh에서 TIMELINE.md 로직 제거 Unresolved: None --- SKILL.md | 14 +- assets/templates/TIMELINE.md | 20 --- references/git-workflows.md | 24 +++- references/schema.md | 30 ---- references/update-rules.md | 6 +- scripts/bootstrap_repo.sh | 1 - scripts/compact_timeline.sh | 265 ----------------------------------- scripts/summarize_context.sh | 10 -- scripts/validate_context.sh | 92 +----------- 9 files changed, 35 insertions(+), 427 deletions(-) delete mode 100644 assets/templates/TIMELINE.md delete mode 100755 scripts/compact_timeline.sh diff --git a/SKILL.md b/SKILL.md index 8392dde..5af556f 100644 --- a/SKILL.md +++ b/SKILL.md @@ -1,6 +1,12 @@ --- name: shared-context-git-skill description: Use when multiple AI agents need to share durable project context through a Git-backed remote repository. This skill standardizes how agents bootstrap a shared context repo, sync before reading or writing, record facts and decisions in Markdown, prepare branch-first updates, detect conflicts, and leave a clean handoff using only standard Git CLI and bundled Bash helpers. +license: MIT +compatibility: Requires git CLI. Works with any skills-compatible agent. +allowed-tools: "Bash(git:*) Bash(scripts/*) Read" +metadata: + author: kuil09 + version: 1.0.0 --- # Shared Context Git Skill @@ -19,7 +25,7 @@ The shared memory lives in a separate Git repository made of Markdown files. Age ## Core Rules -1. Read before write. Fetch or sync first, then read `CONTEXT.md` and `TIMELINE.md` before editing. +1. Read before write. Fetch or sync first, then read `CONTEXT.md` before editing. 2. Keep shared memory in the repo, not only in session-local notes. 3. Prefer branch-first updates. Direct pushes to the default branch should be rare and explicitly justified. 4. Never overwrite conflicts automatically. If the repo is dirty or the branch has diverged, stop and reconcile. @@ -30,7 +36,7 @@ The shared memory lives in a separate Git repository made of Markdown files. Age 1. If the repo does not exist yet, run `scripts/bootstrap_repo.sh`. 2. In a local clone of the shared context repo, run `scripts/sync_context.sh`. -3. Read `CONTEXT.md`, `TIMELINE.md`, and `HANDOFF.md` if present. +3. Read `CONTEXT.md` and `HANDOFF.md` if present. 4. If you expect to share updates, run `scripts/prepare_branch.sh --actor --slug `. 5. Update the Markdown files using the schema in [references/schema.md](references/schema.md). 6. Run `scripts/validate_context.sh`. @@ -48,7 +54,6 @@ The shared memory lives in a separate Git repository made of Markdown files. Age Required documents: - `CONTEXT.md` -- `TIMELINE.md` Optional documents: @@ -68,10 +73,9 @@ Use the templates in `assets/templates/` and the detailed rules in the reference - `scripts/bootstrap_repo.sh`: create the initial document set from templates - `scripts/check_divergence.sh`: report divergence of `context/*` branches from the base branch and flag stale branches - `scripts/cleanup_branches.sh`: remove merged, older `context/*` branches locally and optionally on `origin` -- `scripts/compact_timeline.sh`: compact old TIMELINE.md entries by promoting applied changes to CONTEXT.md stable facts - `scripts/sync_context.sh`: fetch remote changes and fast-forward the base branch when safe - `scripts/prepare_branch.sh`: create or switch to a branch named `context//-` -- `scripts/validate_context.sh`: check required files, headings, and timeline entry shape +- `scripts/validate_context.sh`: check required files and headings - `scripts/summarize_context.sh`: print a compact status summary and compaction hints If a task needs host-specific PR creation or repo policy enforcement, keep this skill focused on the Git-native workflow and use separate tooling for the provider-specific step. diff --git a/assets/templates/TIMELINE.md b/assets/templates/TIMELINE.md deleted file mode 100644 index 637b1c7..0000000 --- a/assets/templates/TIMELINE.md +++ /dev/null @@ -1,20 +0,0 @@ -# Timeline - -Use this file as an append-only record of meaningful context changes. - -## Entry Template - -```markdown -### 2026-03-12T09:15:00Z | agent-name -- Timestamp: 2026-03-12T09:15:00Z -- Actor: agent-name -- Trigger: What prompted this update -- Applied Changes: - - Summarize the facts, decisions, or context that changed -- Unresolved Items: - - List remaining uncertainty, or write `- None` -``` - -## Entries - -Append new entries below this heading. diff --git a/references/git-workflows.md b/references/git-workflows.md index d83c281..d1c4cd4 100644 --- a/references/git-workflows.md +++ b/references/git-workflows.md @@ -14,6 +14,28 @@ Use this when you are still exploring or the update is not ready to share. 6. Review `git diff` 7. Stop without committing if the notes are still private or provisional +## Commit Message Convention + +Every meaningful context commit must use this format: + +``` +context: <한줄 요약> + +Trigger: <원인> +Applied: <변경 내용> +Unresolved: <미해결 사항 또는 None> +``` + +Example: + +``` +context: API rate limit confirmed at 60 req/min + +Trigger: Load test results from staging run +Applied: Moved rate-limit fact to Stable Facts in CONTEXT.md +Unresolved: None +``` + ## Commit To Branch And Push Use this when the update is meaningful and ready for review or shared use. @@ -23,7 +45,7 @@ Use this when the update is meaningful and ready for review or shared use. 3. Edit the context files 4. Run `scripts/validate_context.sh` 5. Review `git diff` -6. Commit with a focused message +6. Commit using the commit message convention above 7. Push the branch ## PR Proposal diff --git a/references/schema.md b/references/schema.md index 19b60f1..67e6916 100644 --- a/references/schema.md +++ b/references/schema.md @@ -5,7 +5,6 @@ This skill uses a small, predictable document model so agents can read and updat ## Required Files - `CONTEXT.md` -- `TIMELINE.md` ## Optional Files @@ -36,35 +35,6 @@ Section intent: - `Decisions`: accepted decisions with brief rationale when useful - `Open Questions`: unresolved issues, assumptions to validate, and explicit hypotheses -## `TIMELINE.md` - -`TIMELINE.md` is append-only history for meaningful context changes. New entries go at the top or bottom of the `Entries` section as long as the team is consistent. Do not rewrite old entries except to fix clear factual mistakes. - -Required structure: - -```markdown -# Timeline - -## Entry Template -### 2026-03-12T09:15:00Z | agent-name -- Timestamp: 2026-03-12T09:15:00Z -- Actor: agent-name -- Trigger: What caused this update -- Applied Changes: - - Bullet summary -- Unresolved Items: - - Bullet summary or `- None` - -## Entries -``` - -Entry rules: - -- Each real entry uses a `### | ` heading -- Each entry must include `Timestamp`, `Actor`, `Trigger`, `Applied Changes`, and `Unresolved Items` -- `Actor` must be human-readable; tool or model metadata is optional -- `Applied Changes` should summarize the actual change, not just the intent - ## `HANDOFF.md` Use `HANDOFF.md` only for what the next agent needs immediately. It should be short and current. diff --git a/references/update-rules.md b/references/update-rules.md index ab687c4..35a1a28 100644 --- a/references/update-rules.md +++ b/references/update-rules.md @@ -8,8 +8,7 @@ Before any edit: 1. Fetch or sync the repo 2. Read `CONTEXT.md` -3. Read recent `TIMELINE.md` entries -4. Read `HANDOFF.md` if it exists +3. Read `HANDOFF.md` if it exists Do not update the shared context from memory alone when the remote may have changed. @@ -19,7 +18,7 @@ Do not update the shared context from memory alone when the remote may have chan - Put current status, near-term focus, and live observations in `Active Context`. - Put accepted choices in `Decisions`. - Put uncertainty, missing evidence, and competing hypotheses in `Open Questions`. -- Put change history in `TIMELINE.md`, not in `CONTEXT.md`. +- Put change history in commit messages, not in `CONTEXT.md`. ## Facts vs. Inference @@ -63,5 +62,4 @@ Good compaction: Bad compaction: - Deleting unresolved questions because they are uncomfortable -- Rewriting history in `TIMELINE.md` to make the record cleaner - Removing rationale from a decision that future agents will need diff --git a/scripts/bootstrap_repo.sh b/scripts/bootstrap_repo.sh index 6d1e2e4..351dfb6 100755 --- a/scripts/bootstrap_repo.sh +++ b/scripts/bootstrap_repo.sh @@ -70,7 +70,6 @@ copy_template() { } copy_template "CONTEXT.md" "CONTEXT.md" -copy_template "TIMELINE.md" "TIMELINE.md" if [[ "$with_handoff" -eq 1 ]]; then copy_template "HANDOFF.md" "HANDOFF.md" diff --git a/scripts/compact_timeline.sh b/scripts/compact_timeline.sh deleted file mode 100755 index 7337915..0000000 --- a/scripts/compact_timeline.sh +++ /dev/null @@ -1,265 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -usage() { - cat <<'EOF' -Usage: compact_timeline.sh [--repo DIR] [--days N] [--dry-run] [--yes] - -Compact old TIMELINE.md entries by moving their Applied Changes into -CONTEXT.md Stable Facts and removing the original entries. - -Options: - --repo DIR Repository path (default: .) - --days N Compact entries older than N days (default: 30) - --dry-run Show what would change without modifying files - --yes Skip confirmation prompt - -h, --help Show this help message -EOF -} - -repo_dir="." -max_days=30 -dry_run=false -auto_yes=false - -while [[ $# -gt 0 ]]; do - case "$1" in - --repo) - [[ $# -ge 2 ]] || { echo "Missing value for --repo" >&2; exit 1; } - repo_dir="$2" - shift 2 - ;; - --days) - [[ $# -ge 2 ]] || { echo "Missing value for --days" >&2; exit 1; } - max_days="$2" - shift 2 - ;; - --dry-run) - dry_run=true - shift - ;; - --yes) - auto_yes=true - shift - ;; - -h|--help) - usage - exit 0 - ;; - *) - echo "Unknown argument: $1" >&2 - usage >&2 - exit 1 - ;; - esac -done - -if ! [[ "$max_days" =~ ^[0-9]+$ ]]; then - echo "--days must be a non-negative integer" >&2 - exit 1 -fi - -cd "$repo_dir" - -[[ -f "TIMELINE.md" ]] || { echo "Missing TIMELINE.md" >&2; exit 1; } -[[ -f "CONTEXT.md" ]] || { echo "Missing CONTEXT.md" >&2; exit 1; } - -cutoff_epoch=$(( $(date +%s) - max_days * 86400 )) - -# Parse TIMELINE.md entries and classify as old or recent -old_facts=() -old_entry_headings=() -entries_section_started=0 -in_entry=0 -current_heading="" -current_timestamp="" -in_applied=0 -entry_is_old=0 - -classify_timestamp() { - local ts="$1" - # Extract date part: YYYY-MM-DDTHH:MM:SS... - local date_part="${ts%%T*}" - if [[ ! "$date_part" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then - # Cannot parse — treat as recent (keep it) - return 1 - fi - local entry_epoch - if date --version >/dev/null 2>&1; then - # GNU date - entry_epoch=$(date -d "${date_part}T00:00:00" +%s 2>/dev/null) || return 1 - else - # BSD/macOS date — must specify time to avoid using current wall-clock time - entry_epoch=$(date -j -f "%Y-%m-%dT%H:%M:%S" "${date_part}T00:00:00" +%s 2>/dev/null) || return 1 - fi - (( entry_epoch < cutoff_epoch )) -} - -flush_entry() { - if [[ "$in_entry" -eq 1 && "$entry_is_old" -eq 1 ]]; then - old_entry_headings+=("$current_heading") - fi - in_entry=0 - in_applied=0 - entry_is_old=0 - current_heading="" - current_timestamp="" -} - -while IFS= read -r line; do - # Detect ## Entries section - if [[ "$line" == "## Entries" ]]; then - entries_section_started=1 - continue - fi - - [[ "$entries_section_started" -eq 1 ]] || continue - - # Entry heading: ### | - if [[ "$line" =~ ^###\ ]]; then - flush_entry - in_entry=1 - current_heading="$line" - # Extract timestamp from heading - local_ts="${line#\#\#\# }" - local_ts="${local_ts%% |*}" - local_ts="${local_ts%% *}" - current_timestamp="$local_ts" - if classify_timestamp "$current_timestamp"; then - entry_is_old=1 - fi - continue - fi - - [[ "$in_entry" -eq 1 ]] || continue - - # Track Applied Changes section - if [[ "$line" =~ ^-\ Applied\ Changes: ]]; then - in_applied=1 - continue - fi - - # End of Applied Changes when we hit another top-level field - if [[ "$line" =~ ^-\ && ! "$line" =~ ^\ \ ]]; then - in_applied=0 - fi - - # Collect applied change bullets from old entries - if [[ "$in_applied" -eq 1 && "$entry_is_old" -eq 1 && "$line" =~ ^\ \ -\ ]]; then - local_fact="${line# - }" - if [[ -n "$local_fact" ]]; then - old_facts+=("$local_fact") - fi - fi -done < TIMELINE.md - -flush_entry - -if [[ ${#old_entry_headings[@]} -eq 0 ]]; then - echo "No entries older than ${max_days} days to compact." - exit 0 -fi - -echo "Found ${#old_entry_headings[@]} entry/entries older than ${max_days} days." - -if [[ ${#old_facts[@]} -gt 0 ]]; then - echo "Facts to add to Stable Facts:" - for fact in "${old_facts[@]}"; do - echo " - $fact" - done -fi - -echo "" -echo "Entries to remove:" -for heading in "${old_entry_headings[@]}"; do - echo " $heading" -done - -if $dry_run; then - echo "" - echo "[dry-run] No files modified." - exit 0 -fi - -if ! $auto_yes; then - printf '\nProceed? [y/N] ' - read -r answer - if [[ "$answer" != "y" && "$answer" != "Y" ]]; then - echo "Aborted." - exit 0 - fi -fi - -# Step 1: Append facts to CONTEXT.md under ## Stable Facts -if [[ ${#old_facts[@]} -gt 0 ]]; then - tmpfile="$(mktemp)" - found_stable=0 - while IFS= read -r line; do - echo "$line" >> "$tmpfile" - if [[ "$found_stable" -eq 0 && "$line" == "## Stable Facts" ]]; then - found_stable=1 - # Read next line (expect blank or first bullet) - if IFS= read -r next_line; then - echo "$next_line" >> "$tmpfile" - fi - # Append new facts - for fact in "${old_facts[@]}"; do - echo "- $fact" >> "$tmpfile" - done - fi - done < CONTEXT.md - mv "$tmpfile" CONTEXT.md -fi - -# Step 2: Remove old entries from TIMELINE.md -tmpfile="$(mktemp)" -entries_section_started=0 -skip_until_next=0 -current_heading="" - -while IFS= read -r line; do - if [[ "$line" == "## Entries" ]]; then - entries_section_started=1 - echo "$line" >> "$tmpfile" - continue - fi - - if [[ "$entries_section_started" -eq 1 && "$line" =~ ^###\ ]]; then - # Check if this heading is in old_entry_headings - skip_until_next=0 - for old_heading in "${old_entry_headings[@]}"; do - if [[ "$line" == "$old_heading" ]]; then - skip_until_next=1 - break - fi - done - if [[ "$skip_until_next" -eq 1 ]]; then - continue - fi - fi - - if [[ "$skip_until_next" -eq 1 ]]; then - # Skip lines until next ### heading or EOF - if [[ "$line" =~ ^###\ ]]; then - # New entry — check if it's also old - skip_until_next=0 - for old_heading in "${old_entry_headings[@]}"; do - if [[ "$line" == "$old_heading" ]]; then - skip_until_next=1 - break - fi - done - if [[ "$skip_until_next" -eq 1 ]]; then - continue - fi - else - continue - fi - fi - - echo "$line" >> "$tmpfile" -done < TIMELINE.md - -mv "$tmpfile" TIMELINE.md - -echo "Compacted ${#old_entry_headings[@]} entry/entries. ${#old_facts[@]} fact(s) added to Stable Facts." diff --git a/scripts/summarize_context.sh b/scripts/summarize_context.sh index 0182f26..ef32551 100755 --- a/scripts/summarize_context.sh +++ b/scripts/summarize_context.sh @@ -33,7 +33,6 @@ done cd "$repo_dir" [[ -f "CONTEXT.md" ]] || { echo "Missing CONTEXT.md" >&2; exit 1; } -[[ -f "TIMELINE.md" ]] || { echo "Missing TIMELINE.md" >&2; exit 1; } extract_section() { local section_name="$1" @@ -60,16 +59,12 @@ print_section_summary() { echo } -timeline_entries="$(grep -Ec '^### ' TIMELINE.md || true)" context_lines="$(wc -l < CONTEXT.md | tr -d ' ')" -timeline_lines="$(wc -l < TIMELINE.md | tr -d ' ')" duplicate_bullets="$(grep -E '^- ' CONTEXT.md | sort | uniq -d || true)" echo "# Shared Context Summary" echo echo "CONTEXT.md lines: ${context_lines}" -echo "TIMELINE.md lines: ${timeline_lines}" -echo "Timeline entries: ${timeline_entries}" echo print_section_summary "Overview" @@ -86,11 +81,6 @@ if [[ "$context_lines" -gt 120 ]]; then hint_emitted=1 fi -if [[ "$timeline_entries" -gt 20 ]]; then - echo "- TIMELINE.md has more than 20 entries; consider synthesizing recent history into CONTEXT.md." - hint_emitted=1 -fi - if [[ -n "$duplicate_bullets" ]]; then echo "- Duplicate bullets detected in CONTEXT.md:" printf '%s\n' "$duplicate_bullets" | sed 's/^/ /' diff --git a/scripts/validate_context.sh b/scripts/validate_context.sh index b03d5f5..0e67fa3 100755 --- a/scripts/validate_context.sh +++ b/scripts/validate_context.sh @@ -9,7 +9,7 @@ Validate required shared-context files and basic document structure. Options: --repo DIR Path to the repository directory (default: .) - --strict Enable strict validation (ISO 8601 timestamps, non-empty fields) + --strict Enable strict validation (non-empty fields) EOF } @@ -42,7 +42,6 @@ done cd "$repo_dir" [[ -f "CONTEXT.md" ]] || { echo "Missing CONTEXT.md" >&2; exit 1; } -[[ -f "TIMELINE.md" ]] || { echo "Missing TIMELINE.md" >&2; exit 1; } required_context_headings=( "^## Overview$" @@ -59,93 +58,4 @@ for heading in "${required_context_headings[@]}"; do fi done -if ! grep -Eq '^# Timeline$' TIMELINE.md; then - echo "TIMELINE.md is missing '# Timeline'" >&2 - exit 1 -fi - -if ! grep -Eq '^## Entry Template$' TIMELINE.md; then - echo "TIMELINE.md is missing '## Entry Template'" >&2 - exit 1 -fi - -if ! grep -Eq '^## Entries$' TIMELINE.md; then - echo "TIMELINE.md is missing '## Entries'" >&2 - exit 1 -fi - -entry_count=0 -in_entry=0 -has_timestamp=0 -has_actor=0 -has_trigger=0 -has_applied=0 -has_unresolved=0 - -finish_entry() { - if [[ "$in_entry" -eq 1 ]]; then - if [[ "$has_timestamp" -ne 1 || "$has_actor" -ne 1 || "$has_trigger" -ne 1 || "$has_applied" -ne 1 || "$has_unresolved" -ne 1 ]]; then - echo "TIMELINE.md has an entry missing one of: Timestamp, Actor, Trigger, Applied Changes, Unresolved Items" >&2 - exit 1 - fi - fi -} - -while IFS= read -r line; do - if [[ "$line" =~ ^###\ ]]; then - finish_entry - in_entry=1 - entry_count=$((entry_count + 1)) - has_timestamp=0 - has_actor=0 - has_trigger=0 - has_applied=0 - has_unresolved=0 - continue - fi - - [[ "$in_entry" -eq 1 ]] || continue - - if [[ "$line" =~ ^-\ Timestamp: ]]; then - has_timestamp=1 - if [[ "$strict" -eq 1 ]]; then - local_val="${line#- Timestamp:}" - local_val="${local_val#"${local_val%%[![:space:]]*}"}" - if [[ -z "$local_val" || ! "$local_val" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2} ]]; then - echo "TIMELINE.md has an entry with invalid ISO 8601 timestamp: '${local_val}'" >&2 - exit 1 - fi - fi - fi - if [[ "$line" =~ ^-\ Actor: ]]; then - has_actor=1 - if [[ "$strict" -eq 1 ]]; then - local_val="${line#- Actor:}" - local_val="${local_val#"${local_val%%[![:space:]]*}"}" - if [[ -z "$local_val" ]]; then - echo "TIMELINE.md has an entry with empty Actor field" >&2 - exit 1 - fi - fi - fi - if [[ "$line" =~ ^-\ Trigger: ]]; then - has_trigger=1 - if [[ "$strict" -eq 1 ]]; then - local_val="${line#- Trigger:}" - local_val="${local_val#"${local_val%%[![:space:]]*}"}" - if [[ -z "$local_val" ]]; then - echo "TIMELINE.md has an entry with empty Trigger field" >&2 - exit 1 - fi - fi - fi - [[ "$line" =~ ^-\ Applied\ Changes: ]] && has_applied=1 - [[ "$line" =~ ^-\ Unresolved\ Items: ]] && has_unresolved=1 -done < TIMELINE.md - -finish_entry - echo "Shared context structure is valid" -if [[ "$entry_count" -eq 0 ]]; then - echo "No timeline entries yet; template-only state is allowed" -fi From d34ce45f02d99640bcc1e2a0fe533fab27564590 Mon Sep 17 00:00:00 2001 From: hwanggg Date: Thu, 12 Mar 2026 21:11:01 +0900 Subject: [PATCH 22/23] =?UTF-8?q?test:=20TIMELINE.md=20=EC=A0=9C=EA=B1=B0?= =?UTF-8?q?=20=ED=9B=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8=20(NIT-50)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - compact_timeline.bats 삭제 (compact_timeline.sh 제거됨) - validate_context.bats: TIMELINE.md 관련 테스트 케이스 제거 - summarize_context.bats: TIMELINE.md 관련 테스트 케이스 제거 - bootstrap_repo.bats: TIMELINE.md 생성 확인 로직 제거 - test_helper.bash: bootstrap_context에서 TIMELINE.md 복사 제거 Co-Authored-By: Claude Sonnet 4.6 --- tests/bootstrap_repo.bats | 11 +- tests/compact_timeline.bats | 420 ----------------------------------- tests/summarize_context.bats | 31 +-- tests/test_helper.bash | 1 - tests/validate_context.bats | 356 ----------------------------- 5 files changed, 4 insertions(+), 815 deletions(-) delete mode 100644 tests/compact_timeline.bats diff --git a/tests/bootstrap_repo.bats b/tests/bootstrap_repo.bats index 277d52f..991faf1 100644 --- a/tests/bootstrap_repo.bats +++ b/tests/bootstrap_repo.bats @@ -5,11 +5,10 @@ load test_helper setup() { setup_tmpdir; } teardown() { teardown_tmpdir; } -@test "bootstrap creates CONTEXT.md and TIMELINE.md by default" { +@test "bootstrap creates CONTEXT.md by default" { run "$SCRIPTS_DIR/bootstrap_repo.sh" --target "$TEST_TMPDIR" [ "$status" -eq 0 ] [ -f "$TEST_TMPDIR/CONTEXT.md" ] - [ -f "$TEST_TMPDIR/TIMELINE.md" ] [[ "$output" == *"Created"* ]] } @@ -25,11 +24,10 @@ teardown() { teardown_tmpdir; } [ -f "$TEST_TMPDIR/POLICY.md" ] } -@test "bootstrap with all optional flags creates all 4 files" { +@test "bootstrap with all optional flags creates all files" { run "$SCRIPTS_DIR/bootstrap_repo.sh" --target "$TEST_TMPDIR" --with-handoff --with-policy [ "$status" -eq 0 ] [ -f "$TEST_TMPDIR/CONTEXT.md" ] - [ -f "$TEST_TMPDIR/TIMELINE.md" ] [ -f "$TEST_TMPDIR/HANDOFF.md" ] [ -f "$TEST_TMPDIR/POLICY.md" ] } @@ -85,14 +83,11 @@ teardown() { teardown_tmpdir; } [[ "$output" == *"Missing value"* ]] } -@test "bootstrap refuses to overwrite existing CONTEXT.md even when TIMELINE.md is absent" { - # Only place CONTEXT.md in the target — TIMELINE.md is not there yet +@test "bootstrap refuses to overwrite existing CONTEXT.md" { cp "$TEMPLATES_DIR/CONTEXT.md" "$TEST_TMPDIR/CONTEXT.md" run "$SCRIPTS_DIR/bootstrap_repo.sh" --target "$TEST_TMPDIR" [ "$status" -ne 0 ] [[ "$output" == *"Refusing to overwrite"* ]] - # TIMELINE.md should NOT have been created - [ ! -f "$TEST_TMPDIR/TIMELINE.md" ] } diff --git a/tests/compact_timeline.bats b/tests/compact_timeline.bats deleted file mode 100644 index c53e9b1..0000000 --- a/tests/compact_timeline.bats +++ /dev/null @@ -1,420 +0,0 @@ -#!/usr/bin/env bats - -load test_helper - -setup() { setup_tmpdir; } -teardown() { teardown_tmpdir; } - -# Helper: create a TIMELINE.md with entries at specific dates -create_timeline_with_entries() { - local old_date="$1" - local recent_date="$2" - cat > "$TEST_TMPDIR/TIMELINE.md" < "$TEST_TMPDIR/TIMELINE.md" <<'EOF' -# Timeline - -## Entry Template - -## Entries -EOF - - run "$SCRIPTS_DIR/compact_timeline.sh" --repo "$TEST_TMPDIR" - - [ "$status" -ne 0 ] - [[ "$output" == *"Missing CONTEXT.md"* ]] -} - -@test "compact_timeline rejects invalid --days value" { - run "$SCRIPTS_DIR/compact_timeline.sh" --days abc - - [ "$status" -ne 0 ] - [[ "$output" == *"non-negative integer"* ]] -} - -@test "compact_timeline preserves TIMELINE.md structure after compaction" { - bootstrap_context - local old_date recent_date - old_date="$(date -v-60d +%Y-%m-%d 2>/dev/null || date -d '60 days ago' +%Y-%m-%d)" - recent_date="$(date +%Y-%m-%d)" - create_timeline_with_entries "$old_date" "$recent_date" - - run "$SCRIPTS_DIR/compact_timeline.sh" --repo "$TEST_TMPDIR" --days 30 --yes - [ "$status" -eq 0 ] - - # Validate the resulting TIMELINE.md still passes validation - run "$SCRIPTS_DIR/validate_context.sh" --repo "$TEST_TMPDIR" - [ "$status" -eq 0 ] - [[ "$output" == *"Shared context structure is valid"* ]] -} - -@test "compact_timeline rejects unknown argument" { - run "$SCRIPTS_DIR/compact_timeline.sh" --bogus-flag - - [ "$status" -ne 0 ] - [[ "$output" == *"Unknown argument"* ]] -} - -@test "compact_timeline rejects --days with missing value" { - run "$SCRIPTS_DIR/compact_timeline.sh" --days - - [ "$status" -ne 0 ] -} - -@test "compact_timeline with --days 0 compacts all entries" { - bootstrap_context - local today - today="$(date +%Y-%m-%d)" - create_timeline_with_entries "$today" "$today" - - run "$SCRIPTS_DIR/compact_timeline.sh" --repo "$TEST_TMPDIR" --days 0 --dry-run - - [ "$status" -eq 0 ] - [[ "$output" == *"2 entry/entries older than 0 days"* ]] -} - -@test "compact_timeline entry with no Applied Changes bullets compacts cleanly" { - bootstrap_context - local old_date - old_date="$(date -v-60d +%Y-%m-%d 2>/dev/null || date -d '60 days ago' +%Y-%m-%d)" - cat > "$TEST_TMPDIR/TIMELINE.md" < "$TEST_TMPDIR/TIMELINE.md" < "$TEST_TMPDIR/TIMELINE.md" < "$TEST_TMPDIR/TIMELINE.md" <<'EOF' -# Timeline - -Use this file as an append-only record of meaningful context changes. - -## Entry Template - -```markdown -### 2026-03-12T09:15:00Z | agent-name -- Timestamp: 2026-03-12T09:15:00Z -- Actor: agent-name -- Trigger: What prompted this update -- Applied Changes: - - Summarize the facts, decisions, or context that changed -- Unresolved Items: - - List remaining uncertainty, or write `- None` -``` - -## Entries - -### not-a-date | some-bot -- Timestamp: not-a-date -- Actor: some-bot -- Trigger: Unparseable timestamp test -- Applied Changes: - - Some important fact -- Unresolved Items: - - None -EOF - - run "$SCRIPTS_DIR/compact_timeline.sh" --repo "$TEST_TMPDIR" --days 30 --dry-run - - [ "$status" -eq 0 ] - [[ "$output" == *"No entries older than 30 days to compact"* ]] -} diff --git a/tests/summarize_context.bats b/tests/summarize_context.bats index ce8a0af..8d2d1bc 100644 --- a/tests/summarize_context.bats +++ b/tests/summarize_context.bats @@ -49,19 +49,6 @@ EOF [[ "$output" == *"- Same bullet"* ]] } -@test "summarize_context warns when TIMELINE.md has more than 20 entries" { - bootstrap_context - # Append 21 ### entries to TIMELINE.md - for i in $(seq 1 21); do - printf '\n### 2026-03-12T10:%02d:00Z | bot\n- Entry %d\n' "$i" "$i" >> "$TEST_TMPDIR/TIMELINE.md" - done - - run "$SCRIPTS_DIR/summarize_context.sh" --repo "$TEST_TMPDIR" - - [ "$status" -eq 0 ] - [[ "$output" == *"more than 20 entries"* ]] -} - @test "summarize_context warns when CONTEXT.md exceeds 120 lines" { bootstrap_context { @@ -90,23 +77,12 @@ EOF } @test "summarize_context fails when CONTEXT.md is missing" { - cp "$TEMPLATES_DIR/TIMELINE.md" "$TEST_TMPDIR/TIMELINE.md" - run "$SCRIPTS_DIR/summarize_context.sh" --repo "$TEST_TMPDIR" [ "$status" -ne 0 ] [[ "$output" == *"Missing CONTEXT.md"* ]] } -@test "summarize_context fails when TIMELINE.md is missing" { - cp "$TEMPLATES_DIR/CONTEXT.md" "$TEST_TMPDIR/CONTEXT.md" - - run "$SCRIPTS_DIR/summarize_context.sh" --repo "$TEST_TMPDIR" - - [ "$status" -ne 0 ] - [[ "$output" == *"Missing TIMELINE.md"* ]] -} - @test "summarize_context --help prints usage" { run "$SCRIPTS_DIR/summarize_context.sh" --help @@ -151,7 +127,7 @@ EOF [[ "$output" != *"Duplicate bullets"* ]] } -@test "summarize_context emits all three hints when all conditions are met" { +@test "summarize_context emits both hints when conditions are met" { bootstrap_context { echo "# Shared Context" @@ -174,14 +150,9 @@ EOF for i in $(seq 1 15); do echo "- Question $i"; done } > "$TEST_TMPDIR/CONTEXT.md" - for i in $(seq 1 21); do - printf '\n### 2026-03-12T10:%02d:00Z | bot\n- Entry %d\n' "$i" "$i" >> "$TEST_TMPDIR/TIMELINE.md" - done - run "$SCRIPTS_DIR/summarize_context.sh" --repo "$TEST_TMPDIR" [ "$status" -eq 0 ] [[ "$output" == *"longer than 120 lines"* ]] - [[ "$output" == *"more than 20 entries"* ]] [[ "$output" == *"Duplicate bullets detected in CONTEXT.md"* ]] } diff --git a/tests/test_helper.bash b/tests/test_helper.bash index fe1c345..8d02de2 100644 --- a/tests/test_helper.bash +++ b/tests/test_helper.bash @@ -30,7 +30,6 @@ setup_git_repo() { bootstrap_context() { local dir="${1:-$TEST_TMPDIR}" cp "$TEMPLATES_DIR/CONTEXT.md" "$dir/CONTEXT.md" - cp "$TEMPLATES_DIR/TIMELINE.md" "$dir/TIMELINE.md" } setup_tracking_repo() { diff --git a/tests/validate_context.bats b/tests/validate_context.bats index 2a24151..6799c9b 100644 --- a/tests/validate_context.bats +++ b/tests/validate_context.bats @@ -31,80 +31,6 @@ PY [[ "$output" == *"CONTEXT.md is missing required heading matching: ^## Decisions$"* ]] } -@test "validate_context rejects timeline entry missing required fields" { - bootstrap_context - cat > "$TEST_TMPDIR/TIMELINE.md" <<'EOF' -# Timeline - -Use this file as an append-only record of meaningful context changes. - -## Entry Template - -```markdown -### 2026-03-12T09:15:00Z | agent-name -- Timestamp: 2026-03-12T09:15:00Z -- Actor: agent-name -- Trigger: What prompted this update -- Applied Changes: - - Summarize the facts, decisions, or context that changed -- Unresolved Items: - - List remaining uncertainty, or write `- None` -``` - -## Entries - -### 2026-03-12T10:00:00Z | bot -- Timestamp: 2026-03-12T10:00:00Z -- Actor: bot -- Trigger: Test broken entry -- Applied Changes: - - Added an invalid test case -EOF - - run "$SCRIPTS_DIR/validate_context.sh" --repo "$TEST_TMPDIR" - - [ "$status" -ne 0 ] - [[ "$output" == *"TIMELINE.md has an entry missing one of"* ]] -} - -@test "validate_context accepts a complete timeline entry" { - bootstrap_context - cat > "$TEST_TMPDIR/TIMELINE.md" <<'EOF' -# Timeline - -Use this file as an append-only record of meaningful context changes. - -## Entry Template - -```markdown -### 2026-03-12T09:15:00Z | agent-name -- Timestamp: 2026-03-12T09:15:00Z -- Actor: agent-name -- Trigger: What prompted this update -- Applied Changes: - - Summarize the facts, decisions, or context that changed -- Unresolved Items: - - List remaining uncertainty, or write `- None` -``` - -## Entries - -### 2026-03-12T10:00:00Z | bot -- Timestamp: 2026-03-12T10:00:00Z -- Actor: bot -- Trigger: Test valid entry -- Applied Changes: - - Added a valid test case -- Unresolved Items: - - None -EOF - - run "$SCRIPTS_DIR/validate_context.sh" --repo "$TEST_TMPDIR" - - [ "$status" -eq 0 ] - [[ "$output" == *"Shared context structure is valid"* ]] -} - @test "validate_context --help prints usage" { run "$SCRIPTS_DIR/validate_context.sh" --help @@ -112,158 +38,6 @@ EOF [[ "$output" == *"Usage:"* ]] } -@test "validate_context --strict accepts valid ISO 8601 timestamp" { - bootstrap_context - cat > "$TEST_TMPDIR/TIMELINE.md" <<'EOF' -# Timeline - -Use this file as an append-only record of meaningful context changes. - -## Entry Template - -```markdown -### 2026-03-12T09:15:00Z | agent-name -- Timestamp: 2026-03-12T09:15:00Z -- Actor: agent-name -- Trigger: What prompted this update -- Applied Changes: - - Summarize the facts, decisions, or context that changed -- Unresolved Items: - - List remaining uncertainty, or write `- None` -``` - -## Entries - -### 2026-03-12T10:00:00Z | bot -- Timestamp: 2026-03-12T10:00:00Z -- Actor: bot -- Trigger: Test valid entry -- Applied Changes: - - Added a valid test case -- Unresolved Items: - - None -EOF - - run "$SCRIPTS_DIR/validate_context.sh" --repo "$TEST_TMPDIR" --strict - - [ "$status" -eq 0 ] - [[ "$output" == *"Shared context structure is valid"* ]] -} - -@test "validate_context --strict rejects invalid timestamp format" { - bootstrap_context - cat > "$TEST_TMPDIR/TIMELINE.md" <<'EOF' -# Timeline - -Use this file as an append-only record of meaningful context changes. - -## Entry Template - -```markdown -### 2026-03-12T09:15:00Z | agent-name -- Timestamp: 2026-03-12T09:15:00Z -- Actor: agent-name -- Trigger: What prompted this update -- Applied Changes: - - Summarize the facts, decisions, or context that changed -- Unresolved Items: - - List remaining uncertainty, or write `- None` -``` - -## Entries - -### bad-timestamp | bot -- Timestamp: not-a-date -- Actor: bot -- Trigger: Test invalid timestamp -- Applied Changes: - - Added a test case -- Unresolved Items: - - None -EOF - - run "$SCRIPTS_DIR/validate_context.sh" --repo "$TEST_TMPDIR" --strict - - [ "$status" -ne 0 ] - [[ "$output" == *"invalid ISO 8601 timestamp"* ]] -} - -@test "validate_context --strict rejects empty Actor field" { - bootstrap_context - cat > "$TEST_TMPDIR/TIMELINE.md" <<'EOF' -# Timeline - -Use this file as an append-only record of meaningful context changes. - -## Entry Template - -```markdown -### 2026-03-12T09:15:00Z | agent-name -- Timestamp: 2026-03-12T09:15:00Z -- Actor: agent-name -- Trigger: What prompted this update -- Applied Changes: - - Summarize the facts, decisions, or context that changed -- Unresolved Items: - - List remaining uncertainty, or write `- None` -``` - -## Entries - -### 2026-03-12T10:00:00Z | -- Timestamp: 2026-03-12T10:00:00Z -- Actor: -- Trigger: Test empty actor -- Applied Changes: - - Added a test case -- Unresolved Items: - - None -EOF - - run "$SCRIPTS_DIR/validate_context.sh" --repo "$TEST_TMPDIR" --strict - - [ "$status" -ne 0 ] - [[ "$output" == *"empty Actor field"* ]] -} - -@test "validate_context --strict rejects empty Trigger field" { - bootstrap_context - cat > "$TEST_TMPDIR/TIMELINE.md" <<'EOF' -# Timeline - -Use this file as an append-only record of meaningful context changes. - -## Entry Template - -```markdown -### 2026-03-12T09:15:00Z | agent-name -- Timestamp: 2026-03-12T09:15:00Z -- Actor: agent-name -- Trigger: What prompted this update -- Applied Changes: - - Summarize the facts, decisions, or context that changed -- Unresolved Items: - - List remaining uncertainty, or write `- None` -``` - -## Entries - -### 2026-03-12T10:00:00Z | bot -- Timestamp: 2026-03-12T10:00:00Z -- Actor: bot -- Trigger: -- Applied Changes: - - Added a test case -- Unresolved Items: - - None -EOF - - run "$SCRIPTS_DIR/validate_context.sh" --repo "$TEST_TMPDIR" --strict - - [ "$status" -ne 0 ] - [[ "$output" == *"empty Trigger field"* ]] -} - @test "validate_context rejects unknown argument" { run "$SCRIPTS_DIR/validate_context.sh" --unknown-flag @@ -273,7 +47,6 @@ EOF @test "validate_context fails when CONTEXT.md is missing" { setup_tmpdir - cp "$TEMPLATES_DIR/TIMELINE.md" "$TEST_TMPDIR/TIMELINE.md" run "$SCRIPTS_DIR/validate_context.sh" --repo "$TEST_TMPDIR" @@ -281,39 +54,6 @@ EOF [[ "$output" == *"Missing CONTEXT.md"* ]] } -@test "validate_context fails when TIMELINE.md is missing" { - setup_tmpdir - cp "$TEMPLATES_DIR/CONTEXT.md" "$TEST_TMPDIR/CONTEXT.md" - - run "$SCRIPTS_DIR/validate_context.sh" --repo "$TEST_TMPDIR" - - [ "$status" -ne 0 ] - [[ "$output" == *"Missing TIMELINE.md"* ]] -} - -@test "validate_context fails when TIMELINE.md is missing '# Timeline' heading" { - bootstrap_context - # Remove the top-level heading - sed 's/^# Timeline$//' "$TEST_TMPDIR/TIMELINE.md" > "$TEST_TMPDIR/TIMELINE.md.tmp" - mv "$TEST_TMPDIR/TIMELINE.md.tmp" "$TEST_TMPDIR/TIMELINE.md" - - run "$SCRIPTS_DIR/validate_context.sh" --repo "$TEST_TMPDIR" - - [ "$status" -ne 0 ] - [[ "$output" == *"missing '# Timeline'"* ]] -} - -@test "validate_context fails when TIMELINE.md is missing '## Entries' section" { - bootstrap_context - sed 's/^## Entries$//' "$TEST_TMPDIR/TIMELINE.md" > "$TEST_TMPDIR/TIMELINE.md.tmp" - mv "$TEST_TMPDIR/TIMELINE.md.tmp" "$TEST_TMPDIR/TIMELINE.md" - - run "$SCRIPTS_DIR/validate_context.sh" --repo "$TEST_TMPDIR" - - [ "$status" -ne 0 ] - [[ "$output" == *"missing '## Entries'"* ]] -} - @test "validate_context rejects --repo with missing value" { run "$SCRIPTS_DIR/validate_context.sh" --repo @@ -338,17 +78,6 @@ PY [[ "$output" == *"CONTEXT.md is missing required heading matching: ^## Stable Facts$"* ]] } -@test "validate_context fails when TIMELINE.md is missing Entry Template section" { - bootstrap_context - sed 's/^## Entry Template$//' "$TEST_TMPDIR/TIMELINE.md" > "$TEST_TMPDIR/TIMELINE.md.tmp" - mv "$TEST_TMPDIR/TIMELINE.md.tmp" "$TEST_TMPDIR/TIMELINE.md" - - run "$SCRIPTS_DIR/validate_context.sh" --repo "$TEST_TMPDIR" - - [ "$status" -ne 0 ] - [[ "$output" == *"missing '## Entry Template'"* ]] -} - @test "validate_context passes when CONTEXT.md has duplicate required heading" { bootstrap_context # Add a second ## Decisions heading — grep -Eq still finds the pattern, so should pass @@ -359,88 +88,3 @@ PY [ "$status" -eq 0 ] [[ "$output" == *"Shared context structure is valid"* ]] } - -@test "validate_context --strict fails on invalid entry even when a valid entry precedes it" { - bootstrap_context - cat > "$TEST_TMPDIR/TIMELINE.md" <<'EOF' -# Timeline - -Use this file as an append-only record of meaningful context changes. - -## Entry Template - -```markdown -### 2026-03-12T09:15:00Z | agent-name -- Timestamp: 2026-03-12T09:15:00Z -- Actor: agent-name -- Trigger: What prompted this update -- Applied Changes: - - Summarize the facts, decisions, or context that changed -- Unresolved Items: - - List remaining uncertainty, or write `- None` -``` - -## Entries - -### 2026-03-12T10:00:00Z | valid-bot -- Timestamp: 2026-03-12T10:00:00Z -- Actor: valid-bot -- Trigger: Valid entry -- Applied Changes: - - Something valid -- Unresolved Items: - - None - -### bad-timestamp | invalid-bot -- Timestamp: not-a-date -- Actor: invalid-bot -- Trigger: Invalid entry -- Applied Changes: - - Something invalid -- Unresolved Items: - - None -EOF - - run "$SCRIPTS_DIR/validate_context.sh" --repo "$TEST_TMPDIR" --strict - - [ "$status" -ne 0 ] - [[ "$output" == *"invalid ISO 8601 timestamp"* ]] -} - -@test "validate_context without --strict passes invalid timestamp (backward compat)" { - bootstrap_context - cat > "$TEST_TMPDIR/TIMELINE.md" <<'EOF' -# Timeline - -Use this file as an append-only record of meaningful context changes. - -## Entry Template - -```markdown -### 2026-03-12T09:15:00Z | agent-name -- Timestamp: 2026-03-12T09:15:00Z -- Actor: agent-name -- Trigger: What prompted this update -- Applied Changes: - - Summarize the facts, decisions, or context that changed -- Unresolved Items: - - List remaining uncertainty, or write `- None` -``` - -## Entries - -### bad-timestamp | bot -- Timestamp: not-a-date -- Actor: bot -- Trigger: Test backward compat -- Applied Changes: - - Added a test case -- Unresolved Items: - - None -EOF - - run "$SCRIPTS_DIR/validate_context.sh" --repo "$TEST_TMPDIR" - - [ "$status" -eq 0 ] - [[ "$output" == *"Shared context structure is valid"* ]] -} From 93c74ed345c6ec742bc884c8de1c9019a51ba514 Mon Sep 17 00:00:00 2001 From: hwanggg Date: Thu, 12 Mar 2026 21:13:56 +0900 Subject: [PATCH 23/23] context: Remove remaining TIMELINE.md references from docs, configs, and READMEs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Trigger: NIT-51 final review found TIMELINE.md references still present in README (en/ko/ja/zh), agent YAML configs, SKILL.md, and reference docs after the core implementation (NIT-49) and test updates (NIT-50). Applied: - Removed TIMELINE.md from all README document tables, repo structure trees, core rules, and workflow instructions across all 4 languages - Updated agent YAML workflow_hints to reference commit message conventions - Updated SKILL.md rule 6 to reflect commit-based change history - Updated references (handoff-guidelines, cto-cmo-sync, conflict-policy) Unresolved: None — competitive-tools-analysis.md uses "timeline" generically, not as a TIMELINE.md reference, so left as-is. Co-Authored-By: Claude Opus 4.6 --- README.ja.md | 12 ++++-------- README.ko.md | 12 ++++-------- README.md | 14 +++++--------- README.zh.md | 12 ++++-------- SKILL.md | 2 +- agents/claude.yaml | 4 ++-- agents/codex.yaml | 4 ++-- agents/openai.yaml | 4 ++-- references/conflict-policy.md | 2 +- references/cto-cmo-sync-process.md | 1 - references/handoff-guidelines.md | 2 +- 11 files changed, 26 insertions(+), 43 deletions(-) diff --git a/README.ja.md b/README.ja.md index a4275db..a13863b 100644 --- a/README.ja.md +++ b/README.ja.md @@ -24,7 +24,6 @@ │ ├── bootstrap_repo.sh # テンプレートから初期ドキュメントを生成 │ ├── check_divergence.sh # コンテキストブランチの乖離と古いブランチを報告 │ ├── cleanup_branches.sh # 古いマージ済みcontext/*ブランチを削除 -│ ├── compact_timeline.sh # 古いタイムラインエントリをCONTEXT.mdの安定した事実に昇格 │ ├── sync_context.sh # リモート変更をfetchしてfast-forward │ ├── prepare_branch.sh # コンテキストブランチを作成 │ ├── validate_context.sh # ドキュメント構造を検証 @@ -37,7 +36,6 @@ ├── assets/ │ └── templates/ # ドキュメントスタータテンプレート │ ├── CONTEXT.md # 共有状態ドキュメント -│ ├── TIMELINE.md # 追記専用の変更履歴 │ ├── HANDOFF.md # 引き継ぎノート(任意) │ └── POLICY.md # 協働ポリシー(任意) └── references/ # 詳細リファレンスドキュメント @@ -55,7 +53,6 @@ | ドキュメント | 説明 | |-------------|------| | `CONTEXT.md` | 現在のプロジェクト状態を要約するコアドキュメント。概要、安定した事実、アクティブコンテキスト、意思決定、未解決の質問セクションで構成。 | -| `TIMELINE.md` | 意味のあるコンテキスト変更の追記専用履歴。 | ### 任意ドキュメント @@ -66,12 +63,12 @@ ## コアルール -1. **読み優先**: fetchまたはsync後、`CONTEXT.md`と`TIMELINE.md`を読んでから編集します。 +1. **読み優先**: fetchまたはsync後、`CONTEXT.md`を読んでから編集します。 2. **共有メモリはリポジトリに保存**: セッションローカルのノートではなく、リポジトリに共有メモリを保管します。 3. **ブランチベースの更新を優先**: デフォルトブランチへの直接pushは避け、ブランチを通じて更新します。 4. **競合の自動解決禁止**: リポジトリがdirtyまたはブランチが乖離している場合は、停止して調整します。 5. **事実と推論を分離**: 検証済みの事実は安定セクションに、不確実な内容は未解決の質問に記録します。 -6. **意味のあるタイムラインエントリのみ**: 些細な変更ではなく、意味のある変更のみタイムラインに記録します。 +6. **変更履歴はコミットメッセージに記録**: 別ファイルではなく、構造化されたコミットメッセージ(Trigger/Applied/Unresolved)を使用します。 ## 使い方 @@ -84,7 +81,7 @@ scripts/bootstrap_repo.sh # 2. ローカルクローンで同期 scripts/sync_context.sh -# 3. CONTEXT.md、TIMELINE.md、HANDOFF.md(存在する場合)を読む +# 3. CONTEXT.md、HANDOFF.md(存在する場合)を読む # 4. 更新を共有する場合はブランチを作成 scripts/prepare_branch.sh --actor --slug @@ -107,10 +104,9 @@ scripts/summarize_context.sh | `bootstrap_repo.sh` | テンプレートから初期ドキュメントセットを作成します。 | | `check_divergence.sh` | `context/*`ブランチのベースブランチからの乖離と古いブランチを報告します。 | | `cleanup_branches.sh` | マージ済みの古い`context/*`ブランチをローカルおよびオプションで`origin`から削除します。 | -| `compact_timeline.sh` | 古いTIMELINE.mdエントリを圧縮し、適用済み変更をCONTEXT.mdの安定した事実に昇格します。 | | `sync_context.sh` | リモート変更をfetchし、安全な場合にベースブランチをfast-forwardします。 | | `prepare_branch.sh` | `context//-`という名前のブランチを作成または切り替えます。 | -| `validate_context.sh` | 必須ファイル、見出し、タイムラインエントリの形式を確認します。 | +| `validate_context.sh` | 必須ファイルと見出しを確認します。 | | `summarize_context.sh` | コンパクトなステータスサマリーと圧縮ヒントを出力します。 | ## テスト diff --git a/README.ko.md b/README.ko.md index 3f8f32b..8edf8d0 100644 --- a/README.ko.md +++ b/README.ko.md @@ -24,7 +24,6 @@ │ ├── bootstrap_repo.sh # 템플릿으로 초기 문서 생성 │ ├── check_divergence.sh # 컨텍스트 브랜치 분기 및 오래된 브랜치 보고 │ ├── cleanup_branches.sh # 오래된 병합 완료 컨텍스트 브랜치 정리 -│ ├── compact_timeline.sh # 오래된 타임라인 항목을 CONTEXT.md 안정 사실로 승격 │ ├── sync_context.sh # 원격 변경 fetch 및 fast-forward │ ├── prepare_branch.sh # 컨텍스트 브랜치 생성 │ ├── validate_context.sh # 문서 구조 검증 @@ -37,7 +36,6 @@ ├── assets/ │ └── templates/ # 문서 시작 템플릿 │ ├── CONTEXT.md # 공유 상태 문서 -│ ├── TIMELINE.md # 변경 이력 (추가 전용) │ ├── HANDOFF.md # 인수인계 노트 (선택) │ └── POLICY.md # 협업 정책 (선택) └── references/ # 상세 참조 문서 @@ -55,7 +53,6 @@ | 문서 | 설명 | |------|------| | `CONTEXT.md` | 현재 프로젝트 상태를 요약하는 핵심 문서. 개요, 안정적 사실, 활성 컨텍스트, 의사결정, 미해결 질문 섹션으로 구성 | -| `TIMELINE.md` | 의미 있는 컨텍스트 변경의 추가 전용(append-only) 이력 | ### 선택 문서 @@ -66,12 +63,12 @@ ## 핵심 규칙 -1. **읽기 우선**: fetch 또는 sync 후 `CONTEXT.md`와 `TIMELINE.md`를 먼저 읽은 뒤 편집합니다. +1. **읽기 우선**: fetch 또는 sync 후 `CONTEXT.md`를 먼저 읽은 뒤 편집합니다. 2. **공유 메모리 활용**: 세션 로컬 노트가 아닌 저장소에 공유 메모리를 보관합니다. 3. **브랜치 기반 업데이트**: 기본 브랜치에 직접 push하지 않고 브랜치를 통해 업데이트합니다. 4. **충돌 자동 해결 금지**: 저장소가 dirty하거나 브랜치가 분기된 경우 중단하고 조정합니다. 5. **사실과 추론 분리**: 검증된 사실은 안정 섹션에, 불확실한 내용은 미해결 질문에 기록합니다. -6. **의미 있는 타임라인 항목**: 사소한 변경이 아닌 의미 있는 변경만 타임라인에 기록합니다. +6. **변경 이력은 커밋 메시지에 기록**: 별도 파일이 아닌 구조화된 커밋 메시지(Trigger/Applied/Unresolved)를 사용합니다. ## 사용 방법 @@ -84,7 +81,7 @@ scripts/bootstrap_repo.sh # 2. 로컬 클론에서 동기화 scripts/sync_context.sh -# 3. CONTEXT.md, TIMELINE.md, HANDOFF.md(있으면) 읽기 +# 3. CONTEXT.md, HANDOFF.md(있으면) 읽기 # 4. 업데이트 공유가 필요하면 브랜치 생성 scripts/prepare_branch.sh --actor --slug @@ -107,10 +104,9 @@ scripts/summarize_context.sh | `bootstrap_repo.sh` | 템플릿에서 초기 문서 세트를 생성합니다 | | `check_divergence.sh` | `context/*` 브랜치의 분기 상태 및 오래된 브랜치를 보고합니다 | | `cleanup_branches.sh` | 병합된 오래된 `context/*` 브랜치를 로컬 또는 원격에서 정리합니다 | -| `compact_timeline.sh` | 오래된 TIMELINE.md 항목을 CONTEXT.md 안정 사실로 승격하고 정리합니다 | | `sync_context.sh` | 원격 변경을 fetch하고 기본 브랜치를 안전하게 fast-forward합니다 | | `prepare_branch.sh` | `context//-` 형식의 브랜치를 생성하거나 전환합니다 | -| `validate_context.sh` | 필수 파일, 제목, 타임라인 항목 형식을 검사합니다 | +| `validate_context.sh` | 필수 파일과 제목을 검사합니다 | | `summarize_context.sh` | 간결한 상태 요약과 압축 힌트를 출력합니다 | ## 테스트 diff --git a/README.md b/README.md index ad7b80c..fd97ac2 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ scripts/sync_context.sh # 3. Create a context branch for your update scripts/prepare_branch.sh --actor --slug -# 4. Edit CONTEXT.md and/or TIMELINE.md +# 4. Edit CONTEXT.md # 5. Validate, review, and commit scripts/validate_context.sh @@ -53,7 +53,6 @@ git push │ ├── bootstrap_repo.sh # Create initial documents from templates │ ├── check_divergence.sh # Report context branch divergence and stale branches │ ├── cleanup_branches.sh # Remove old merged context/* branches -│ ├── compact_timeline.sh # Promote old timeline entries to CONTEXT.md stable facts │ ├── sync_context.sh # Fetch remote changes and fast-forward │ ├── prepare_branch.sh # Create a context branch │ ├── validate_context.sh # Validate document structure @@ -66,7 +65,6 @@ git push ├── assets/ │ └── templates/ # Starter document templates │ ├── CONTEXT.md # Shared state document -│ ├── TIMELINE.md # Append-only change history │ ├── HANDOFF.md # Handoff notes (optional) │ └── POLICY.md # Collaboration policy (optional) └── references/ # Detailed reference documents @@ -84,7 +82,6 @@ git push | Document | Description | |----------|-------------| | `CONTEXT.md` | Core document summarizing current project state. Contains overview, stable facts, active context, decisions, and open questions sections. | -| `TIMELINE.md` | Append-only history of meaningful context changes. | ### Optional @@ -95,12 +92,12 @@ git push ## Core Rules -1. **Read before write.** Fetch or sync first, then read `CONTEXT.md` and `TIMELINE.md` before editing. +1. **Read before write.** Fetch or sync first, then read `CONTEXT.md` before editing. 2. **Keep shared memory in the repo**, not only in session-local notes. 3. **Prefer branch-first updates.** Direct pushes to the default branch should be rare and explicitly justified. 4. **Never overwrite conflicts automatically.** If the repo is dirty or the branch has diverged, stop and reconcile. 5. **Separate facts from inferences.** Verified facts belong in the stable sections; uncertainty stays visible in open questions or clearly labeled hypotheses. -6. **Add timeline entries only for meaningful changes.** Avoid noisy logging for every minor thought. +6. **Record change history in commit messages**, not in separate files. Use structured commit messages with Trigger/Applied/Unresolved fields. ## Usage @@ -113,7 +110,7 @@ scripts/bootstrap_repo.sh # 2. Sync in a local clone scripts/sync_context.sh -# 3. Read CONTEXT.md, TIMELINE.md, and HANDOFF.md if present +# 3. Read CONTEXT.md and HANDOFF.md if present # 4. Create a branch if you expect to share updates scripts/prepare_branch.sh --actor --slug @@ -136,10 +133,9 @@ scripts/summarize_context.sh | `bootstrap_repo.sh` | Create the initial document set from templates. | | `check_divergence.sh` | Report divergence of `context/*` branches from the base branch and flag stale branches. | | `cleanup_branches.sh` | Remove merged, older `context/*` branches locally and optionally on `origin`. | -| `compact_timeline.sh` | Compact old TIMELINE.md entries by promoting applied changes to CONTEXT.md stable facts. | | `sync_context.sh` | Fetch remote changes and fast-forward the base branch when safe. | | `prepare_branch.sh` | Create or switch to a branch named `context//-`. | -| `validate_context.sh` | Check required files, headings, and timeline entry shape. | +| `validate_context.sh` | Check required files and headings. | | `summarize_context.sh` | Print a compact status summary and compaction hints. | ## Tests diff --git a/README.zh.md b/README.zh.md index 7c8a9fa..8d80547 100644 --- a/README.zh.md +++ b/README.zh.md @@ -24,7 +24,6 @@ │ ├── bootstrap_repo.sh # 从模板创建初始文档 │ ├── check_divergence.sh # 报告上下文分支的分叉和过期分支 │ ├── cleanup_branches.sh # 删除旧的已合并context/*分支 -│ ├── compact_timeline.sh # 将旧时间线条目提升为CONTEXT.md中的稳定事实 │ ├── sync_context.sh # 获取远程变更并快进 │ ├── prepare_branch.sh # 创建上下文分支 │ ├── validate_context.sh # 验证文档结构 @@ -37,7 +36,6 @@ ├── assets/ │ └── templates/ # 文档起始模板 │ ├── CONTEXT.md # 共享状态文档 -│ ├── TIMELINE.md # 仅追加的变更历史 │ ├── HANDOFF.md # 交接笔记(可选) │ └── POLICY.md # 协作策略(可选) └── references/ # 详细参考文档 @@ -55,7 +53,6 @@ | 文档 | 说明 | |------|------| | `CONTEXT.md` | 汇总当前项目状态的核心文档,包含概述、稳定事实、活跃上下文、决策和未解决问题等部分。 | -| `TIMELINE.md` | 重要上下文变更的仅追加历史记录。 | ### 可选文档 @@ -66,12 +63,12 @@ ## 核心规则 -1. **先读后写**:先获取或同步,再读取`CONTEXT.md`和`TIMELINE.md`,然后再编辑。 +1. **先读后写**:先获取或同步,再读取`CONTEXT.md`,然后再编辑。 2. **将共享记忆保存在仓库中**,而不仅仅在会话本地笔记中。 3. **优先使用基于分支的更新**:应避免直接推送到默认分支,若需直接推送须有明确理由。 4. **永不自动解决冲突**:若仓库脏或分支已分叉,停止并进行协调。 5. **区分事实和推断**:已验证的事实放在稳定部分;不确定性在未解决问题中保持可见。 -6. **只为有意义的变更添加时间线条目**:避免为每个细微想法记录噪音日志。 +6. **在提交消息中记录变更历史**:使用结构化提交消息(Trigger/Applied/Unresolved),而非单独的文件。 ## 使用方法 @@ -84,7 +81,7 @@ scripts/bootstrap_repo.sh # 2. 在本地克隆中同步 scripts/sync_context.sh -# 3. 读取CONTEXT.md、TIMELINE.md,以及HANDOFF.md(如有) +# 3. 读取CONTEXT.md,以及HANDOFF.md(如有) # 4. 若需共享更新则创建分支 scripts/prepare_branch.sh --actor --slug @@ -107,10 +104,9 @@ scripts/summarize_context.sh | `bootstrap_repo.sh` | 从模板创建初始文档集。 | | `check_divergence.sh` | 报告`context/*`分支与基础分支的分叉情况,并标记过期分支。 | | `cleanup_branches.sh` | 从本地和可选的`origin`上删除已合并的旧`context/*`分支。 | -| `compact_timeline.sh` | 压缩旧的TIMELINE.md条目,将已应用的变更提升为CONTEXT.md的稳定事实。 | | `sync_context.sh` | 获取远程变更,并在安全时快进基础分支。 | | `prepare_branch.sh` | 创建或切换到名为`context//-`的分支。 | -| `validate_context.sh` | 检查必需文件、标题和时间线条目格式。 | +| `validate_context.sh` | 检查必需文件和标题。 | | `summarize_context.sh` | 输出紧凑的状态摘要和压缩提示。 | ## 测试 diff --git a/SKILL.md b/SKILL.md index 5af556f..e3a96f0 100644 --- a/SKILL.md +++ b/SKILL.md @@ -30,7 +30,7 @@ The shared memory lives in a separate Git repository made of Markdown files. Age 3. Prefer branch-first updates. Direct pushes to the default branch should be rare and explicitly justified. 4. Never overwrite conflicts automatically. If the repo is dirty or the branch has diverged, stop and reconcile. 5. Separate facts from inferences. Verified facts belong in the stable sections; uncertainty stays visible in open questions or clearly labeled hypotheses. -6. Add timeline entries only for meaningful changes. Avoid noisy logging for every minor thought. +6. Record change history in commit messages, not in separate files. Use structured commit messages with Trigger/Applied/Unresolved fields. ## Recommended Workflow diff --git a/agents/claude.yaml b/agents/claude.yaml index d5aa5df..2e83b5e 100644 --- a/agents/claude.yaml +++ b/agents/claude.yaml @@ -13,10 +13,10 @@ parameters: workflow_hints: - "Run scripts/sync_context.sh before reading or editing any shared document." - - "Always read CONTEXT.md and TIMELINE.md before making changes." + - "Always read CONTEXT.md before making changes." - "Use scripts/prepare_branch.sh --actor claude --slug to create update branches." - "Run scripts/validate_context.sh after edits and before committing." - - "Commit only when changes are meaningful — avoid noisy timeline entries." + - "Commit only when changes are meaningful. Use structured commit messages with Trigger/Applied/Unresolved fields." skill_paths: skill_definition: "SKILL.md" diff --git a/agents/codex.yaml b/agents/codex.yaml index 018f76b..cd8eab6 100644 --- a/agents/codex.yaml +++ b/agents/codex.yaml @@ -13,10 +13,10 @@ parameters: workflow_hints: - "Run scripts/sync_context.sh before reading or editing any shared document." - - "Always read CONTEXT.md and TIMELINE.md before making changes." + - "Always read CONTEXT.md before making changes." - "Use scripts/prepare_branch.sh --actor codex --slug to create update branches." - "Run scripts/validate_context.sh after edits and before committing." - - "Commit only when changes are meaningful — avoid noisy timeline entries." + - "Commit only when changes are meaningful. Use structured commit messages with Trigger/Applied/Unresolved fields." skill_paths: skill_definition: "SKILL.md" diff --git a/agents/openai.yaml b/agents/openai.yaml index 144f15f..2d3c01f 100644 --- a/agents/openai.yaml +++ b/agents/openai.yaml @@ -13,10 +13,10 @@ parameters: workflow_hints: - "Run scripts/sync_context.sh before reading or editing any shared document." - - "Always read CONTEXT.md and TIMELINE.md before making changes." + - "Always read CONTEXT.md before making changes." - "Use scripts/prepare_branch.sh --actor openai --slug to create update branches." - "Run scripts/validate_context.sh after edits and before committing." - - "Commit only when changes are meaningful - avoid noisy timeline entries." + - "Commit only when changes are meaningful. Use structured commit messages with Trigger/Applied/Unresolved fields." skill_paths: skill_definition: "SKILL.md" diff --git a/references/conflict-policy.md b/references/conflict-policy.md index c56f4ae..40a18ed 100644 --- a/references/conflict-policy.md +++ b/references/conflict-policy.md @@ -46,4 +46,4 @@ If Git reports conflicts: - Stop editing automation - Open the conflicting files and reconcile them manually - Preserve both the latest verified facts and the latest unresolved questions -- Add a timeline entry if the reconcile materially changes the shared understanding +- Use a structured commit message if the reconcile materially changes the shared understanding diff --git a/references/cto-cmo-sync-process.md b/references/cto-cmo-sync-process.md index 815fdad..588b5d2 100644 --- a/references/cto-cmo-sync-process.md +++ b/references/cto-cmo-sync-process.md @@ -69,7 +69,6 @@ After each sync, the CTO updates the shared context repo: ```bash scripts/prepare_branch.sh --actor cto --slug cto-cmo-sync # Update CONTEXT.md with decisions and active state -# Update TIMELINE.md with sync summary entry scripts/validate_context.sh ``` diff --git a/references/handoff-guidelines.md b/references/handoff-guidelines.md index 5619ace..07d0e77 100644 --- a/references/handoff-guidelines.md +++ b/references/handoff-guidelines.md @@ -29,7 +29,7 @@ Cover these four points: ## What Changed - Added verified API rate-limit notes to `CONTEXT.md` -- Logged the update in `TIMELINE.md` +- Committed the update with a structured context message ## What Is True Now - The shared repo has the latest staging findings