multi-forge 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- forge/__init__.py +3 -0
- forge/_extensions/agents/.gitkeep +0 -0
- forge/_extensions/commands/.gitkeep +0 -0
- forge/_extensions/skills/analyze/SKILL.md +87 -0
- forge/_extensions/skills/challenge/SKILL.md +91 -0
- forge/_extensions/skills/consensus/SKILL.md +120 -0
- forge/_extensions/skills/consensus/resources/code_consensus_evaluation.md +94 -0
- forge/_extensions/skills/consensus/resources/consensus_evaluation.md +70 -0
- forge/_extensions/skills/consensus/resources/synthesis.md +101 -0
- forge/_extensions/skills/debate/SKILL.md +116 -0
- forge/_extensions/skills/debate/resources/code_debate_evaluation.md +101 -0
- forge/_extensions/skills/debate/resources/debate_evaluation.md +90 -0
- forge/_extensions/skills/panel/SKILL.md +141 -0
- forge/_extensions/skills/panel/resources/synthesis.md +103 -0
- forge/_extensions/skills/qa/SKILL.md +704 -0
- forge/_extensions/skills/qa/resources/checklist/0-enable.md +78 -0
- forge/_extensions/skills/qa/resources/checklist/1-preflight.md +24 -0
- forge/_extensions/skills/qa/resources/checklist/10-resume.md +143 -0
- forge/_extensions/skills/qa/resources/checklist/11-config.md +150 -0
- forge/_extensions/skills/qa/resources/checklist/12-search.md +58 -0
- forge/_extensions/skills/qa/resources/checklist/13-guard.md +237 -0
- forge/_extensions/skills/qa/resources/checklist/14-workflow.md +305 -0
- forge/_extensions/skills/qa/resources/checklist/15-skills.md +155 -0
- forge/_extensions/skills/qa/resources/checklist/16-handoff.md +224 -0
- forge/_extensions/skills/qa/resources/checklist/17-info.md +50 -0
- forge/_extensions/skills/qa/resources/checklist/18-disable.md +84 -0
- forge/_extensions/skills/qa/resources/checklist/19-uninstall.md +146 -0
- forge/_extensions/skills/qa/resources/checklist/2-extensions.md +188 -0
- forge/_extensions/skills/qa/resources/checklist/20-cleanup.md +36 -0
- forge/_extensions/skills/qa/resources/checklist/3-auth.md +234 -0
- forge/_extensions/skills/qa/resources/checklist/4-proxy.md +481 -0
- forge/_extensions/skills/qa/resources/checklist/5-session.md +541 -0
- forge/_extensions/skills/qa/resources/checklist/6-hooks.md +275 -0
- forge/_extensions/skills/qa/resources/checklist/7-costs.md +309 -0
- forge/_extensions/skills/qa/resources/checklist/8-status-line.md +174 -0
- forge/_extensions/skills/qa/resources/checklist/9-direct-commands.md +146 -0
- forge/_extensions/skills/qa/resources/checklist.md +103 -0
- forge/_extensions/skills/qa/resources/report-template.md +62 -0
- forge/_extensions/skills/qa/scripts/start-container.sh +529 -0
- forge/_extensions/skills/qa/scripts/walkthrough-state.py +1137 -0
- forge/_extensions/skills/review/SKILL.md +125 -0
- forge/_extensions/skills/review/references/claude-4.6.md +474 -0
- forge/_extensions/skills/review/references/claude-4.7.md +710 -0
- forge/_extensions/skills/review/references/gemini-3.1.md +546 -0
- forge/_extensions/skills/review/references/gpt-5.5.md +490 -0
- forge/_extensions/skills/review/references/skills-writing-guide.md +1588 -0
- forge/_extensions/skills/review/resources/code-anthropic.md +160 -0
- forge/_extensions/skills/review/resources/code-gemini.md +184 -0
- forge/_extensions/skills/review/resources/code-openai.md +203 -0
- forge/_extensions/skills/review/resources/code.md +160 -0
- forge/_extensions/skills/review-docs/SKILL.md +121 -0
- forge/_extensions/skills/review-docs/resources/docs-anthropic.md +170 -0
- forge/_extensions/skills/review-docs/resources/docs-gemini.md +204 -0
- forge/_extensions/skills/review-docs/resources/docs-openai.md +231 -0
- forge/_extensions/skills/review-docs/resources/docs.md +170 -0
- forge/_extensions/skills/smoke-test/SKILL.md +27 -0
- forge/_extensions/skills/smoke-test/scripts/smoke-test.sh +118 -0
- forge/_extensions/skills/understand/SKILL.md +148 -0
- forge/_extensions/skills/understand/resources/code-anthropic.md +163 -0
- forge/_extensions/skills/understand/resources/code-gemini.md +194 -0
- forge/_extensions/skills/understand/resources/code-openai.md +181 -0
- forge/_extensions/skills/understand/resources/code.md +163 -0
- forge/_extensions/skills/understand/resources/docs-anthropic.md +177 -0
- forge/_extensions/skills/understand/resources/docs-gemini.md +202 -0
- forge/_extensions/skills/understand/resources/docs-openai.md +191 -0
- forge/_extensions/skills/understand/resources/docs.md +177 -0
- forge/_extensions/skills/walkthrough/SKILL.md +599 -0
- forge/_extensions/skills/walkthrough/resources/checklist.md +765 -0
- forge/_extensions/skills/walkthrough/scripts/run-in-repo.sh +118 -0
- forge/_extensions/skills/walkthrough/scripts/setup-test-repo.sh +198 -0
- forge/_extensions/skills/walkthrough/scripts/walkthrough-state.py +1137 -0
- forge/backend/__init__.py +174 -0
- forge/backend/adapters/__init__.py +38 -0
- forge/backend/adapters/litellm.py +158 -0
- forge/backend/creation.py +89 -0
- forge/backend/registry.py +178 -0
- forge/cli/__init__.py +16 -0
- forge/cli/auth.py +483 -0
- forge/cli/backend.py +298 -0
- forge/cli/claude.py +411 -0
- forge/cli/config_cmd.py +303 -0
- forge/cli/extensions.py +1001 -0
- forge/cli/gc.py +165 -0
- forge/cli/guard.py +1018 -0
- forge/cli/guards.py +106 -0
- forge/cli/handoff.py +110 -0
- forge/cli/hooks/__init__.py +36 -0
- forge/cli/hooks/_group.py +20 -0
- forge/cli/hooks/_helpers.py +149 -0
- forge/cli/hooks/commands.py +1677 -0
- forge/cli/hooks/direct_commands.py +1304 -0
- forge/cli/hooks/install.py +232 -0
- forge/cli/hooks/policy.py +151 -0
- forge/cli/hooks/read_hygiene.py +74 -0
- forge/cli/hooks/verification.py +370 -0
- forge/cli/logs.py +406 -0
- forge/cli/main.py +292 -0
- forge/cli/proxy.py +1821 -0
- forge/cli/proxy_costs.py +313 -0
- forge/cli/search.py +416 -0
- forge/cli/session.py +892 -0
- forge/cli/session_addendum.py +81 -0
- forge/cli/session_fork.py +750 -0
- forge/cli/session_handoff.py +141 -0
- forge/cli/session_lifecycle.py +2053 -0
- forge/cli/session_manage.py +1336 -0
- forge/cli/session_memory.py +201 -0
- forge/cli/status_line.py +1398 -0
- forge/cli/workflow.py +1964 -0
- forge/config/__init__.py +110 -0
- forge/config/dataclass_utils.py +88 -0
- forge/config/defaults/__init__.py +0 -0
- forge/config/defaults/backends/__init__.py +0 -0
- forge/config/defaults/backends/litellm.yaml +196 -0
- forge/config/defaults/templates/__init__.py +0 -0
- forge/config/defaults/templates/litellm-anthropic-local.yaml +33 -0
- forge/config/defaults/templates/litellm-anthropic.yaml +24 -0
- forge/config/defaults/templates/litellm-gemini-flash-local.yaml +37 -0
- forge/config/defaults/templates/litellm-gemini-local.yaml +32 -0
- forge/config/defaults/templates/litellm-gemini-test.yaml +34 -0
- forge/config/defaults/templates/litellm-gemini.yaml +21 -0
- forge/config/defaults/templates/litellm-openai-codex-local.yaml +36 -0
- forge/config/defaults/templates/litellm-openai-local.yaml +38 -0
- forge/config/defaults/templates/litellm-openai.yaml +28 -0
- forge/config/defaults/templates/openrouter-anthropic.yaml +23 -0
- forge/config/defaults/templates/openrouter-deepseek.yaml +26 -0
- forge/config/defaults/templates/openrouter-gemini-flash.yaml +26 -0
- forge/config/defaults/templates/openrouter-gemini.yaml +23 -0
- forge/config/defaults/templates/openrouter-glm.yaml +23 -0
- forge/config/defaults/templates/openrouter-kimi.yaml +30 -0
- forge/config/defaults/templates/openrouter-minimax.yaml +26 -0
- forge/config/defaults/templates/openrouter-openai-codex.yaml +23 -0
- forge/config/defaults/templates/openrouter-openai.yaml +28 -0
- forge/config/defaults/templates/openrouter-qwen.yaml +25 -0
- forge/config/loader.py +675 -0
- forge/config/schema.py +448 -0
- forge/core/__init__.py +5 -0
- forge/core/auth/__init__.py +67 -0
- forge/core/auth/capabilities.py +219 -0
- forge/core/auth/credentials_file.py +244 -0
- forge/core/auth/protocols.py +18 -0
- forge/core/auth/secrets.py +243 -0
- forge/core/auth/template_secrets.py +112 -0
- forge/core/data/__init__.py +5 -0
- forge/core/data/model_catalog.yaml +1522 -0
- forge/core/data/pricing.yaml +140 -0
- forge/core/data/system_prompt_addendums/__init__.py +0 -0
- forge/core/data/system_prompt_addendums/gemini.md +330 -0
- forge/core/data/system_prompt_addendums/openai.md +328 -0
- forge/core/llm/__init__.py +231 -0
- forge/core/llm/clients/__init__.py +14 -0
- forge/core/llm/clients/base.py +115 -0
- forge/core/llm/clients/litellm.py +619 -0
- forge/core/llm/clients/openai_compat.py +244 -0
- forge/core/llm/clients/openrouter.py +234 -0
- forge/core/llm/credentials.py +439 -0
- forge/core/llm/detection.py +86 -0
- forge/core/llm/errors.py +44 -0
- forge/core/llm/protocols.py +80 -0
- forge/core/llm/types.py +176 -0
- forge/core/logging.py +146 -0
- forge/core/models/__init__.py +91 -0
- forge/core/models/catalog.py +467 -0
- forge/core/models/pricing.py +165 -0
- forge/core/models/types.py +167 -0
- forge/core/naming.py +212 -0
- forge/core/ops/__init__.py +73 -0
- forge/core/ops/context.py +141 -0
- forge/core/ops/gc.py +802 -0
- forge/core/ops/proxy.py +146 -0
- forge/core/ops/resolution.py +135 -0
- forge/core/ops/session.py +344 -0
- forge/core/ops/session_context.py +548 -0
- forge/core/paths.py +38 -0
- forge/core/process.py +54 -0
- forge/core/reactive/__init__.py +38 -0
- forge/core/reactive/cost_tracking.py +300 -0
- forge/core/reactive/env.py +180 -0
- forge/core/reactive/proxy.py +78 -0
- forge/core/reactive/routing.py +622 -0
- forge/core/reactive/session_runner.py +185 -0
- forge/core/reactive/structured_output.py +62 -0
- forge/core/reactive/tagger.py +94 -0
- forge/core/reactive/throttle.py +132 -0
- forge/core/state/__init__.py +59 -0
- forge/core/state/exceptions.py +59 -0
- forge/core/state/io.py +140 -0
- forge/core/state/lock.py +99 -0
- forge/core/state/timestamps.py +60 -0
- forge/core/transcript.py +78 -0
- forge/core/typing_helpers.py +24 -0
- forge/core/workqueue/__init__.py +67 -0
- forge/core/workqueue/queue.py +552 -0
- forge/core/workqueue/types.py +63 -0
- forge/guard/__init__.py +26 -0
- forge/guard/deterministic/__init__.py +26 -0
- forge/guard/deterministic/base.py +158 -0
- forge/guard/deterministic/coding_standards.py +256 -0
- forge/guard/deterministic/registry.py +148 -0
- forge/guard/deterministic/tdd.py +171 -0
- forge/guard/engine.py +216 -0
- forge/guard/protocols.py +91 -0
- forge/guard/queries.py +96 -0
- forge/guard/semantic/__init__.py +34 -0
- forge/guard/semantic/promotion.py +18 -0
- forge/guard/semantic/supervisor.py +813 -0
- forge/guard/semantic/verdict.py +183 -0
- forge/guard/store.py +124 -0
- forge/guard/team/__init__.py +6 -0
- forge/guard/team/config.py +24 -0
- forge/guard/team/handlers.py +209 -0
- forge/guard/team/prompts.py +41 -0
- forge/guard/types.py +125 -0
- forge/guard/workflow/__init__.py +17 -0
- forge/guard/workflow/branches.py +67 -0
- forge/guard/workflow/config.py +63 -0
- forge/guard/workflow/divergence.py +113 -0
- forge/guard/workflow/policy.py +87 -0
- forge/guard/workflow/stages.py +205 -0
- forge/install/__init__.py +55 -0
- forge/install/cli.py +281 -0
- forge/install/exceptions.py +163 -0
- forge/install/hooks.py +109 -0
- forge/install/installer.py +1037 -0
- forge/install/models.py +321 -0
- forge/install/preset.py +272 -0
- forge/install/settings_merge.py +831 -0
- forge/install/tracking.py +238 -0
- forge/install/version.py +141 -0
- forge/proxy/__init__.py +0 -0
- forge/proxy/base_client.py +181 -0
- forge/proxy/client_adapter.py +476 -0
- forge/proxy/client_factory.py +531 -0
- forge/proxy/converters.py +1206 -0
- forge/proxy/cost_logger.py +132 -0
- forge/proxy/cost_tracker.py +242 -0
- forge/proxy/data_models.py +338 -0
- forge/proxy/error_hints.py +92 -0
- forge/proxy/metrics.py +222 -0
- forge/proxy/model_spec.py +158 -0
- forge/proxy/proxies.py +333 -0
- forge/proxy/proxy_identity.py +134 -0
- forge/proxy/proxy_orchestrator.py +1018 -0
- forge/proxy/proxy_startup.py +54 -0
- forge/proxy/server.py +1561 -0
- forge/proxy/utils.py +537 -0
- forge/review/__init__.py +6 -0
- forge/review/adversarial.py +111 -0
- forge/review/consensus.py +236 -0
- forge/review/engine.py +356 -0
- forge/review/models.py +437 -0
- forge/review/resources/__init__.py +5 -0
- forge/review/resources/codereview-performance.md +85 -0
- forge/review/resources/codereview-quick.md +75 -0
- forge/review/resources/codereview-security.md +92 -0
- forge/review/resources/codereview.md +85 -0
- forge/review/resources/docreview-quick.md +75 -0
- forge/review/resources/docreview.md +86 -0
- forge/review/resources/thinkdeep.md +89 -0
- forge/review/routing.py +368 -0
- forge/review/synthesis.py +73 -0
- forge/runtime_config.py +438 -0
- forge/search/__init__.py +55 -0
- forge/search/bm25_store.py +264 -0
- forge/search/content_store.py +197 -0
- forge/search/engine.py +352 -0
- forge/search/exceptions.py +51 -0
- forge/search/extractor.py +234 -0
- forge/search/index_state.py +295 -0
- forge/search/store.py +215 -0
- forge/search/tokenizer.py +24 -0
- forge/session/__init__.py +130 -0
- forge/session/active.py +339 -0
- forge/session/artifacts.py +202 -0
- forge/session/claude/__init__.py +50 -0
- forge/session/claude/cleanup.py +105 -0
- forge/session/claude/invoke.py +236 -0
- forge/session/claude/paths.py +200 -0
- forge/session/cleanup.py +216 -0
- forge/session/config.py +34 -0
- forge/session/direct_model.py +107 -0
- forge/session/effective.py +169 -0
- forge/session/exceptions.py +255 -0
- forge/session/handoff.py +881 -0
- forge/session/handoff_agent.py +544 -0
- forge/session/hooks/__init__.py +35 -0
- forge/session/hooks/models.py +73 -0
- forge/session/hooks/session_start.py +507 -0
- forge/session/identity.py +84 -0
- forge/session/index.py +553 -0
- forge/session/manager.py +1506 -0
- forge/session/models.py +572 -0
- forge/session/overrides.py +344 -0
- forge/session/plan_resolution.py +286 -0
- forge/session/prev_sessions.py +128 -0
- forge/session/store.py +431 -0
- forge/session/validation.py +47 -0
- forge/session/worktree/__init__.py +65 -0
- forge/session/worktree/cleanup.py +262 -0
- forge/session/worktree/config_copy.py +203 -0
- forge/session/worktree/create.py +332 -0
- forge/sidecar/__init__.py +29 -0
- forge/sidecar/container.py +161 -0
- forge/sidecar/docker.py +86 -0
- forge/sidecar/secrets.py +19 -0
- multi_forge-0.2.0.dist-info/METADATA +242 -0
- multi_forge-0.2.0.dist-info/RECORD +311 -0
- multi_forge-0.2.0.dist-info/WHEEL +4 -0
- multi_forge-0.2.0.dist-info/entry_points.txt +2 -0
- multi_forge-0.2.0.dist-info/licenses/LICENSE +203 -0
- multi_forge-0.2.0.dist-info/licenses/NOTICE +14 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Safety wrapper for Forge walkthrough commands.
|
|
3
|
+
# Sources env.sh, verifies isolation through 4 gates, cd's to test repo, runs the command.
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# bash run-in-repo.sh forge session list # cd's to test repo automatically
|
|
7
|
+
# bash run-in-repo.sh jq '.' .claude/settings.json # relative paths work
|
|
8
|
+
# bash run-in-repo.sh --no-cd docker info # skip cd (maintainer-only)
|
|
9
|
+
#
|
|
10
|
+
# Exit codes:
|
|
11
|
+
# Command's exit code on success
|
|
12
|
+
# 1 on any gate failure
|
|
13
|
+
|
|
14
|
+
set -euo pipefail
|
|
15
|
+
|
|
16
|
+
# --- Parse --no-cd flag (maintainer-only: only for commands with no path arguments) ---
|
|
17
|
+
NO_CD=false
|
|
18
|
+
if [ "${1:-}" = "--no-cd" ]; then
|
|
19
|
+
NO_CD=true
|
|
20
|
+
shift
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
if [ $# -eq 0 ]; then
|
|
24
|
+
echo "ERROR: No command specified." >&2
|
|
25
|
+
echo "Usage: bash run-in-repo.sh [--no-cd] <command...>" >&2
|
|
26
|
+
exit 1
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
# --- Resolve FORGE_TEST_REPO ---
|
|
30
|
+
# Check for explicitly-set empty value before applying default
|
|
31
|
+
if [ "${FORGE_TEST_REPO+set}" = "set" ] && [ -z "$FORGE_TEST_REPO" ]; then
|
|
32
|
+
echo "ERROR: FORGE_TEST_REPO is explicitly set to empty. Refusing to proceed." >&2
|
|
33
|
+
exit 1
|
|
34
|
+
fi
|
|
35
|
+
FORGE_TEST_REPO="${FORGE_TEST_REPO:-${FORGE_HOME:-$HOME/.forge}/manual-testing/walkthrough/test-repo}"
|
|
36
|
+
FORGE_TEST_REPO="$(python3 -c 'import os,sys; print(os.path.abspath(os.path.expanduser(sys.argv[1])))' "$FORGE_TEST_REPO")"
|
|
37
|
+
|
|
38
|
+
# --- Denylist: refuse obviously dangerous values ---
|
|
39
|
+
check_safe_path() {
|
|
40
|
+
local resolved="$1"
|
|
41
|
+
|
|
42
|
+
if [ -z "$resolved" ]; then
|
|
43
|
+
echo "ERROR: FORGE_TEST_REPO is empty. Refusing to proceed." >&2
|
|
44
|
+
exit 1
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
local -a denylist=("/" "$HOME" "/Users" "/tmp" "/var" "/etc" "/opt" "/usr")
|
|
48
|
+
for bad in "${denylist[@]}"; do
|
|
49
|
+
local bad_resolved
|
|
50
|
+
bad_resolved="$(realpath "$bad" 2>/dev/null || echo "$bad")"
|
|
51
|
+
if [ "$resolved" = "$bad" ] || [ "$resolved" = "$bad_resolved" ]; then
|
|
52
|
+
echo "ERROR: FORGE_TEST_REPO='$resolved' is a denylisted path. Refusing to proceed." >&2
|
|
53
|
+
echo " Set FORGE_TEST_REPO to a safe test directory (not $bad)." >&2
|
|
54
|
+
exit 1
|
|
55
|
+
fi
|
|
56
|
+
done
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
check_safe_path "$FORGE_TEST_REPO"
|
|
60
|
+
|
|
61
|
+
# --- Gate 1: env.sh exists ---
|
|
62
|
+
ENV_FILE="$FORGE_TEST_REPO/.forge/walkthrough/env.sh"
|
|
63
|
+
if [ ! -f "$ENV_FILE" ]; then
|
|
64
|
+
echo "ERROR: env.sh not found at: $ENV_FILE" >&2
|
|
65
|
+
echo "" >&2
|
|
66
|
+
echo " The test environment is missing. Likely causes:" >&2
|
|
67
|
+
echo " - Test repo was deleted (rm -rf $FORGE_TEST_REPO)" >&2
|
|
68
|
+
echo " - setup-test-repo.sh has not been run" >&2
|
|
69
|
+
echo "" >&2
|
|
70
|
+
echo " Fix: Run setup-test-repo.sh to recreate the test environment." >&2
|
|
71
|
+
exit 1
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
# shellcheck source=/dev/null
|
|
75
|
+
source "$ENV_FILE"
|
|
76
|
+
|
|
77
|
+
# --- Gate 2: marker file exists ---
|
|
78
|
+
MARKER_FILE="$FORGE_TEST_REPO/.forge-walkthrough-marker"
|
|
79
|
+
if [ ! -f "$MARKER_FILE" ]; then
|
|
80
|
+
echo "ERROR: Marker file missing at: $MARKER_FILE" >&2
|
|
81
|
+
echo " This directory was not created by setup-test-repo.sh." >&2
|
|
82
|
+
echo " Refusing to run commands -- your real system may be at risk." >&2
|
|
83
|
+
exit 1
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
# --- Gate 3: FORGE_HOME isolation ---
|
|
87
|
+
EXPECTED_FORGE_HOME="$FORGE_TEST_REPO/.forge-home"
|
|
88
|
+
if [ "${FORGE_HOME:-}" != "$EXPECTED_FORGE_HOME" ]; then
|
|
89
|
+
echo "ERROR: FORGE_HOME is not redirected to the test sandbox." >&2
|
|
90
|
+
echo " Expected: $EXPECTED_FORGE_HOME" >&2
|
|
91
|
+
echo " Actual: ${FORGE_HOME:-<unset>}" >&2
|
|
92
|
+
echo " Did you source env.sh?" >&2
|
|
93
|
+
exit 1
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# --- Gate 4: structure check ---
|
|
97
|
+
if [ ! -d "$FORGE_TEST_REPO/.forge/walkthrough" ]; then
|
|
98
|
+
echo "ERROR: Expected directory missing: $FORGE_TEST_REPO/.forge/walkthrough/" >&2
|
|
99
|
+
echo " The test repo structure is incomplete. Run setup-test-repo.sh." >&2
|
|
100
|
+
exit 1
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
if [ ! -f "$FORGE_TEST_REPO/CLAUDE.md" ]; then
|
|
104
|
+
echo "ERROR: Expected file missing: $FORGE_TEST_REPO/CLAUDE.md" >&2
|
|
105
|
+
echo " This doesn't look like a forge walkthrough test repo." >&2
|
|
106
|
+
exit 1
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
# --- cd to test repo (unless --no-cd) ---
|
|
110
|
+
if [ "$NO_CD" = false ]; then
|
|
111
|
+
cd "$FORGE_TEST_REPO" || {
|
|
112
|
+
echo "ERROR: Cannot cd to test repo: $FORGE_TEST_REPO" >&2
|
|
113
|
+
exit 1
|
|
114
|
+
}
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
# --- Execute the command ---
|
|
118
|
+
exec "$@"
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Setup script for Forge walkthrough test repo.
|
|
3
|
+
# Creates an isolated test workspace with hermetic FORGE_HOME.
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# setup-test-repo.sh # Create test repo (default location)
|
|
7
|
+
# setup-test-repo.sh --reset # Reset existing repo to clean baseline
|
|
8
|
+
#
|
|
9
|
+
# Environment:
|
|
10
|
+
# FORGE_TEST_REPO Override test repo location (default: ~/.forge/manual-testing/walkthrough/test-repo)
|
|
11
|
+
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
|
|
14
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
|
|
15
|
+
|
|
16
|
+
FORGE_TEST_REPO="${FORGE_TEST_REPO:-${FORGE_HOME:-$HOME/.forge}/manual-testing/walkthrough/test-repo}"
|
|
17
|
+
# Resolve to absolute path (and expand leading "~"), even if parent dirs don't exist yet.
|
|
18
|
+
FORGE_TEST_REPO="$(python3 -c 'import os,sys; print(os.path.abspath(os.path.expanduser(sys.argv[1])))' "$FORGE_TEST_REPO")"
|
|
19
|
+
|
|
20
|
+
MARKER_FILE="$FORGE_TEST_REPO/.forge-walkthrough-marker"
|
|
21
|
+
|
|
22
|
+
# --- Safety: refuse known-bad paths ---
|
|
23
|
+
check_safe_path() {
|
|
24
|
+
local resolved
|
|
25
|
+
resolved="$(realpath "$1" 2>/dev/null || echo "$1")"
|
|
26
|
+
|
|
27
|
+
local -a denylist=("/" "$HOME" "/Users" "/tmp" "/var" "/etc" "/opt" "/usr")
|
|
28
|
+
for bad in "${denylist[@]}"; do
|
|
29
|
+
if [ "$resolved" = "$bad" ] || [ "$resolved" = "$(realpath "$bad" 2>/dev/null || echo "$bad")" ]; then
|
|
30
|
+
echo "ERROR: Refusing to operate on '$resolved' (denylisted path)" >&2
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
done
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
check_safe_path "$FORGE_TEST_REPO"
|
|
37
|
+
|
|
38
|
+
# --- Generate env.sh (defined early, used by both --reset and fresh init) ---
|
|
39
|
+
generate_env() {
|
|
40
|
+
mkdir -p "$FORGE_TEST_REPO/.forge/walkthrough"
|
|
41
|
+
cat > "$FORGE_TEST_REPO/.forge/walkthrough/env.sh" << ENVEOF
|
|
42
|
+
# Generated by setup-test-repo.sh -- sandbox Forge state only.
|
|
43
|
+
# HOME stays real (Claude auth, native install, shell config all work).
|
|
44
|
+
# Only FORGE_HOME is redirected so forge CLI state is isolated.
|
|
45
|
+
export FORGE_TEST_REPO="$FORGE_TEST_REPO"
|
|
46
|
+
export FORGE_HOME="$FORGE_TEST_REPO/.forge-home"
|
|
47
|
+
export FORGE_DEBUG="1"
|
|
48
|
+
echo "Forge walkthrough sandbox active:" >&2
|
|
49
|
+
echo " FORGE_HOME = \$FORGE_HOME (isolated)" >&2
|
|
50
|
+
echo " HOME = \$HOME (unchanged)" >&2
|
|
51
|
+
echo " FORGE_DEBUG = \$FORGE_DEBUG (sandbox debug logging)" >&2
|
|
52
|
+
ENVEOF
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# --- Remove walkthrough-derived state that should not persist across reruns ---
|
|
56
|
+
scrub_volatile_state() {
|
|
57
|
+
rm -rf "$FORGE_TEST_REPO/.forge/artifacts"
|
|
58
|
+
rm -rf "$FORGE_TEST_REPO/.forge/search-index"
|
|
59
|
+
rm -rf "$FORGE_TEST_REPO/.forge-home/logs"
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# --- Handle --reset ---
|
|
63
|
+
if [ "${1:-}" = "--reset" ]; then
|
|
64
|
+
if [ -d "$FORGE_TEST_REPO" ] && [ -f "$MARKER_FILE" ]; then
|
|
65
|
+
# Marker exists: fast reset via git
|
|
66
|
+
if [ ! -f "$FORGE_TEST_REPO/src/main.py" ] || [ ! -f "$FORGE_TEST_REPO/CLAUDE.md" ]; then
|
|
67
|
+
echo "ERROR: Expected structure not found (src/main.py, CLAUDE.md)." >&2
|
|
68
|
+
echo "This does not look like a forge-walkthrough repo. Refusing --reset." >&2
|
|
69
|
+
exit 1
|
|
70
|
+
fi
|
|
71
|
+
echo "Resetting test repo: $FORGE_TEST_REPO" >&2
|
|
72
|
+
cd "$FORGE_TEST_REPO"
|
|
73
|
+
git clean -fdx -e .forge/walkthrough/ -e .forge-home/
|
|
74
|
+
git checkout -- .
|
|
75
|
+
mkdir -p .forge-home
|
|
76
|
+
mkdir -p .forge/walkthrough
|
|
77
|
+
scrub_volatile_state
|
|
78
|
+
echo "forge-walkthrough-marker" > "$MARKER_FILE"
|
|
79
|
+
generate_env
|
|
80
|
+
echo "Reset complete." >&2
|
|
81
|
+
exit 0
|
|
82
|
+
elif [ -d "$FORGE_TEST_REPO" ]; then
|
|
83
|
+
# No marker but dir exists: nuke and fall through to fresh init
|
|
84
|
+
echo "No marker file found. Removing stale directory and recreating." >&2
|
|
85
|
+
rm -rf "$FORGE_TEST_REPO"
|
|
86
|
+
fi
|
|
87
|
+
# Fall through to fresh init below
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# --- Idempotent: skip if already exists ---
|
|
91
|
+
if [ -d "$FORGE_TEST_REPO" ] && [ -f "$MARKER_FILE" ]; then
|
|
92
|
+
# Keep the repo baseline, but scrub walkthrough-derived artifacts so reruns start clean.
|
|
93
|
+
scrub_volatile_state
|
|
94
|
+
# Ensure env.sh exists and stays up to date (e.g., if skill path moved).
|
|
95
|
+
generate_env
|
|
96
|
+
echo "Test repo already exists: $FORGE_TEST_REPO" >&2
|
|
97
|
+
echo "Scrubbed volatile walkthrough state (.forge/artifacts, .forge/search-index, .forge-home/logs)." >&2
|
|
98
|
+
echo "Env file: $FORGE_TEST_REPO/.forge/walkthrough/env.sh" >&2
|
|
99
|
+
echo "Use --reset to clean and re-initialize." >&2
|
|
100
|
+
exit 0
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
# --- Refuse to init into an existing directory without a marker ---
|
|
104
|
+
if [ -d "$FORGE_TEST_REPO" ] && [ ! -f "$MARKER_FILE" ]; then
|
|
105
|
+
echo "ERROR: Directory exists but has no marker: $FORGE_TEST_REPO" >&2
|
|
106
|
+
echo "This was not created by setup-test-repo.sh. Refusing to initialize." >&2
|
|
107
|
+
echo "Remove it manually or choose a different FORGE_TEST_REPO path." >&2
|
|
108
|
+
exit 1
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
# --- Fresh init ---
|
|
112
|
+
echo "Creating test repo: $FORGE_TEST_REPO" >&2
|
|
113
|
+
|
|
114
|
+
mkdir -p "$FORGE_TEST_REPO"
|
|
115
|
+
cd "$FORGE_TEST_REPO"
|
|
116
|
+
|
|
117
|
+
# Isolated Forge state (only FORGE_HOME is sandboxed, not HOME)
|
|
118
|
+
mkdir -p .forge-home
|
|
119
|
+
|
|
120
|
+
# State/reports namespace
|
|
121
|
+
mkdir -p .forge/walkthrough
|
|
122
|
+
|
|
123
|
+
# Source tree
|
|
124
|
+
mkdir -p src tests .claude
|
|
125
|
+
|
|
126
|
+
# --- Write fixture files ---
|
|
127
|
+
|
|
128
|
+
cat > src/main.py << 'PYEOF'
|
|
129
|
+
def hello():
|
|
130
|
+
return "world"
|
|
131
|
+
PYEOF
|
|
132
|
+
|
|
133
|
+
cat > tests/test_main.py << 'PYEOF'
|
|
134
|
+
from src.main import hello
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def test_hello():
|
|
138
|
+
assert hello() == "world"
|
|
139
|
+
PYEOF
|
|
140
|
+
|
|
141
|
+
cat > CLAUDE.md << 'PYEOF'
|
|
142
|
+
# forge-walkthrough
|
|
143
|
+
|
|
144
|
+
This is a test repo for the Forge walkthrough skill.
|
|
145
|
+
PYEOF
|
|
146
|
+
|
|
147
|
+
cat > README.md << 'PYEOF'
|
|
148
|
+
# forge-walkthrough
|
|
149
|
+
|
|
150
|
+
Test workspace for the Forge `/walkthrough` skill.
|
|
151
|
+
PYEOF
|
|
152
|
+
|
|
153
|
+
cat > .claude/settings.local.json << 'JSONEOF'
|
|
154
|
+
{
|
|
155
|
+
"permissions": {
|
|
156
|
+
"allow": [
|
|
157
|
+
"Bash(npm test)",
|
|
158
|
+
"Bash(uv run pytest*)"
|
|
159
|
+
]
|
|
160
|
+
},
|
|
161
|
+
"env": {
|
|
162
|
+
"MY_CUSTOM_VAR": "should-survive-forge"
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
JSONEOF
|
|
166
|
+
|
|
167
|
+
cat > .gitignore << 'GITEOF'
|
|
168
|
+
.DS_Store
|
|
169
|
+
.idea/
|
|
170
|
+
.env
|
|
171
|
+
.forge-home/
|
|
172
|
+
.forge/
|
|
173
|
+
__pycache__/
|
|
174
|
+
*.pyc
|
|
175
|
+
GITEOF
|
|
176
|
+
|
|
177
|
+
# --- Git init ---
|
|
178
|
+
git init -q
|
|
179
|
+
git config user.email "forge-test@localhost"
|
|
180
|
+
git config user.name "Forge Test"
|
|
181
|
+
git config commit.gpgsign false
|
|
182
|
+
git add -A
|
|
183
|
+
# Force-add paths excluded by Claude Code's global gitignore (~/.config/git/ignore):
|
|
184
|
+
# **/.claude/settings.local.json, **/CLAUDE.local.md
|
|
185
|
+
for f in .claude/ CLAUDE.local.md; do
|
|
186
|
+
[ -e "$f" ] && git add -f "$f"
|
|
187
|
+
done
|
|
188
|
+
git commit -q -m "Initial test repo for forge walkthrough"
|
|
189
|
+
|
|
190
|
+
# --- Write marker (after commit so it's untracked = survives git checkout) ---
|
|
191
|
+
echo "forge-walkthrough-marker" > "$MARKER_FILE"
|
|
192
|
+
|
|
193
|
+
# --- Generate env.sh ---
|
|
194
|
+
generate_env
|
|
195
|
+
|
|
196
|
+
echo "Test repo created: $FORGE_TEST_REPO" >&2
|
|
197
|
+
echo "FORGE_HOME: $FORGE_TEST_REPO/.forge-home/" >&2
|
|
198
|
+
echo "Env file: $FORGE_TEST_REPO/.forge/walkthrough/env.sh" >&2
|