osdlc-kit 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.
osdlc/__init__.py ADDED
File without changes
osdlc/__main__.py ADDED
@@ -0,0 +1,5 @@
1
+ import sys
2
+ from osdlc.cli import main
3
+
4
+ if __name__ == "__main__":
5
+ sys.exit(main())
osdlc/cli.py ADDED
@@ -0,0 +1,188 @@
1
+ import os
2
+ import sys
3
+ import argparse
4
+ from osdlc.detector import detect_project_type, suggest_language_by_extension
5
+ from osdlc.scaffold import scaffold
6
+
7
+
8
+ def prompt(prompt_text, default=None):
9
+ if default is not None:
10
+ user_input = input(f"{prompt_text} [{default}]: ").strip()
11
+ return user_input if user_input else default
12
+ return input(f"{prompt_text}: ").strip()
13
+
14
+
15
+ def confirm(prompt_text, default=True):
16
+ default_str = "Y/n" if default else "y/N"
17
+ user_input = input(f"{prompt_text} [{default_str}]: ").strip().lower()
18
+ if not user_input:
19
+ return default
20
+ return user_input.startswith("y")
21
+
22
+
23
+ def print_banner():
24
+ print("=" * 60)
25
+ print(" AI Open SDLC Kit - Project Scaffolding")
26
+ print("=" * 60)
27
+
28
+
29
+ def print_summary(generated, skipped, errors):
30
+ print()
31
+ print("=" * 60)
32
+ print(" Summary")
33
+ print("=" * 60)
34
+
35
+ if generated:
36
+ print(f"\n Generated ({len(generated)} files):")
37
+ for f in generated:
38
+ print(f" + {f}")
39
+
40
+ if skipped:
41
+ print(f"\n Skipped ({len(skipped)} files - already exist):")
42
+ for f in skipped:
43
+ print(f" ~ {f}")
44
+ print("\n Use --force to overwrite existing files.")
45
+
46
+ if errors:
47
+ print(f"\n Errors ({len(errors)}):")
48
+ for f, err in errors:
49
+ print(f" ! {f}: {err}")
50
+
51
+ print()
52
+ print(" Quick Start:")
53
+ print(" -------------")
54
+ print(" Run /oc analyze on any issue to start the agent-driven SDLC cycle.")
55
+ print(" Create an issue and comment with '/oc analyze' to get started.")
56
+ print()
57
+
58
+
59
+ def check_git_repo(root="."):
60
+ try:
61
+ import subprocess
62
+ result = subprocess.run(
63
+ ["git", "rev-parse", "--is-inside-work-tree"],
64
+ capture_output=True, text=True, timeout=5,
65
+ cwd=root
66
+ )
67
+ return result.returncode == 0
68
+ except Exception:
69
+ return False
70
+
71
+
72
+ def main():
73
+ parser = argparse.ArgumentParser(
74
+ description="AI Open SDLC Kit - Bootstrap your repository with SDLC methodology"
75
+ )
76
+ parser.add_argument(
77
+ "command", nargs="?", default="init",
78
+ choices=["init"],
79
+ help="Command to run (default: init)"
80
+ )
81
+ parser.add_argument(
82
+ "--force", "-f", action="store_true",
83
+ help="Overwrite existing files"
84
+ )
85
+ parser.add_argument(
86
+ "--target", "-t", default=".",
87
+ help="Target directory (default: current directory)"
88
+ )
89
+ parser.add_argument(
90
+ "--non-interactive", action="store_true",
91
+ help="Skip interactive prompts (use defaults/detected values)"
92
+ )
93
+
94
+ args = parser.parse_args()
95
+
96
+ if args.command != "init":
97
+ print(f"Unknown command: {args.command}")
98
+ return 1
99
+
100
+ root = os.path.abspath(args.target)
101
+
102
+ if not os.path.isdir(root):
103
+ print(f"Error: target directory '{root}' does not exist.")
104
+ return 1
105
+
106
+ if not check_git_repo(root):
107
+ print("Warning: Not inside a Git repository. Some features may not work.")
108
+ print("Consider running 'git init' first.")
109
+
110
+ print_banner()
111
+
112
+ detected = detect_project_type(root)
113
+ ext_lang = suggest_language_by_extension(root)
114
+
115
+ if detected:
116
+ print(f"\n Detected: {detected['language']} ({detected['build_system']})")
117
+ elif ext_lang:
118
+ print(f"\n Suggested language (by file extension): {ext_lang}")
119
+ else:
120
+ print("\n No build system detected. You will be prompted for project details.")
121
+
122
+ print()
123
+
124
+ if args.non_interactive:
125
+ if detected:
126
+ config = detected.copy()
127
+ else:
128
+ config = {
129
+ "build_system": "unknown",
130
+ "language": ext_lang or "unknown",
131
+ "version_file": "VERSION",
132
+ "build_cmd": "echo 'no build command configured'",
133
+ "test_cmd": "echo 'no test command configured'",
134
+ "lint_cmd": "echo 'no lint command configured'",
135
+ "probe": "manual",
136
+ }
137
+ config.setdefault("project_name", os.path.basename(root))
138
+ config.setdefault("default_branch", "main")
139
+ config.setdefault("error_to_issue", False)
140
+ config.setdefault("provider_google", "")
141
+ config.setdefault("model", "opencode/deepseek-v4-flash-free")
142
+ config.setdefault("env_notes", "No special environment constraints.")
143
+ config.setdefault("architectural_notes", "")
144
+ else:
145
+ config = {}
146
+
147
+ template = detected or {}
148
+
149
+ project_name = prompt("Project name", default=os.path.basename(root))
150
+ config["project_name"] = project_name
151
+
152
+ config["default_branch"] = prompt("Default branch name", default="main")
153
+
154
+ version_file_default = template.get("version_file", "VERSION")
155
+ config["version_file"] = prompt("Version config file path", default=version_file_default)
156
+
157
+ config["build_cmd"] = prompt("Build command", default=template.get("build_cmd", "echo 'no build'"))
158
+ config["test_cmd"] = prompt("Test command", default=template.get("test_cmd", "echo 'no tests'"))
159
+ config["lint_cmd"] = prompt("Lint command", default=template.get("lint_cmd", "echo 'no linter'"))
160
+
161
+ config["language"] = template.get("language", ext_lang or prompt("Language", default="unknown"))
162
+ config["build_system"] = template.get("build_system", prompt("Build system", default="manual"))
163
+ config["probe"] = template.get("probe", "manual")
164
+
165
+ error_to_issue = confirm("Enable error-to-issue pipeline?", default=False)
166
+ config["error_to_issue"] = error_to_issue
167
+
168
+ if error_to_issue:
169
+ config["github_token_env"] = prompt("GITHUB_TOKEN environment variable name", default="GITHUB_TOKEN")
170
+ config["provider_google"] = ',\n "google": {\n "options": {\n "timeout": 300000,\n "chunkTimeout": 60000\n }\n }'
171
+ else:
172
+ config["provider_google"] = ""
173
+
174
+ config["model"] = prompt("Default model", default="opencode/deepseek-v4-flash-free")
175
+
176
+ config["env_notes"] = prompt("Environment notes (optional)", default="No special environment constraints.")
177
+ config["architectural_notes"] = prompt("Architectural notes (optional)", default="")
178
+
179
+ print("Scaffolding project...")
180
+ generated, skipped, errors = scaffold(config, root=root, force=args.force)
181
+
182
+ print_summary(generated, skipped, errors)
183
+
184
+ return 0 if not errors else 1
185
+
186
+
187
+ if __name__ == "__main__":
188
+ sys.exit(main())
osdlc/detector.py ADDED
@@ -0,0 +1,67 @@
1
+ import os
2
+
3
+ PROBES = [
4
+ ("pom.xml", "Maven", "Java", "pom.xml", "mvn compile", "mvn test", "mvn checkstyle:check"),
5
+ ("build.gradle.kts", "Gradle Kotlin","Kotlin/Java","gradle.properties", "./gradlew build", "./gradlew test", "./gradlew ktlintCheck"),
6
+ ("build.gradle", "Gradle", "Java", "gradle.properties", "./gradlew build", "./gradlew test", "./gradlew check"),
7
+ ("yarn.lock", "Yarn", "JavaScript", "package.json", "yarn build", "yarn test", "yarn lint"),
8
+ ("pnpm-lock.yaml", "pnpm", "JavaScript", "package.json", "pnpm build", "pnpm test", "pnpm lint"),
9
+ ("package.json", "npm", "JavaScript", "package.json", "npm run build", "npm test", "npm run lint"),
10
+ ("Cargo.toml", "Cargo", "Rust", "Cargo.toml", "cargo build", "cargo test", "cargo clippy"),
11
+ ("pyproject.toml", "PEP 621", "Python", "pyproject.toml", "python -m build", "pytest", "ruff check ."),
12
+ ("setup.py", "setuptools", "Python", "VERSION", "python setup.py sdist", "pytest", "ruff check ."),
13
+ ("requirements.txt", "pip", "Python", "VERSION", "pip install -r requirements.txt", "pytest", "ruff check ."),
14
+ ("go.mod", "Go modules", "Go", "version.go", "go build ./...", "go test ./...", "go vet ./..."),
15
+ ("composer.json", "Composer", "PHP", "composer.json", "composer install", "phpunit", "phpcs"),
16
+ ("Gemfile", "Bundler", "Ruby", "lib/version.rb", "bundle exec rake build", "bundle exec rspec", "bundle exec rubocop"),
17
+ ("CMakeLists.txt", "CMake", "C/C++", "CMakeLists.txt", "cmake --build build", "ctest", "cmake --build build --target lint"),
18
+ ("mix.exs", "Mix", "Elixir", "mix.exs", "mix compile", "mix test", "mix credo"),
19
+ ]
20
+
21
+ LANGUAGE_EXTENSIONS = {
22
+ ".kt": "Kotlin",
23
+ ".kts": "Kotlin",
24
+ ".java": "Java",
25
+ ".js": "JavaScript",
26
+ ".ts": "TypeScript",
27
+ ".jsx": "React",
28
+ ".tsx": "React TypeScript",
29
+ ".rs": "Rust",
30
+ ".py": "Python",
31
+ ".go": "Go",
32
+ ".rb": "Ruby",
33
+ ".ex": "Elixir",
34
+ ".exs": "Elixir",
35
+ ".php": "PHP",
36
+ ".cs": "C#",
37
+ ".swift":"Swift",
38
+ }
39
+
40
+
41
+ def detect_project_type(root="."):
42
+ for probe, build_system, language, version_file, build_cmd, test_cmd, lint_cmd in PROBES:
43
+ path = os.path.join(root, probe)
44
+ if os.path.exists(path):
45
+ return {
46
+ "build_system": build_system,
47
+ "language": language,
48
+ "version_file": version_file,
49
+ "build_cmd": build_cmd,
50
+ "test_cmd": test_cmd,
51
+ "lint_cmd": lint_cmd,
52
+ "probe": probe,
53
+ }
54
+ return None
55
+
56
+
57
+ def suggest_language_by_extension(root="."):
58
+ counts = {}
59
+ for entry in os.scandir(root):
60
+ if entry.is_file():
61
+ _, ext = os.path.splitext(entry.name)
62
+ if ext in LANGUAGE_EXTENSIONS:
63
+ lang = LANGUAGE_EXTENSIONS[ext]
64
+ counts[lang] = counts.get(lang, 0) + 1
65
+ if counts:
66
+ return max(counts, key=counts.get)
67
+ return None
osdlc/scaffold.py ADDED
@@ -0,0 +1,206 @@
1
+ import os
2
+ from osdlc.templates import ALL_TEMPLATES
3
+
4
+
5
+ def detect_codeql_languages(language):
6
+ mapping = {
7
+ "Java": "java-kotlin",
8
+ "Kotlin": "java-kotlin",
9
+ "Kotlin/Java": "java-kotlin",
10
+ "JavaScript": "javascript-typescript",
11
+ "TypeScript": "javascript-typescript",
12
+ "Python": "python",
13
+ "Rust": "rust",
14
+ "Go": "go",
15
+ "Ruby": "ruby",
16
+ "PHP": "python",
17
+ "C/C++": "cpp",
18
+ "C#": "csharp",
19
+ "Swift": "swift",
20
+ "Elixir": "python",
21
+ }
22
+ return mapping.get(language, "python")
23
+
24
+
25
+ def detect_ecosystem(build_system):
26
+ mapping = {
27
+ "Maven": "maven",
28
+ "Gradle Kotlin": "gradle",
29
+ "Gradle": "gradle",
30
+ "npm": "npm",
31
+ "Yarn": "npm",
32
+ "pnpm": "npm",
33
+ "Cargo": "cargo",
34
+ "PEP 621": "pip",
35
+ "setuptools": "pip",
36
+ "pip": "pip",
37
+ "Go modules": "go_mod",
38
+ "Composer": "composer",
39
+ "Bundler": "bundler",
40
+ "CMake": "cmake",
41
+ "Mix": "mix",
42
+ }
43
+ return mapping.get(build_system, "pip")
44
+
45
+
46
+ def detect_language_setup_step(language):
47
+ mapping = {
48
+ "Java": """\
49
+ - name: Set up JDK 17
50
+ uses: actions/setup-java@v5
51
+ with:
52
+ java-version: '17'
53
+ distribution: 'temurin'
54
+
55
+ - name: Setup Gradle
56
+ uses: gradle/actions/setup-gradle@v3""",
57
+ "Kotlin/Java": """\
58
+ - name: Set up JDK 17
59
+ uses: actions/setup-java@v5
60
+ with:
61
+ java-version: '17'
62
+ distribution: 'temurin'
63
+
64
+ - name: Setup Gradle
65
+ uses: gradle/actions/setup-gradle@v3""",
66
+ "JavaScript": """\
67
+ - name: Setup Node.js
68
+ uses: actions/setup-node@v4
69
+ with:
70
+ node-version: 'latest'
71
+
72
+ - name: Install dependencies
73
+ run: npm ci""",
74
+ "Python": """\
75
+ - name: Setup Python
76
+ uses: actions/setup-python@v5
77
+ with:
78
+ python-version: '3.x'
79
+
80
+ - name: Install dependencies
81
+ run: pip install .""",
82
+ "Rust": """\
83
+ - name: Setup Rust
84
+ uses: dtolnay/action-rust-toolchain@stable""",
85
+ "Go": """\
86
+ - name: Setup Go
87
+ uses: actions/setup-go@v5
88
+ with:
89
+ go-version: 'stable'""",
90
+ "Ruby": """\
91
+ - name: Setup Ruby
92
+ uses: ruby/setup-ruby@v1
93
+ with:
94
+ ruby-version: '3.x'
95
+ bundler-cache: true""",
96
+ "PHP": """\
97
+ - name: Setup PHP
98
+ uses: shivammathur/setup-php@v2
99
+ with:
100
+ php-version: '8.2'""",
101
+ "C/C++": """\
102
+ - name: Setup CMake
103
+ uses: lukka/get-cmake@latest""",
104
+ "Elixir": """\
105
+ - name: Setup Elixir
106
+ uses: erlef/setup-beam@v1
107
+ with:
108
+ elixir-version: 'latest'
109
+ otp-version: 'latest'""",
110
+ }
111
+ return mapping.get(language, "")
112
+
113
+
114
+ def build_template_vars(config):
115
+ language = config["language"]
116
+ build_system = config["build_system"]
117
+ codeql_lang = detect_codeql_languages(language)
118
+ ecosystem = detect_ecosystem(build_system)
119
+ lang_setup = detect_language_setup_step(language)
120
+
121
+ l = language.lower()
122
+ validate_java = "true" if l in ("java", "kotlin", "kotlin/java") else "false"
123
+ validate_kotlin = "true" if l in ("kotlin", "kotlin/java") else "false"
124
+ validate_javascript = "true" if l in ("javascript", "typescript", "react", "react typescript") else "false"
125
+ validate_typescript = "true" if l in ("typescript", "react typescript") else "false"
126
+ validate_python = "true" if l == "python" else "false"
127
+ validate_go = "true" if l == "go" else "false"
128
+ validate_rust = "true" if l == "rust" else "false"
129
+ validate_ruby = "true" if l == "ruby" else "false"
130
+ validate_php = "true" if l == "php" else "false"
131
+ validate_xml = "true" if l in ("java", "kotlin", "kotlin/java") else "false"
132
+ validate_properties = "true" if l in ("java", "kotlin", "kotlin/java") else "false"
133
+
134
+ return {
135
+ "project_name": config.get("project_name", "my-project"),
136
+ "default_branch": config["default_branch"],
137
+ "version_file": config["version_file"],
138
+ "build_cmd": config["build_cmd"],
139
+ "test_cmd": config["test_cmd"],
140
+ "lint_cmd": config["lint_cmd"],
141
+ "language_description": f"This project uses {language} with the {build_system} build system ({config['probe']}).",
142
+ "env_notes": config.get("env_notes", "No special environment constraints."),
143
+ "architectural_notes": config.get("architectural_notes", ""),
144
+ "model": config.get("model", "opencode/deepseek-v4-flash-free"),
145
+ "provider_google": config.get("provider_google", ""),
146
+ "codeql_languages": repr(codeql_lang),
147
+ "ecosystem": ecosystem,
148
+ "language_setup": lang_setup,
149
+ "validate_java": validate_java,
150
+ "validate_kotlin": validate_kotlin,
151
+ "validate_javascript": validate_javascript,
152
+ "validate_typescript": validate_typescript,
153
+ "validate_python": validate_python,
154
+ "validate_go": validate_go,
155
+ "validate_rust": validate_rust,
156
+ "validate_ruby": validate_ruby,
157
+ "validate_php": validate_php,
158
+ "validate_xml": validate_xml,
159
+ "validate_properties": validate_properties,
160
+ }
161
+
162
+
163
+ def render_template(template, vars_dict):
164
+ result = template
165
+ for key, value in vars_dict.items():
166
+ placeholder = "{" + key + "}"
167
+ result = result.replace(placeholder, str(value))
168
+ return result
169
+
170
+
171
+ def scaffold(config, root=".", force=False):
172
+ vars_dict = build_template_vars(config)
173
+ generated = []
174
+ skipped = []
175
+ errors = []
176
+
177
+ templates = dict(ALL_TEMPLATES)
178
+ if not config.get("error_to_issue"):
179
+ templates.pop(".opencode/skills/error-handling/SKILL.md", None)
180
+
181
+ dirs_to_create = set()
182
+ for filepath in templates:
183
+ full_path = os.path.join(root, filepath)
184
+ parent = os.path.dirname(full_path)
185
+ if parent:
186
+ dirs_to_create.add(parent)
187
+
188
+ for d in sorted(dirs_to_create):
189
+ os.makedirs(d, exist_ok=True)
190
+
191
+ for filepath, template in templates.items():
192
+ full_path = os.path.join(root, filepath)
193
+
194
+ if os.path.exists(full_path) and not force:
195
+ skipped.append(filepath)
196
+ continue
197
+
198
+ try:
199
+ content = render_template(template, vars_dict)
200
+ with open(full_path, "w", newline="\n", encoding="utf-8") as f:
201
+ f.write(content)
202
+ generated.append(filepath)
203
+ except Exception as e:
204
+ errors.append((filepath, str(e)))
205
+
206
+ return generated, skipped, errors