codex-workspace-bootstrap 0.3.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.
- codex_workspace_bootstrap/__init__.py +3 -0
- codex_workspace_bootstrap/__main__.py +4 -0
- codex_workspace_bootstrap/agents.py +108 -0
- codex_workspace_bootstrap/audit.py +217 -0
- codex_workspace_bootstrap/cli.py +111 -0
- codex_workspace_bootstrap/sarif.py +83 -0
- codex_workspace_bootstrap-0.3.0.dist-info/METADATA +235 -0
- codex_workspace_bootstrap-0.3.0.dist-info/RECORD +12 -0
- codex_workspace_bootstrap-0.3.0.dist-info/WHEEL +5 -0
- codex_workspace_bootstrap-0.3.0.dist-info/entry_points.txt +2 -0
- codex_workspace_bootstrap-0.3.0.dist-info/licenses/LICENSE +21 -0
- codex_workspace_bootstrap-0.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
PYTHON_MARKERS = ("pyproject.toml", "requirements.txt", "setup.py", "setup.cfg")
|
|
8
|
+
NODE_MARKER = "package.json"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def detect_project_signals(root: Path) -> list[str]:
|
|
12
|
+
signals: list[str] = []
|
|
13
|
+
if any((root / marker).exists() for marker in PYTHON_MARKERS):
|
|
14
|
+
signals.append("Python")
|
|
15
|
+
if (root / NODE_MARKER).exists():
|
|
16
|
+
signals.append("Node.js")
|
|
17
|
+
return signals
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _node_script_names(root: Path) -> set[str]:
|
|
21
|
+
package_json = root / NODE_MARKER
|
|
22
|
+
if not package_json.exists():
|
|
23
|
+
return set()
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
data = json.loads(package_json.read_text(encoding="utf-8"))
|
|
27
|
+
except (OSError, json.JSONDecodeError, UnicodeDecodeError):
|
|
28
|
+
return set()
|
|
29
|
+
|
|
30
|
+
scripts = data.get("scripts")
|
|
31
|
+
if not isinstance(scripts, dict):
|
|
32
|
+
return set()
|
|
33
|
+
|
|
34
|
+
return {name for name, value in scripts.items() if isinstance(name, str) and isinstance(value, str)}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def validation_commands(root: Path) -> list[str]:
|
|
38
|
+
signals = detect_project_signals(root)
|
|
39
|
+
commands: list[str] = []
|
|
40
|
+
|
|
41
|
+
if "Python" in signals:
|
|
42
|
+
if (root / "tests").is_dir():
|
|
43
|
+
commands.append("python -m pytest")
|
|
44
|
+
else:
|
|
45
|
+
commands.append("python -m compileall .")
|
|
46
|
+
|
|
47
|
+
if "Node.js" in signals:
|
|
48
|
+
scripts = _node_script_names(root)
|
|
49
|
+
if "test" in scripts:
|
|
50
|
+
commands.append("npm test")
|
|
51
|
+
if "lint" in scripts:
|
|
52
|
+
commands.append("npm run lint")
|
|
53
|
+
if not {"test", "lint"} & scripts:
|
|
54
|
+
commands.append("npm install --ignore-scripts --package-lock-only --dry-run")
|
|
55
|
+
|
|
56
|
+
commands.extend(
|
|
57
|
+
[
|
|
58
|
+
"codex-workspace-bootstrap audit . --strict",
|
|
59
|
+
"git diff --check",
|
|
60
|
+
"git status --short",
|
|
61
|
+
]
|
|
62
|
+
)
|
|
63
|
+
return commands
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def generate_agents(root: Path) -> str:
|
|
67
|
+
signals = detect_project_signals(root)
|
|
68
|
+
signal_text = ", ".join(signals) if signals else "No common Python or Node.js manifest detected"
|
|
69
|
+
commands = validation_commands(root)
|
|
70
|
+
command_lines = "\n".join(f"- `{command}`" for command in commands)
|
|
71
|
+
|
|
72
|
+
return f"""# AGENTS.md
|
|
73
|
+
|
|
74
|
+
## Purpose
|
|
75
|
+
|
|
76
|
+
This repository is maintained with assistance from Codex.
|
|
77
|
+
|
|
78
|
+
## Detected project signals
|
|
79
|
+
|
|
80
|
+
{signal_text}
|
|
81
|
+
|
|
82
|
+
These signals are derived only from files present in the repository. Review this file before relying on the generated commands.
|
|
83
|
+
|
|
84
|
+
## Working rules
|
|
85
|
+
|
|
86
|
+
- Read README files and relevant project manifests before editing.
|
|
87
|
+
- Keep changes scoped to the requested issue or task.
|
|
88
|
+
- Do not add secrets, credentials, tokens, private data, or generated environment files.
|
|
89
|
+
- Prefer deterministic, scriptable commands over manual steps.
|
|
90
|
+
- Preserve existing platform support unless the task explicitly changes it.
|
|
91
|
+
- Add or update tests for behavior changes when the repository has a test suite.
|
|
92
|
+
- Explain user-visible behavior changes in the pull request or commit summary.
|
|
93
|
+
- Inspect the final diff before proposing completion.
|
|
94
|
+
|
|
95
|
+
## Suggested validation
|
|
96
|
+
|
|
97
|
+
Run the commands that apply to the change:
|
|
98
|
+
|
|
99
|
+
{command_lines}
|
|
100
|
+
|
|
101
|
+
If a generated command does not match the repository's documented workflow, follow the repository documentation and update this file rather than forcing the command to run.
|
|
102
|
+
|
|
103
|
+
## Safety
|
|
104
|
+
|
|
105
|
+
- Do not print the contents of files suspected to contain secrets.
|
|
106
|
+
- Do not overwrite user files without explicit approval.
|
|
107
|
+
- Treat successful automated checks as evidence, not as a security guarantee.
|
|
108
|
+
"""
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import asdict, dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
7
|
+
from typing import Iterable
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
|
+
class Check:
|
|
12
|
+
name: str
|
|
13
|
+
status: str
|
|
14
|
+
message: str
|
|
15
|
+
blocking: bool = False
|
|
16
|
+
|
|
17
|
+
def to_dict(self) -> dict[str, object]:
|
|
18
|
+
return asdict(self)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
COMMON_TOOLS: tuple[tuple[str, tuple[str, ...]], ...] = (
|
|
22
|
+
("git", ("git", "--version")),
|
|
23
|
+
("python", ("python", "--version")),
|
|
24
|
+
("node", ("node", "--version")),
|
|
25
|
+
("npm", ("npm", "--version")),
|
|
26
|
+
("powershell", ("powershell", "-NoProfile", "-Command", "$PSVersionTable.PSVersion.ToString()")),
|
|
27
|
+
("wsl", ("wsl", "--status")),
|
|
28
|
+
("codex", ("codex", "--version")),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
MANIFESTS = (
|
|
32
|
+
"pyproject.toml",
|
|
33
|
+
"requirements.txt",
|
|
34
|
+
"package.json",
|
|
35
|
+
"go.mod",
|
|
36
|
+
"Cargo.toml",
|
|
37
|
+
"pom.xml",
|
|
38
|
+
"build.gradle",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
RISK_FILENAMES = {
|
|
42
|
+
".env",
|
|
43
|
+
".env.local",
|
|
44
|
+
"credentials.json",
|
|
45
|
+
"service-account.json",
|
|
46
|
+
"id_rsa",
|
|
47
|
+
"id_ed25519",
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
RISK_SUFFIXES = (".pem", ".p12", ".pfx", ".key")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _tool_check(label: str, command: tuple[str, ...]) -> Check:
|
|
54
|
+
executable = shutil.which(command[0])
|
|
55
|
+
if not executable:
|
|
56
|
+
return Check(label, "warn", f"{command[0]} command not found")
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
result = subprocess.run(
|
|
60
|
+
command,
|
|
61
|
+
capture_output=True,
|
|
62
|
+
text=True,
|
|
63
|
+
timeout=5,
|
|
64
|
+
check=False,
|
|
65
|
+
)
|
|
66
|
+
output = (result.stdout or result.stderr).strip().splitlines()
|
|
67
|
+
detail = output[0] if output else "available"
|
|
68
|
+
if result.returncode == 0:
|
|
69
|
+
return Check(label, "pass", detail)
|
|
70
|
+
return Check(label, "warn", f"available but returned exit code {result.returncode}")
|
|
71
|
+
except (OSError, subprocess.SubprocessError) as exc:
|
|
72
|
+
return Check(label, "warn", f"could not execute: {exc}")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _iter_project_files(root: Path) -> Iterable[Path]:
|
|
76
|
+
excluded = {".git", ".venv", "venv", "node_modules", "__pycache__"}
|
|
77
|
+
for path in root.rglob("*"):
|
|
78
|
+
if any(part in excluded for part in path.parts):
|
|
79
|
+
continue
|
|
80
|
+
if path.is_file():
|
|
81
|
+
yield path
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _git_matches(root: Path, args: tuple[str, ...]) -> bool:
|
|
85
|
+
if shutil.which("git") is None:
|
|
86
|
+
return False
|
|
87
|
+
try:
|
|
88
|
+
result = subprocess.run(
|
|
89
|
+
("git", "-C", str(root), *args),
|
|
90
|
+
capture_output=True,
|
|
91
|
+
text=True,
|
|
92
|
+
timeout=5,
|
|
93
|
+
check=False,
|
|
94
|
+
)
|
|
95
|
+
return result.returncode == 0
|
|
96
|
+
except (OSError, subprocess.SubprocessError):
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _classify_risky_paths(root: Path, risky: list[str]) -> tuple[list[str], list[str], list[str]]:
|
|
101
|
+
tracked: list[str] = []
|
|
102
|
+
ignored: list[str] = []
|
|
103
|
+
untracked: list[str] = []
|
|
104
|
+
|
|
105
|
+
for relative in risky:
|
|
106
|
+
if _git_matches(root, ("ls-files", "--error-unmatch", "--", relative)):
|
|
107
|
+
tracked.append(relative)
|
|
108
|
+
elif _git_matches(root, ("check-ignore", "--quiet", "--", relative)):
|
|
109
|
+
ignored.append(relative)
|
|
110
|
+
else:
|
|
111
|
+
untracked.append(relative)
|
|
112
|
+
|
|
113
|
+
return tracked, ignored, untracked
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _preview(paths: list[str], limit: int = 8) -> str:
|
|
117
|
+
visible = ", ".join(sorted(paths)[:limit])
|
|
118
|
+
suffix = "" if len(paths) <= limit else f" (+{len(paths) - limit} more)"
|
|
119
|
+
return f"{visible}{suffix}"
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def audit_repository(root: Path) -> list[Check]:
|
|
123
|
+
root = root.resolve()
|
|
124
|
+
checks: list[Check] = []
|
|
125
|
+
|
|
126
|
+
git_dir = root / ".git"
|
|
127
|
+
checks.append(
|
|
128
|
+
Check(
|
|
129
|
+
"git-repository",
|
|
130
|
+
"pass" if git_dir.exists() else "warn",
|
|
131
|
+
"Git repository detected" if git_dir.exists() else ".git directory not found",
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
readme = next((p for p in root.iterdir() if p.is_file() and p.name.lower().startswith("readme")), None)
|
|
136
|
+
checks.append(
|
|
137
|
+
Check(
|
|
138
|
+
"readme",
|
|
139
|
+
"pass" if readme else "warn",
|
|
140
|
+
f"README detected: {readme.name}" if readme else "README not found",
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
license_file = next((p for p in root.iterdir() if p.is_file() and p.name.lower().startswith("license")), None)
|
|
145
|
+
checks.append(
|
|
146
|
+
Check(
|
|
147
|
+
"license",
|
|
148
|
+
"pass" if license_file else "warn",
|
|
149
|
+
f"License detected: {license_file.name}" if license_file else "License file not found",
|
|
150
|
+
)
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
checks.append(
|
|
154
|
+
Check(
|
|
155
|
+
"gitignore",
|
|
156
|
+
"pass" if (root / ".gitignore").exists() else "warn",
|
|
157
|
+
".gitignore detected" if (root / ".gitignore").exists() else ".gitignore not found",
|
|
158
|
+
)
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
checks.append(
|
|
162
|
+
Check(
|
|
163
|
+
"agents",
|
|
164
|
+
"pass" if (root / "AGENTS.md").exists() else "warn",
|
|
165
|
+
"AGENTS.md detected" if (root / "AGENTS.md").exists() else "AGENTS.md not found; run init-agents",
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
manifests = [name for name in MANIFESTS if (root / name).exists()]
|
|
170
|
+
checks.append(
|
|
171
|
+
Check(
|
|
172
|
+
"project-manifest",
|
|
173
|
+
"pass" if manifests else "warn",
|
|
174
|
+
f"Detected: {', '.join(manifests)}" if manifests else "No common project manifest detected",
|
|
175
|
+
)
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
for label, command in COMMON_TOOLS:
|
|
179
|
+
checks.append(_tool_check(label, command))
|
|
180
|
+
|
|
181
|
+
risky: list[str] = []
|
|
182
|
+
for path in _iter_project_files(root):
|
|
183
|
+
name = path.name.lower()
|
|
184
|
+
if name in RISK_FILENAMES or name.endswith(RISK_SUFFIXES):
|
|
185
|
+
risky.append(path.relative_to(root).as_posix())
|
|
186
|
+
|
|
187
|
+
if risky:
|
|
188
|
+
tracked, ignored, untracked = _classify_risky_paths(root, risky)
|
|
189
|
+
parts: list[str] = []
|
|
190
|
+
if tracked:
|
|
191
|
+
parts.append(f"tracked: {_preview(tracked)}")
|
|
192
|
+
if ignored:
|
|
193
|
+
parts.append(f"ignored: {_preview(ignored)}")
|
|
194
|
+
if untracked:
|
|
195
|
+
parts.append(f"untracked/unknown: {_preview(untracked)}")
|
|
196
|
+
|
|
197
|
+
checks.append(
|
|
198
|
+
Check(
|
|
199
|
+
"secret-risk-files",
|
|
200
|
+
"warn",
|
|
201
|
+
"Potential secret-bearing filenames detected (" + "; ".join(parts) + "). "
|
|
202
|
+
"The audit does not read file contents.",
|
|
203
|
+
blocking=bool(tracked),
|
|
204
|
+
)
|
|
205
|
+
)
|
|
206
|
+
else:
|
|
207
|
+
checks.append(Check("secret-risk-files", "pass", "No common secret-bearing filenames detected"))
|
|
208
|
+
|
|
209
|
+
return checks
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def summary(checks: list[Check]) -> dict[str, int]:
|
|
213
|
+
return {
|
|
214
|
+
"passed": sum(c.status == "pass" for c in checks),
|
|
215
|
+
"warnings": sum(c.status == "warn" for c in checks),
|
|
216
|
+
"blocking": sum(c.blocking for c in checks),
|
|
217
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from . import __version__
|
|
9
|
+
from .agents import generate_agents
|
|
10
|
+
from .audit import audit_repository, summary
|
|
11
|
+
from .sarif import checks_to_sarif
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _parser() -> argparse.ArgumentParser:
|
|
15
|
+
parser = argparse.ArgumentParser(
|
|
16
|
+
prog="codex-workspace-bootstrap",
|
|
17
|
+
description="Audit and bootstrap repositories for reliable Codex workflows.",
|
|
18
|
+
)
|
|
19
|
+
parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
|
|
20
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
21
|
+
|
|
22
|
+
audit = sub.add_parser("audit", help="Audit a repository and local toolchain")
|
|
23
|
+
audit.add_argument("path", nargs="?", default=".")
|
|
24
|
+
audit.add_argument("--json", dest="json_path", help="Write the complete report to a JSON file")
|
|
25
|
+
audit.add_argument("--sarif", dest="sarif_path", help="Write warnings and blocking findings as SARIF 2.1.0")
|
|
26
|
+
audit.add_argument(
|
|
27
|
+
"--strict",
|
|
28
|
+
action="store_true",
|
|
29
|
+
help="Return a non-zero exit code if blocking checks are present",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
init_agents = sub.add_parser("init-agents", help="Create a project-aware starter AGENTS.md")
|
|
33
|
+
init_agents.add_argument("path", nargs="?", default=".")
|
|
34
|
+
init_agents.add_argument("--force", action="store_true", help="Overwrite an existing AGENTS.md")
|
|
35
|
+
|
|
36
|
+
return parser
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _write_json(path: str, payload: object, label: str) -> None:
|
|
40
|
+
output = Path(path).expanduser().resolve()
|
|
41
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
42
|
+
output.write_text(json.dumps(payload, indent=2), encoding="utf-8")
|
|
43
|
+
print(f"{label} written to: {output}")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _run_audit(
|
|
47
|
+
path: str,
|
|
48
|
+
json_path: str | None,
|
|
49
|
+
sarif_path: str | None,
|
|
50
|
+
strict: bool,
|
|
51
|
+
) -> int:
|
|
52
|
+
root = Path(path).expanduser().resolve()
|
|
53
|
+
if not root.exists() or not root.is_dir():
|
|
54
|
+
print(f"error: repository path does not exist or is not a directory: {root}", file=sys.stderr)
|
|
55
|
+
return 2
|
|
56
|
+
|
|
57
|
+
checks = audit_repository(root)
|
|
58
|
+
totals = summary(checks)
|
|
59
|
+
|
|
60
|
+
print(f"Repository: {root}")
|
|
61
|
+
for check in checks:
|
|
62
|
+
tag = "PASS" if check.status == "pass" else "WARN"
|
|
63
|
+
print(f"[{tag}] {check.name}: {check.message}")
|
|
64
|
+
|
|
65
|
+
print(
|
|
66
|
+
f"Summary: {totals['passed']} passed, "
|
|
67
|
+
f"{totals['warnings']} warnings, {totals['blocking']} blocking"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
if json_path:
|
|
71
|
+
_write_json(
|
|
72
|
+
json_path,
|
|
73
|
+
{
|
|
74
|
+
"repository": str(root),
|
|
75
|
+
"checks": [c.to_dict() for c in checks],
|
|
76
|
+
"summary": totals,
|
|
77
|
+
},
|
|
78
|
+
"JSON report",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
if sarif_path:
|
|
82
|
+
_write_json(sarif_path, checks_to_sarif(checks), "SARIF report")
|
|
83
|
+
|
|
84
|
+
if strict and totals["blocking"]:
|
|
85
|
+
return 1
|
|
86
|
+
return 0
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _run_init_agents(path: str, force: bool) -> int:
|
|
90
|
+
root = Path(path).expanduser().resolve()
|
|
91
|
+
if not root.exists() or not root.is_dir():
|
|
92
|
+
print(f"error: path does not exist or is not a directory: {root}", file=sys.stderr)
|
|
93
|
+
return 2
|
|
94
|
+
|
|
95
|
+
target = root / "AGENTS.md"
|
|
96
|
+
if target.exists() and not force:
|
|
97
|
+
print(f"error: {target} already exists; use --force to overwrite", file=sys.stderr)
|
|
98
|
+
return 1
|
|
99
|
+
|
|
100
|
+
target.write_text(generate_agents(root), encoding="utf-8")
|
|
101
|
+
print(f"Created {target}")
|
|
102
|
+
return 0
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def main(argv: list[str] | None = None) -> int:
|
|
106
|
+
args = _parser().parse_args(argv)
|
|
107
|
+
if args.command == "audit":
|
|
108
|
+
return _run_audit(args.path, args.json_path, args.sarif_path, args.strict)
|
|
109
|
+
if args.command == "init-agents":
|
|
110
|
+
return _run_init_agents(args.path, args.force)
|
|
111
|
+
return 2
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Iterable
|
|
4
|
+
|
|
5
|
+
from . import __version__
|
|
6
|
+
from .audit import Check
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
RULE_HELP: dict[str, str] = {
|
|
10
|
+
"git-repository": "Run the audit from the intended Git repository root when repository-aware checks are needed.",
|
|
11
|
+
"readme": "Add or identify project documentation that explains how contributors should work with the repository.",
|
|
12
|
+
"license": "Add an explicit open-source license when the project is intended for public reuse.",
|
|
13
|
+
"gitignore": "Use ignore rules to keep generated, local, and secret-bearing files out of version control.",
|
|
14
|
+
"agents": "Add or generate AGENTS.md so agent-assisted work has repository-specific instructions.",
|
|
15
|
+
"project-manifest": "Add or identify the project manifest that describes the repository toolchain.",
|
|
16
|
+
"git": "Install Git or make it available on PATH when Git-aware checks are required.",
|
|
17
|
+
"python": "Install Python or make it available on PATH when the repository requires Python.",
|
|
18
|
+
"node": "Install Node.js or make it available on PATH when the repository requires Node.js.",
|
|
19
|
+
"npm": "Install npm or make it available on PATH when the repository requires npm.",
|
|
20
|
+
"powershell": "Install or expose PowerShell when Windows PowerShell workflows are required.",
|
|
21
|
+
"wsl": "Install or configure WSL only when the repository workflow requires it.",
|
|
22
|
+
"codex": "Install or expose the Codex CLI when local Codex command checks are required.",
|
|
23
|
+
"secret-risk-files": "Review risky filenames before publishing or merging. The audit does not read file contents.",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _rule(check: Check) -> dict[str, object]:
|
|
28
|
+
help_text = RULE_HELP.get(
|
|
29
|
+
check.name,
|
|
30
|
+
"Review this repository-readiness finding and update project documentation or tooling as appropriate.",
|
|
31
|
+
)
|
|
32
|
+
return {
|
|
33
|
+
"id": check.name,
|
|
34
|
+
"name": check.name,
|
|
35
|
+
"shortDescription": {"text": f"codex-workspace-bootstrap: {check.name}"},
|
|
36
|
+
"fullDescription": {"text": help_text},
|
|
37
|
+
"help": {"text": help_text},
|
|
38
|
+
"properties": {
|
|
39
|
+
"precision": "high" if check.blocking else "medium",
|
|
40
|
+
"tags": ["codex", "repository-readiness"],
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def checks_to_sarif(checks: Iterable[Check]) -> dict[str, object]:
|
|
46
|
+
findings = [check for check in checks if check.status != "pass"]
|
|
47
|
+
rule_ids = sorted({check.name for check in findings})
|
|
48
|
+
rule_by_id = {
|
|
49
|
+
check.name: _rule(check)
|
|
50
|
+
for check in findings
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
results: list[dict[str, object]] = []
|
|
54
|
+
for check in findings:
|
|
55
|
+
results.append(
|
|
56
|
+
{
|
|
57
|
+
"ruleId": check.name,
|
|
58
|
+
"level": "error" if check.blocking else "warning",
|
|
59
|
+
"message": {"text": check.message},
|
|
60
|
+
"properties": {
|
|
61
|
+
"blocking": check.blocking,
|
|
62
|
+
"source": "codex-workspace-bootstrap",
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
"$schema": "https://json.schemastore.org/sarif-2.1.0.json",
|
|
69
|
+
"version": "2.1.0",
|
|
70
|
+
"runs": [
|
|
71
|
+
{
|
|
72
|
+
"tool": {
|
|
73
|
+
"driver": {
|
|
74
|
+
"name": "codex-workspace-bootstrap",
|
|
75
|
+
"informationUri": "https://github.com/kohli217/codex-workspace-bootstrap",
|
|
76
|
+
"version": __version__,
|
|
77
|
+
"rules": [rule_by_id[rule_id] for rule_id in rule_ids],
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
"results": results,
|
|
81
|
+
}
|
|
82
|
+
],
|
|
83
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: codex-workspace-bootstrap
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Audit and bootstrap Windows repositories for effective Codex workflows.
|
|
5
|
+
Author: kohli217
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/kohli217/codex-workspace-bootstrap
|
|
8
|
+
Project-URL: Repository, https://github.com/kohli217/codex-workspace-bootstrap
|
|
9
|
+
Project-URL: Issues, https://github.com/kohli217/codex-workspace-bootstrap/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/kohli217/codex-workspace-bootstrap/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: codex,openai,windows,developer-tools,cli,oss,agents-md
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
16
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
23
|
+
Classifier: Topic :: Software Development :: Testing
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# codex-workspace-bootstrap
|
|
30
|
+
|
|
31
|
+
[](https://github.com/kohli217/codex-workspace-bootstrap/actions/workflows/ci.yml)
|
|
32
|
+
[](https://github.com/kohli217/codex-workspace-bootstrap/actions/workflows/codeql.yml)
|
|
33
|
+
[](https://github.com/kohli217/codex-workspace-bootstrap/releases/latest)
|
|
34
|
+
[](LICENSE)
|
|
35
|
+
[](pyproject.toml)
|
|
36
|
+
|
|
37
|
+
Make Windows repositories **Codex-ready** with automated environment checks, project instructions, safety audits, and maintainer workflows.
|
|
38
|
+
|
|
39
|
+
[日本語ガイド](docs/README.ja.md) · [Examples](docs/EXAMPLES.md) · [Roadmap](docs/ROADMAP.md) · [Releases](https://github.com/kohli217/codex-workspace-bootstrap/releases)
|
|
40
|
+
|
|
41
|
+
> Community-maintained project. It is not an official OpenAI product and is not affiliated with OpenAI.
|
|
42
|
+
|
|
43
|
+
## What problem does it solve?
|
|
44
|
+
|
|
45
|
+
Codex works better when a repository clearly states its toolchain, validation commands, constraints, and maintenance workflow. On Windows, those details are often scattered across README files, shell history, and machine-specific assumptions.
|
|
46
|
+
|
|
47
|
+
`codex-workspace-bootstrap` gives maintainers one repeatable entry point to:
|
|
48
|
+
|
|
49
|
+
- audit Git, Python, Node.js, npm, PowerShell, WSL, and Codex availability;
|
|
50
|
+
- inspect repository basics such as README, license, ignore rules, manifests, and `AGENTS.md`;
|
|
51
|
+
- warn about common secret-bearing filenames without reading their contents;
|
|
52
|
+
- distinguish tracked risky files from ignored or untracked files when Git is available;
|
|
53
|
+
- generate a project-aware `AGENTS.md` for Python, Node.js, mixed, or unknown projects;
|
|
54
|
+
- write machine-readable JSON and SARIF 2.1.0 reports;
|
|
55
|
+
- fail CI on blocking findings with `--strict`;
|
|
56
|
+
- validate releases through automated tests, self-audit, and build checks.
|
|
57
|
+
|
|
58
|
+
## Why Windows-first?
|
|
59
|
+
|
|
60
|
+
The project targets a real repository-readiness problem around Windows and Windows+WSL development. It does not claim to fix upstream Codex product bugs. See [docs/MOTIVATION.md](docs/MOTIVATION.md) for the scope and public upstream references.
|
|
61
|
+
|
|
62
|
+
## 30-second start
|
|
63
|
+
|
|
64
|
+
Install the pinned v0.3.0 wheel:
|
|
65
|
+
|
|
66
|
+
```powershell
|
|
67
|
+
py -m pip install "https://github.com/kohli217/codex-workspace-bootstrap/releases/download/v0.3.0/codex_workspace_bootstrap-0.3.0-py3-none-any.whl"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Audit the current repository:
|
|
71
|
+
|
|
72
|
+
```powershell
|
|
73
|
+
codex-workspace-bootstrap audit .
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Generate project instructions:
|
|
77
|
+
|
|
78
|
+
```powershell
|
|
79
|
+
codex-workspace-bootstrap init-agents .
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Check the installed version:
|
|
83
|
+
|
|
84
|
+
```powershell
|
|
85
|
+
codex-workspace-bootstrap --version
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
For more examples, see [docs/EXAMPLES.md](docs/EXAMPLES.md).
|
|
89
|
+
|
|
90
|
+
## Why a pinned release instead of `irm ... | iex`?
|
|
91
|
+
|
|
92
|
+
The recommended quick start installs a specific published wheel so users can see exactly which release they are installing. A PowerShell helper script remains available in [scripts/install.ps1](scripts/install.ps1), but piping remote scripts directly into PowerShell is not the recommended path.
|
|
93
|
+
|
|
94
|
+
## GitHub Action
|
|
95
|
+
|
|
96
|
+
Use the tool directly in an OSS repository workflow:
|
|
97
|
+
|
|
98
|
+
```yaml
|
|
99
|
+
- uses: actions/checkout@v7
|
|
100
|
+
- uses: actions/setup-python@v7
|
|
101
|
+
with:
|
|
102
|
+
python-version: "3.13"
|
|
103
|
+
- uses: kohli217/codex-workspace-bootstrap@v0.3.0
|
|
104
|
+
with:
|
|
105
|
+
path: .
|
|
106
|
+
strict: "true"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
See [docs/GITHUB_ACTION.md](docs/GITHUB_ACTION.md) for the full workflow and input reference.
|
|
110
|
+
|
|
111
|
+
## Commands
|
|
112
|
+
|
|
113
|
+
### Audit a repository
|
|
114
|
+
|
|
115
|
+
```powershell
|
|
116
|
+
codex-workspace-bootstrap audit .
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Write JSON output:
|
|
120
|
+
|
|
121
|
+
```powershell
|
|
122
|
+
codex-workspace-bootstrap audit . --json audit-report.json
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Use strict mode in CI:
|
|
126
|
+
|
|
127
|
+
```powershell
|
|
128
|
+
codex-workspace-bootstrap audit . --strict
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### SARIF / Code Scanning
|
|
132
|
+
|
|
133
|
+
```powershell
|
|
134
|
+
codex-workspace-bootstrap audit . --sarif codex-workspace-bootstrap.sarif
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Blocking findings are emitted as SARIF errors and other warnings as SARIF warnings. See [docs/SARIF.md](docs/SARIF.md) for GitHub Code Scanning integration.
|
|
138
|
+
|
|
139
|
+
### Generate `AGENTS.md`
|
|
140
|
+
|
|
141
|
+
```powershell
|
|
142
|
+
codex-workspace-bootstrap init-agents .
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The generator inspects common project manifests and layout signals, then writes conservative validation guidance. Existing `AGENTS.md` files are never overwritten unless `--force` is explicit.
|
|
146
|
+
|
|
147
|
+
## Safety model
|
|
148
|
+
|
|
149
|
+
- The core audit path performs local inspection only.
|
|
150
|
+
- The tool does **not** read or print the contents of suspected secret files.
|
|
151
|
+
- The tool does **not** send repository contents to a remote service.
|
|
152
|
+
- A passing audit is evidence about the checks performed, **not** a security guarantee.
|
|
153
|
+
- File modifications are opt-in; existing `AGENTS.md` files are protected by default.
|
|
154
|
+
|
|
155
|
+
See [SECURITY.md](SECURITY.md) for reporting guidance.
|
|
156
|
+
|
|
157
|
+
## Maintainer workflow
|
|
158
|
+
|
|
159
|
+
This project uses an issue → branch → pull request → CI → merge → release workflow.
|
|
160
|
+
|
|
161
|
+
Current automated checks include:
|
|
162
|
+
|
|
163
|
+
- Windows and Ubuntu test matrices on Python 3.10 and 3.13;
|
|
164
|
+
- built-wheel smoke testing;
|
|
165
|
+
- strict self-audit before releases;
|
|
166
|
+
- CodeQL static analysis;
|
|
167
|
+
- weekly dependency update checks for Python and GitHub Actions;
|
|
168
|
+
- validated one-click GitHub releases with attached wheel and source distribution.
|
|
169
|
+
|
|
170
|
+
Release history began with [v0.1.0](https://github.com/kohli217/codex-workspace-bootstrap/releases/tag/v0.1.0).
|
|
171
|
+
|
|
172
|
+
## Codex-oriented workflow
|
|
173
|
+
|
|
174
|
+
A practical repository workflow is:
|
|
175
|
+
|
|
176
|
+
1. run `audit`;
|
|
177
|
+
2. review warnings and blocking findings;
|
|
178
|
+
3. generate or review `AGENTS.md`;
|
|
179
|
+
4. give Codex a scoped issue or task;
|
|
180
|
+
5. run the repository tests and the audit again;
|
|
181
|
+
6. inspect the diff before merge;
|
|
182
|
+
7. release only after CI passes.
|
|
183
|
+
|
|
184
|
+
See [AGENTS.md](AGENTS.md) for this repository's own agent instructions and [skills/codex-workspace-bootstrap/SKILL.md](skills/codex-workspace-bootstrap/SKILL.md) for the reusable skill.
|
|
185
|
+
|
|
186
|
+
## Project scope
|
|
187
|
+
|
|
188
|
+
### In scope
|
|
189
|
+
|
|
190
|
+
- Windows-first repository readiness checks;
|
|
191
|
+
- Codex-oriented project instructions;
|
|
192
|
+
- maintainer automation that is deterministic and reviewable;
|
|
193
|
+
- CI-friendly reporting and safe defaults.
|
|
194
|
+
|
|
195
|
+
### Not in scope
|
|
196
|
+
|
|
197
|
+
- claiming that a repository is secure because an audit passed;
|
|
198
|
+
- silently changing user configuration;
|
|
199
|
+
- uploading repository contents by default;
|
|
200
|
+
- replacing project-specific documentation or human review.
|
|
201
|
+
|
|
202
|
+
## Development
|
|
203
|
+
|
|
204
|
+
```powershell
|
|
205
|
+
git clone https://github.com/kohli217/codex-workspace-bootstrap.git
|
|
206
|
+
cd codex-workspace-bootstrap
|
|
207
|
+
py -m venv .venv
|
|
208
|
+
.\.venv\Scripts\Activate.ps1
|
|
209
|
+
python -m pip install -e . pytest
|
|
210
|
+
pytest -q
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md), [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md), and [SUPPORT.md](SUPPORT.md).
|
|
214
|
+
|
|
215
|
+
## Maintainer and adoption
|
|
216
|
+
|
|
217
|
+
Primary maintainer identity and responsibilities are documented in [MAINTAINERS.md](MAINTAINERS.md).
|
|
218
|
+
|
|
219
|
+
If you use the project, please share a real usage report through the GitHub issue template. The project deliberately avoids fabricated testimonials or adoption claims. See [docs/ADOPTION.md](docs/ADOPTION.md).
|
|
220
|
+
|
|
221
|
+
## Roadmap
|
|
222
|
+
|
|
223
|
+
See [docs/ROADMAP.md](docs/ROADMAP.md).
|
|
224
|
+
|
|
225
|
+
## Releasing
|
|
226
|
+
|
|
227
|
+
Maintainers can create a tested GitHub release from the Actions UI. See [docs/RELEASING.md](docs/RELEASING.md).
|
|
228
|
+
|
|
229
|
+
## Citation
|
|
230
|
+
|
|
231
|
+
Machine-readable citation metadata is available in [CITATION.cff](CITATION.cff).
|
|
232
|
+
|
|
233
|
+
## License
|
|
234
|
+
|
|
235
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
codex_workspace_bootstrap/__init__.py,sha256=0OEVFJTfc3mhl8r58ymaT2GxKMDOfbq1ChGhkkliqgY,64
|
|
2
|
+
codex_workspace_bootstrap/__main__.py,sha256=MHKZ_ae3fSLGTLUUMOx15fWdeOnJSHhq-zslRP5F5Lc,79
|
|
3
|
+
codex_workspace_bootstrap/agents.py,sha256=pxMqikz31WwMVWqFihLjM5sV-ZVgq1AhExPGBHnfikc,3445
|
|
4
|
+
codex_workspace_bootstrap/audit.py,sha256=QKWyVNZlFz5OnmPQX0uU6sMz2l_615qnTLjkNuPvPno,6522
|
|
5
|
+
codex_workspace_bootstrap/cli.py,sha256=YK9mrVXozsGSx6oKpkvIw-Vd9hUE3JU5fLp7y4T9tYo,3661
|
|
6
|
+
codex_workspace_bootstrap/sarif.py,sha256=4DolD85AP2jltI6aR8V7bIB83aZ9WUi8d8qCBleQC3U,3522
|
|
7
|
+
codex_workspace_bootstrap-0.3.0.dist-info/licenses/LICENSE,sha256=7egY_zVehKY142s1eL6Cxft2bUxYat41p-mjFNSCfKs,1065
|
|
8
|
+
codex_workspace_bootstrap-0.3.0.dist-info/METADATA,sha256=nBX03JlQMjALNM0x2wGlJ_9jshJcZyWnKiEpg0t4nKM,8752
|
|
9
|
+
codex_workspace_bootstrap-0.3.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
10
|
+
codex_workspace_bootstrap-0.3.0.dist-info/entry_points.txt,sha256=lNdzIlqHu37wfR8sRuDlU46nBs7JDEvI2fY4MLN_JJg,81
|
|
11
|
+
codex_workspace_bootstrap-0.3.0.dist-info/top_level.txt,sha256=ujZkoqd5P_E7r-xvkaBHKcvb02xsv7yvTlHNCAIUL0M,26
|
|
12
|
+
codex_workspace_bootstrap-0.3.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 kohli217
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
codex_workspace_bootstrap
|