@vyuhlabs/dxkit 2.4.7 → 2.5.0

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.
Files changed (309) hide show
  1. package/CHANGELOG.md +456 -30
  2. package/README.md +360 -439
  3. package/dist/analyzers/bom/gather.d.ts +3 -3
  4. package/dist/analyzers/bom/gather.js +3 -3
  5. package/dist/analyzers/bom/index.js +2 -2
  6. package/dist/analyzers/bom/index.js.map +1 -1
  7. package/dist/analyzers/dashboard/index.d.ts.map +1 -1
  8. package/dist/analyzers/dashboard/index.js +4 -3
  9. package/dist/analyzers/dashboard/index.js.map +1 -1
  10. package/dist/analyzers/developer/index.d.ts.map +1 -1
  11. package/dist/analyzers/developer/index.js +2 -1
  12. package/dist/analyzers/developer/index.js.map +1 -1
  13. package/dist/analyzers/dispatcher.d.ts +15 -0
  14. package/dist/analyzers/dispatcher.d.ts.map +1 -1
  15. package/dist/analyzers/dispatcher.js +42 -6
  16. package/dist/analyzers/dispatcher.js.map +1 -1
  17. package/dist/analyzers/health.d.ts.map +1 -1
  18. package/dist/analyzers/health.js +11 -1
  19. package/dist/analyzers/health.js.map +1 -1
  20. package/dist/analyzers/licenses/gather.d.ts +1 -1
  21. package/dist/analyzers/licenses/gather.d.ts.map +1 -1
  22. package/dist/analyzers/licenses/gather.js +18 -2
  23. package/dist/analyzers/licenses/gather.js.map +1 -1
  24. package/dist/analyzers/quality/index.d.ts.map +1 -1
  25. package/dist/analyzers/quality/index.js +10 -2
  26. package/dist/analyzers/quality/index.js.map +1 -1
  27. package/dist/analyzers/security/aggregator.d.ts.map +1 -1
  28. package/dist/analyzers/security/aggregator.js +8 -48
  29. package/dist/analyzers/security/aggregator.js.map +1 -1
  30. package/dist/analyzers/security/gather.d.ts +4 -3
  31. package/dist/analyzers/security/gather.d.ts.map +1 -1
  32. package/dist/analyzers/security/gather.js +23 -5
  33. package/dist/analyzers/security/gather.js.map +1 -1
  34. package/dist/analyzers/security/index.d.ts +1 -1
  35. package/dist/analyzers/security/index.js +2 -2
  36. package/dist/analyzers/security/index.js.map +1 -1
  37. package/dist/analyzers/tools/autogen-header.js +1 -1
  38. package/dist/analyzers/tools/cloc.js +3 -3
  39. package/dist/analyzers/tools/cloc.js.map +1 -1
  40. package/dist/analyzers/tools/deadline.d.ts +67 -0
  41. package/dist/analyzers/tools/deadline.d.ts.map +1 -0
  42. package/dist/analyzers/tools/deadline.js +81 -0
  43. package/dist/analyzers/tools/deadline.js.map +1 -0
  44. package/dist/analyzers/tools/exclusions.d.ts +6 -6
  45. package/dist/analyzers/tools/exclusions.js +6 -6
  46. package/dist/analyzers/tools/fingerprint.d.ts +91 -26
  47. package/dist/analyzers/tools/fingerprint.d.ts.map +1 -1
  48. package/dist/analyzers/tools/fingerprint.js +111 -22
  49. package/dist/analyzers/tools/fingerprint.js.map +1 -1
  50. package/dist/analyzers/tools/generic.d.ts.map +1 -1
  51. package/dist/analyzers/tools/generic.js +7 -2
  52. package/dist/analyzers/tools/generic.js.map +1 -1
  53. package/dist/analyzers/tools/gitleaks.d.ts +24 -1
  54. package/dist/analyzers/tools/gitleaks.d.ts.map +1 -1
  55. package/dist/analyzers/tools/gitleaks.js +21 -12
  56. package/dist/analyzers/tools/gitleaks.js.map +1 -1
  57. package/dist/analyzers/tools/graphify.js +1 -1
  58. package/dist/analyzers/tools/jscpd.js +1 -1
  59. package/dist/analyzers/tools/jscpd.js.map +1 -1
  60. package/dist/analyzers/tools/lint-label.d.ts +29 -0
  61. package/dist/analyzers/tools/lint-label.d.ts.map +1 -0
  62. package/dist/analyzers/tools/lint-label.js +23 -0
  63. package/dist/analyzers/tools/lint-label.js.map +1 -0
  64. package/dist/analyzers/tools/nuget-package-reference.d.ts +6 -4
  65. package/dist/analyzers/tools/nuget-package-reference.d.ts.map +1 -1
  66. package/dist/analyzers/tools/nuget-package-reference.js +7 -5
  67. package/dist/analyzers/tools/nuget-package-reference.js.map +1 -1
  68. package/dist/analyzers/tools/report-date.d.ts +17 -0
  69. package/dist/analyzers/tools/report-date.d.ts.map +1 -0
  70. package/dist/analyzers/tools/report-date.js +26 -0
  71. package/dist/analyzers/tools/report-date.js.map +1 -0
  72. package/dist/analyzers/tools/runner.js +3 -3
  73. package/dist/analyzers/tools/runner.js.map +1 -1
  74. package/dist/analyzers/tools/vendored-advisor.js +1 -1
  75. package/dist/analyzers/tools/walk-paths.d.ts +1 -1
  76. package/dist/analyzers/tools/walk-paths.js +1 -1
  77. package/dist/analyzers/tools/walk-source-files.js +1 -1
  78. package/dist/analyzers/types.d.ts +6 -4
  79. package/dist/analyzers/types.d.ts.map +1 -1
  80. package/dist/baseline/baseline-file.d.ts +104 -0
  81. package/dist/baseline/baseline-file.d.ts.map +1 -0
  82. package/dist/baseline/baseline-file.js +110 -0
  83. package/dist/baseline/baseline-file.js.map +1 -0
  84. package/dist/baseline/check-renderers.d.ts +108 -0
  85. package/dist/baseline/check-renderers.d.ts.map +1 -0
  86. package/dist/baseline/check-renderers.js +379 -0
  87. package/dist/baseline/check-renderers.js.map +1 -0
  88. package/dist/baseline/check.d.ts +127 -0
  89. package/dist/baseline/check.d.ts.map +1 -0
  90. package/dist/baseline/check.js +462 -0
  91. package/dist/baseline/check.js.map +1 -0
  92. package/dist/baseline/content-hash.d.ts +83 -0
  93. package/dist/baseline/content-hash.d.ts.map +1 -0
  94. package/dist/baseline/content-hash.js +131 -0
  95. package/dist/baseline/content-hash.js.map +1 -0
  96. package/dist/baseline/create.d.ts +96 -0
  97. package/dist/baseline/create.d.ts.map +1 -0
  98. package/dist/baseline/create.js +339 -0
  99. package/dist/baseline/create.js.map +1 -0
  100. package/dist/baseline/entry-to-located.d.ts +35 -0
  101. package/dist/baseline/entry-to-located.d.ts.map +1 -0
  102. package/dist/baseline/entry-to-located.js +72 -0
  103. package/dist/baseline/entry-to-located.js.map +1 -0
  104. package/dist/baseline/finding-identity.d.ts +47 -0
  105. package/dist/baseline/finding-identity.d.ts.map +1 -0
  106. package/dist/baseline/finding-identity.js +292 -0
  107. package/dist/baseline/finding-identity.js.map +1 -0
  108. package/dist/baseline/git-aware-match.d.ts +146 -0
  109. package/dist/baseline/git-aware-match.d.ts.map +1 -0
  110. package/dist/baseline/git-aware-match.js +439 -0
  111. package/dist/baseline/git-aware-match.js.map +1 -0
  112. package/dist/baseline/policy.d.ts +171 -0
  113. package/dist/baseline/policy.d.ts.map +1 -0
  114. package/dist/baseline/policy.js +206 -0
  115. package/dist/baseline/policy.js.map +1 -0
  116. package/dist/baseline/producers/health.d.ts +30 -0
  117. package/dist/baseline/producers/health.d.ts.map +1 -0
  118. package/dist/baseline/producers/health.js +42 -0
  119. package/dist/baseline/producers/health.js.map +1 -0
  120. package/dist/baseline/producers/index.d.ts +164 -0
  121. package/dist/baseline/producers/index.d.ts.map +1 -0
  122. package/dist/baseline/producers/index.js +200 -0
  123. package/dist/baseline/producers/index.js.map +1 -0
  124. package/dist/baseline/producers/licenses.d.ts +23 -0
  125. package/dist/baseline/producers/licenses.d.ts.map +1 -0
  126. package/dist/baseline/producers/licenses.js +46 -0
  127. package/dist/baseline/producers/licenses.js.map +1 -0
  128. package/dist/baseline/producers/quality.d.ts +39 -0
  129. package/dist/baseline/producers/quality.d.ts.map +1 -0
  130. package/dist/baseline/producers/quality.js +84 -0
  131. package/dist/baseline/producers/quality.js.map +1 -0
  132. package/dist/baseline/producers/secret-hmac.d.ts +45 -0
  133. package/dist/baseline/producers/secret-hmac.d.ts.map +1 -0
  134. package/dist/baseline/producers/secret-hmac.js +70 -0
  135. package/dist/baseline/producers/secret-hmac.js.map +1 -0
  136. package/dist/baseline/producers/security.d.ts +59 -0
  137. package/dist/baseline/producers/security.d.ts.map +1 -0
  138. package/dist/baseline/producers/security.js +135 -0
  139. package/dist/baseline/producers/security.js.map +1 -0
  140. package/dist/baseline/producers/tests.d.ts +36 -0
  141. package/dist/baseline/producers/tests.d.ts.map +1 -0
  142. package/dist/baseline/producers/tests.js +69 -0
  143. package/dist/baseline/producers/tests.js.map +1 -0
  144. package/dist/baseline/salt.d.ts +45 -0
  145. package/dist/baseline/salt.d.ts.map +1 -0
  146. package/dist/baseline/salt.js +113 -0
  147. package/dist/baseline/salt.js.map +1 -0
  148. package/dist/baseline/show.d.ts +79 -0
  149. package/dist/baseline/show.d.ts.map +1 -0
  150. package/dist/baseline/show.js +233 -0
  151. package/dist/baseline/show.js.map +1 -0
  152. package/dist/baseline/types.d.ts +482 -0
  153. package/dist/baseline/types.d.ts.map +1 -0
  154. package/dist/baseline/types.js +53 -0
  155. package/dist/baseline/types.js.map +1 -0
  156. package/dist/cli.d.ts.map +1 -1
  157. package/dist/cli.js +395 -92
  158. package/dist/cli.js.map +1 -1
  159. package/dist/codebase-scanner.d.ts.map +1 -1
  160. package/dist/codebase-scanner.js +0 -1
  161. package/dist/codebase-scanner.js.map +1 -1
  162. package/dist/constants.d.ts.map +1 -1
  163. package/dist/constants.js +0 -4
  164. package/dist/constants.js.map +1 -1
  165. package/dist/detect.js +3 -3
  166. package/dist/detect.js.map +1 -1
  167. package/dist/doctor.d.ts.map +1 -1
  168. package/dist/doctor.js +22 -25
  169. package/dist/doctor.js.map +1 -1
  170. package/dist/fail-on.d.ts +84 -0
  171. package/dist/fail-on.d.ts.map +1 -0
  172. package/dist/fail-on.js +128 -0
  173. package/dist/fail-on.js.map +1 -0
  174. package/dist/generator.d.ts.map +1 -1
  175. package/dist/generator.js +2 -141
  176. package/dist/generator.js.map +1 -1
  177. package/dist/languages/capabilities/provider.d.ts +4 -4
  178. package/dist/languages/capabilities/types.d.ts +1 -1
  179. package/dist/languages/csharp.d.ts.map +1 -1
  180. package/dist/languages/csharp.js +15 -24
  181. package/dist/languages/csharp.js.map +1 -1
  182. package/dist/languages/go.d.ts.map +1 -1
  183. package/dist/languages/go.js +0 -15
  184. package/dist/languages/go.js.map +1 -1
  185. package/dist/languages/index.d.ts +4 -3
  186. package/dist/languages/index.d.ts.map +1 -1
  187. package/dist/languages/index.js +3 -2
  188. package/dist/languages/index.js.map +1 -1
  189. package/dist/languages/java.d.ts.map +1 -1
  190. package/dist/languages/java.js +0 -6
  191. package/dist/languages/java.js.map +1 -1
  192. package/dist/languages/kotlin.d.ts.map +1 -1
  193. package/dist/languages/kotlin.js +0 -11
  194. package/dist/languages/kotlin.js.map +1 -1
  195. package/dist/languages/python.d.ts.map +1 -1
  196. package/dist/languages/python.js +0 -15
  197. package/dist/languages/python.js.map +1 -1
  198. package/dist/languages/ruby.d.ts.map +1 -1
  199. package/dist/languages/ruby.js +0 -6
  200. package/dist/languages/ruby.js.map +1 -1
  201. package/dist/languages/rust.d.ts.map +1 -1
  202. package/dist/languages/rust.js +0 -4
  203. package/dist/languages/rust.js.map +1 -1
  204. package/dist/languages/types.d.ts +9 -35
  205. package/dist/languages/types.d.ts.map +1 -1
  206. package/dist/languages/typescript.d.ts.map +1 -1
  207. package/dist/languages/typescript.js +26 -4
  208. package/dist/languages/typescript.js.map +1 -1
  209. package/dist/lib.d.ts +2 -3
  210. package/dist/lib.d.ts.map +1 -1
  211. package/dist/lib.js +3 -6
  212. package/dist/lib.js.map +1 -1
  213. package/dist/prompts.d.ts.map +1 -1
  214. package/dist/prompts.js +0 -10
  215. package/dist/prompts.js.map +1 -1
  216. package/dist/report-schema.d.ts +42 -0
  217. package/dist/report-schema.d.ts.map +1 -0
  218. package/dist/report-schema.js +54 -0
  219. package/dist/report-schema.js.map +1 -0
  220. package/dist/ship-installers.d.ts +106 -0
  221. package/dist/ship-installers.d.ts.map +1 -0
  222. package/dist/ship-installers.js +415 -0
  223. package/dist/ship-installers.js.map +1 -0
  224. package/dist/types.d.ts +0 -4
  225. package/dist/types.d.ts.map +1 -1
  226. package/dist/update.d.ts.map +1 -1
  227. package/dist/update.js +0 -4
  228. package/dist/update.js.map +1 -1
  229. package/package.json +17 -11
  230. package/templates/.claude/agents/onboarding.md +5 -4
  231. package/templates/.claude/agents-available/codebase-explorer.md +1 -1
  232. package/templates/.claude/agents-available/debugger.md +2 -2
  233. package/templates/.claude/agents-available/health-auditor.md +2 -2
  234. package/templates/.claude/commands/doctor.md +20 -12
  235. package/templates/.claude/skills/build/SKILL.md.template +22 -30
  236. package/templates/.claude/skills/deploy/SKILL.md.template +5 -25
  237. package/templates/.claude/skills/doctor/SKILL.md +24 -47
  238. package/templates/.claude/skills/gcloud/SKILL.md +5 -5
  239. package/templates/.claude/skills/learned/SKILL.md +1 -1
  240. package/templates/.claude/skills/pulumi/SKILL.md +2 -2
  241. package/templates/.claude/skills/quality/SKILL.md.template +4 -23
  242. package/templates/.claude/skills/review/SKILL.md.template +4 -3
  243. package/templates/.claude/skills/scaffold/SKILL.md.template +5 -15
  244. package/templates/.claude/skills/secrets/SKILL.md +20 -21
  245. package/templates/.claude/skills/session/SKILL.md +20 -31
  246. package/templates/.claude/skills/test/SKILL.md.template +1 -7
  247. package/templates/.devcontainer/devcontainer.json +81 -0
  248. package/templates/.devcontainer/install-agent-clis.sh +42 -0
  249. package/templates/.devcontainer/post-create.sh +67 -0
  250. package/templates/.githooks/pre-commit +55 -0
  251. package/templates/.githooks/pre-push +63 -0
  252. package/templates/.github/workflows/dxkit-baseline-refresh.yml +78 -0
  253. package/templates/.github/workflows/dxkit-guardrails.yml +98 -0
  254. package/templates/CLAUDE.md.template +62 -196
  255. package/dist/project-yaml.d.ts +0 -13
  256. package/dist/project-yaml.d.ts.map +0 -1
  257. package/dist/project-yaml.js +0 -188
  258. package/dist/project-yaml.js.map +0 -1
  259. package/templates/.ai/README.md +0 -117
  260. package/templates/.ai/prompts/execution-prompt.md +0 -9
  261. package/templates/.ai/prompts/planning-prompt.md +0 -18
  262. package/templates/.ai/prompts/session-end-template.md +0 -182
  263. package/templates/.ai/prompts/session-end.md +0 -132
  264. package/templates/.ai/prompts/session-start.md +0 -109
  265. package/templates/.ai/prompts/step-by-step.md +0 -113
  266. package/templates/.ai/sessions/.gitkeep +0 -0
  267. package/templates/.claude/commands/setup-pr-review.md +0 -72
  268. package/templates/.devcontainer/Dockerfile.dev.template +0 -89
  269. package/templates/.devcontainer/devcontainer.json.template +0 -184
  270. package/templates/.devcontainer/docker-compose.yml.template +0 -105
  271. package/templates/.devcontainer/init-scripts/01-init.sql.template +0 -12
  272. package/templates/.devcontainer/post-create.sh.template +0 -298
  273. package/templates/.github/workflows/ci.yml.template +0 -399
  274. package/templates/.github/workflows/quality.yml.template +0 -376
  275. package/templates/.pre-commit-config.yaml.template +0 -106
  276. package/templates/.project/config/edit_config.py +0 -275
  277. package/templates/.project/config/project_config.py +0 -894
  278. package/templates/.project/scripts/codegen/generate-all.sh +0 -20
  279. package/templates/.project/scripts/codegen/validate-all.sh +0 -17
  280. package/templates/.project/scripts/docs/generate-all.sh +0 -30
  281. package/templates/.project/scripts/docs/serve.sh +0 -20
  282. package/templates/.project/scripts/quality/fix-all.sh +0 -138
  283. package/templates/.project/scripts/quality/lint-go.sh +0 -34
  284. package/templates/.project/scripts/quality/lint-python.sh +0 -54
  285. package/templates/.project/scripts/quality/run-all.sh +0 -497
  286. package/templates/.project/scripts/session/commit.sh +0 -70
  287. package/templates/.project/scripts/session/create-pr.sh +0 -165
  288. package/templates/.project/scripts/session/end.sh +0 -207
  289. package/templates/.project/scripts/session/start.sh +0 -233
  290. package/templates/.project/scripts/setup/doctor.sh +0 -404
  291. package/templates/.project/scripts/setup/interactive-setup.sh +0 -585
  292. package/templates/.project/scripts/sync/sync-template.sh +0 -328
  293. package/templates/.project/scripts/test/run-all.sh +0 -179
  294. package/templates/.project/scripts/test/run-quick.sh +0 -25
  295. package/templates/Makefile +0 -514
  296. package/templates/config/versions.yaml +0 -57
  297. package/templates/configs/go/.golangci.yml.template +0 -172
  298. package/templates/configs/go/go.mod.template +0 -15
  299. package/templates/configs/java/README.md +0 -6
  300. package/templates/configs/kotlin/README.md +0 -6
  301. package/templates/configs/node/package.json.template +0 -67
  302. package/templates/configs/node/tsconfig.json.template +0 -53
  303. package/templates/configs/python/pyproject.toml.template +0 -92
  304. package/templates/configs/python/pytest.ini.template +0 -64
  305. package/templates/configs/python/ruff.toml.template +0 -79
  306. package/templates/configs/ruby/README.md +0 -6
  307. package/templates/configs/rust/Cargo.toml.template +0 -51
  308. package/templates/configs/shared/.editorconfig +0 -67
  309. package/templates/scripts/validate-templates.sh +0 -449
@@ -1,894 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Project Configuration Manager
4
- ==============================
5
-
6
- Reads and manages .project.yaml configuration for multi-language projects.
7
-
8
- USAGE
9
- -----
10
- # Display project info dashboard
11
- python3 project_config.py info
12
-
13
- # Get/set a specific value
14
- python3 project_config.py get languages.python.version
15
- python3 project_config.py set languages.python.quality.coverage 85
16
-
17
- # Quality presets
18
- python3 project_config.py preset # list presets
19
- python3 project_config.py preset strict # apply strict preset
20
- python3 project_config.py preset relaxed # apply relaxed preset
21
-
22
- # Language management
23
- python3 project_config.py lang-list # list languages
24
- python3 project_config.py lang-add python # enable python
25
- python3 project_config.py lang-remove go # disable go
26
-
27
- # Sync config to language files
28
- python3 project_config.py sync # create/update config files
29
- python3 project_config.py sync --dry-run # preview changes
30
-
31
- # Export as shell variables (for CI/scripts)
32
- python3 project_config.py export
33
- python3 project_config.py export python # only python vars
34
-
35
- # Initialize default config
36
- python3 project_config.py init
37
- """
38
-
39
- from __future__ import annotations
40
-
41
- import contextlib
42
- import json
43
- import sys
44
- from importlib.util import find_spec
45
- from pathlib import Path
46
- from typing import Any
47
-
48
-
49
- # Check if yaml is available
50
- HAS_YAML = find_spec("yaml") is not None
51
- if HAS_YAML:
52
- import yaml
53
-
54
-
55
- CONFIG_FILE = ".project.yaml"
56
-
57
- # Quality presets
58
- QUALITY_PRESETS = {
59
- "strict": {
60
- "description": "Production-ready: High coverage, all checks enabled",
61
- "python": {"coverage": 90, "lint": True, "typecheck": True, "format": True},
62
- "go": {"coverage": 80, "lint": True, "format": True},
63
- "node": {"coverage": 85, "lint": True, "typecheck": True, "format": True},
64
- "rust": {"coverage": 75, "lint": True, "format": True},
65
- },
66
- "standard": {
67
- "description": "Balanced: Moderate thresholds, essential checks",
68
- "python": {"coverage": 80, "lint": True, "typecheck": True, "format": True},
69
- "go": {"coverage": 70, "lint": True, "format": True},
70
- "node": {"coverage": 75, "lint": True, "typecheck": True, "format": True},
71
- "rust": {"coverage": 60, "lint": True, "format": True},
72
- },
73
- "relaxed": {
74
- "description": "Rapid prototyping: Lower thresholds, flexible checks",
75
- "python": {"coverage": 50, "lint": True, "typecheck": False, "format": True},
76
- "go": {"coverage": 40, "lint": True, "format": True},
77
- "node": {"coverage": 50, "lint": True, "typecheck": False, "format": True},
78
- "rust": {"coverage": 30, "lint": True, "format": True},
79
- },
80
- "off": {
81
- "description": "No enforcement: All quality checks disabled",
82
- "python": {"coverage": 0, "lint": False, "typecheck": False, "format": False},
83
- "go": {"coverage": 0, "lint": False, "format": False},
84
- "node": {"coverage": 0, "lint": False, "typecheck": False, "format": False},
85
- "rust": {"coverage": 0, "lint": False, "format": False},
86
- },
87
- }
88
-
89
- # Default configuration template
90
- DEFAULT_CONFIG = {
91
- "project": {
92
- "name": "my-project",
93
- "description": "A new project",
94
- },
95
- "languages": {
96
- "python": {
97
- "enabled": False,
98
- "version": "3.12",
99
- "src_dir": "src", # Source directory (e.g., "src", ".", or "src/mypackage")
100
- "quality": {
101
- "coverage": 80,
102
- "lint": True,
103
- "typecheck": True,
104
- "format": True,
105
- },
106
- },
107
- "go": {
108
- "enabled": False,
109
- "version": "1.24.0",
110
- "quality": {
111
- "coverage": 70,
112
- "lint": True,
113
- "format": True,
114
- },
115
- },
116
- "node": {
117
- "enabled": False,
118
- "version": "20",
119
- "quality": {
120
- "coverage": 75,
121
- "lint": True,
122
- "typecheck": True,
123
- "format": True,
124
- },
125
- },
126
- "rust": {
127
- "enabled": False,
128
- "version": "stable",
129
- "quality": {
130
- "coverage": 60,
131
- "lint": True,
132
- "format": True,
133
- },
134
- },
135
- },
136
- "precommit": True,
137
- "infrastructure": {
138
- "postgres": {
139
- "enabled": False,
140
- "version": "16",
141
- },
142
- "redis": {
143
- "enabled": False,
144
- "version": "7",
145
- },
146
- },
147
- }
148
-
149
- # ANSI colors
150
- CYAN = "\033[36m"
151
- GREEN = "\033[32m"
152
- RED = "\033[31m"
153
- DIM = "\033[2m"
154
- BOLD = "\033[1m"
155
- RESET = "\033[0m"
156
-
157
-
158
- def load_config(config_path: str = CONFIG_FILE) -> dict:
159
- """Load configuration from YAML file."""
160
- path = Path(config_path)
161
-
162
- if not path.exists():
163
- return DEFAULT_CONFIG.copy()
164
-
165
- if not HAS_YAML:
166
- print("Warning: PyYAML not installed, using defaults", file=sys.stderr)
167
- return DEFAULT_CONFIG.copy()
168
-
169
- with open(path) as f:
170
- config = yaml.safe_load(f) or {}
171
-
172
- # Merge with defaults to ensure all keys exist
173
- return deep_merge(DEFAULT_CONFIG.copy(), config)
174
-
175
-
176
- def save_config(config: dict, config_path: str = CONFIG_FILE) -> None:
177
- """Save configuration to YAML file."""
178
- if not HAS_YAML:
179
- print("Error: PyYAML required for saving config", file=sys.stderr)
180
- sys.exit(1)
181
-
182
- path = Path(config_path)
183
- with open(path, "w") as f:
184
- yaml.dump(config, f, default_flow_style=False, sort_keys=False, indent=2)
185
-
186
-
187
- def deep_merge(base: dict, override: dict) -> dict:
188
- """Deep merge two dictionaries."""
189
- result = base.copy()
190
- for key, value in override.items():
191
- if key in result and isinstance(result[key], dict) and isinstance(value, dict):
192
- result[key] = deep_merge(result[key], value)
193
- else:
194
- result[key] = value
195
- return result
196
-
197
-
198
- def get_nested(config: dict, path: str) -> Any:
199
- """Get a nested value by dot-separated path."""
200
- keys = path.split(".")
201
- value = config
202
- for key in keys:
203
- if isinstance(value, dict) and key in value:
204
- value = value[key]
205
- else:
206
- return None
207
- return value
208
-
209
-
210
- def set_nested(config: dict, path: str, value: Any) -> dict:
211
- """Set a nested value by dot-separated path."""
212
- keys = path.split(".")
213
- current = config
214
- for key in keys[:-1]:
215
- if key not in current:
216
- current[key] = {}
217
- current = current[key]
218
-
219
- # Try to parse value as appropriate type
220
- if isinstance(value, str):
221
- if value.lower() == "true":
222
- value = True
223
- elif value.lower() == "false":
224
- value = False
225
- else:
226
- with contextlib.suppress(ValueError):
227
- value = int(value)
228
-
229
- current[keys[-1]] = value
230
- return config
231
-
232
-
233
- def cmd_info(config: dict) -> None:
234
- """Display project configuration dashboard."""
235
- width = 57
236
-
237
- def hline(w: int) -> str:
238
- return "─" * w
239
-
240
- def check(val: bool) -> str:
241
- return f"{GREEN}✓{RESET}" if val else f"{DIM}✗{RESET}"
242
-
243
- project_name = config.get("project", {}).get("name", "unknown")
244
- languages = config.get("languages", {})
245
- precommit = config.get("precommit", False)
246
- infra = config.get("infrastructure", {})
247
-
248
- print()
249
- print(f"{CYAN}┌{hline(width - 2)}┐{RESET}")
250
- print(f"{CYAN}│{RESET} {BOLD}{project_name:<{width - 4}}{RESET} {CYAN}│{RESET}")
251
- print(f"{CYAN}├{hline(width - 2)}┤{RESET}")
252
-
253
- # Languages section
254
- print(f"{CYAN}│{RESET} {BOLD}Languages{RESET}{' ' * (width - 12)}{CYAN}│{RESET}")
255
- print(f"{CYAN}│{RESET}{' ' * (width - 2)}{CYAN}│{RESET}")
256
-
257
- lang_tools = {
258
- "python": ("Python", True), # has typecheck (mypy)
259
- "go": ("Go", False), # typecheck built-in
260
- "node": ("Node.js", True), # has typecheck (tsc)
261
- "rust": ("Rust", False), # typecheck built-in
262
- }
263
-
264
- for lang_key, (lang_name, has_typecheck) in lang_tools.items():
265
- lang = languages.get(lang_key, {})
266
- enabled = lang.get("enabled", False)
267
- version = lang.get("version", "")
268
- quality = lang.get("quality", {})
269
-
270
- if enabled:
271
- cov = quality.get("coverage", 0)
272
- lint = quality.get("lint", False)
273
- typecheck = quality.get("typecheck", False)
274
-
275
- extras = []
276
- extras.append(f"cov:{cov}%")
277
- if lint:
278
- extras.append(f"{GREEN}lint{RESET}")
279
- if has_typecheck and typecheck:
280
- extras.append(f"{GREEN}type{RESET}")
281
-
282
- extras_str = " ".join(extras)
283
- line = f" {GREEN}✓{RESET} {lang_name:<10} {CYAN}{version:<8}{RESET} {extras_str}"
284
- else:
285
- line = f" {DIM}✗ {lang_name:<10}{RESET}"
286
-
287
- # Pad to width (accounting for ANSI codes)
288
- visible_len = len(line.replace(GREEN, "").replace(CYAN, "").replace(DIM, "").replace(RESET, "").replace(BOLD, ""))
289
- padding = width - 2 - visible_len
290
- print(f"{CYAN}│{RESET}{line}{' ' * max(0, padding)}{CYAN}│{RESET}")
291
-
292
- print(f"{CYAN}├{hline(width - 2)}┤{RESET}")
293
-
294
- # Global settings
295
- print(f"{CYAN}│{RESET} {BOLD}Settings{RESET}{' ' * (width - 11)}{CYAN}│{RESET}")
296
- print(f"{CYAN}│{RESET}{' ' * (width - 2)}{CYAN}│{RESET}")
297
-
298
- precommit_str = f"{GREEN}enabled{RESET}" if precommit else f"{DIM}disabled{RESET}"
299
- line = f" Pre-commit: {precommit_str}"
300
- visible_len = len(line.replace(GREEN, "").replace(DIM, "").replace(RESET, ""))
301
- padding = width - 2 - visible_len
302
- print(f"{CYAN}│{RESET}{line}{' ' * max(0, padding)}{CYAN}│{RESET}")
303
-
304
- print(f"{CYAN}├{hline(width - 2)}┤{RESET}")
305
-
306
- # Infrastructure section
307
- print(f"{CYAN}│{RESET} {BOLD}Infrastructure{RESET}{' ' * (width - 17)}{CYAN}│{RESET}")
308
- print(f"{CYAN}│{RESET}{' ' * (width - 2)}{CYAN}│{RESET}")
309
-
310
- pg = infra.get("postgres", {}).get("enabled", False)
311
- redis = infra.get("redis", {}).get("enabled", False)
312
-
313
- line = f" {check(pg)} PostgreSQL {check(redis)} Redis"
314
- visible_len = len(line.replace(GREEN, "").replace(DIM, "").replace(RESET, ""))
315
- padding = width - 2 - visible_len
316
- print(f"{CYAN}│{RESET}{line}{' ' * max(0, padding)}{CYAN}│{RESET}")
317
-
318
- print(f"{CYAN}└{hline(width - 2)}┘{RESET}")
319
- print()
320
- print(f"{DIM}Config: {CONFIG_FILE} | Edit: make config{RESET}")
321
- print()
322
-
323
-
324
- def cmd_get(config: dict, path: str) -> None:
325
- """Get a configuration value."""
326
- value = get_nested(config, path)
327
- if value is None:
328
- print(f"Key not found: {path}", file=sys.stderr)
329
- sys.exit(1)
330
- print(value)
331
-
332
-
333
- def cmd_set(config: dict, path: str, value: str) -> None:
334
- """Set a configuration value."""
335
- config = set_nested(config, path, value)
336
- save_config(config)
337
- print(f"Set {path} = {value}")
338
-
339
-
340
- def cmd_export(config: dict, lang_filter: str | None = None) -> None:
341
- """Export configuration as shell variables."""
342
- project = config.get("project", {})
343
- languages = config.get("languages", {})
344
- infra = config.get("infrastructure", {})
345
-
346
- exports = []
347
-
348
- # Project
349
- exports.append(f"PROJECT_NAME='{project.get('name', '')}'")
350
- exports.append(f"PROJECT_DESCRIPTION='{project.get('description', '')}'")
351
-
352
- # Languages
353
- for lang_key, lang in languages.items():
354
- if lang_filter and lang_key != lang_filter:
355
- continue
356
-
357
- prefix = lang_key.upper()
358
- exports.append(f"INCLUDE_{prefix}={str(lang.get('enabled', False)).lower()}")
359
- exports.append(f"{prefix}_VERSION='{lang.get('version', '')}'")
360
-
361
- quality = lang.get("quality", {})
362
- exports.append(f"{prefix}_COVERAGE_THRESHOLD={quality.get('coverage', 0)}")
363
- exports.append(f"{prefix}_LINT_ENABLED={str(quality.get('lint', False)).lower()}")
364
- if "typecheck" in quality:
365
- exports.append(f"{prefix}_TYPECHECK_ENABLED={str(quality.get('typecheck', False)).lower()}")
366
- exports.append(f"{prefix}_FORMAT_ENABLED={str(quality.get('format', False)).lower()}")
367
-
368
- # Precommit
369
- exports.append(f"INCLUDE_PRECOMMIT={str(config.get('precommit', False)).lower()}")
370
-
371
- # Infrastructure
372
- for infra_key, infra_config in infra.items():
373
- prefix = infra_key.upper()
374
- exports.append(f"INCLUDE_{prefix}={str(infra_config.get('enabled', False)).lower()}")
375
- exports.append(f"{prefix}_VERSION='{infra_config.get('version', '')}'")
376
-
377
- for export in exports:
378
- print(export)
379
-
380
-
381
- def cmd_init(config: dict) -> None:
382
- """Initialize configuration file with defaults."""
383
- if Path(CONFIG_FILE).exists():
384
- print(f"Config file already exists: {CONFIG_FILE}")
385
- print("Use 'make config' to edit")
386
- return
387
-
388
- save_config(DEFAULT_CONFIG)
389
- print(f"Created {CONFIG_FILE}")
390
-
391
-
392
- def cmd_preset(config: dict, preset_name: str | None = None) -> None:
393
- """Apply a quality preset or list available presets."""
394
- if preset_name is None:
395
- # List available presets
396
- print()
397
- print(f"{BOLD}Available Quality Presets{RESET}")
398
- print("=" * 50)
399
- print()
400
- for name, preset in QUALITY_PRESETS.items():
401
- print(f" {CYAN}{name:<12}{RESET} {preset['description']}")
402
- print()
403
- print(f"{DIM}Usage: python3 project_config.py preset <name>{RESET}")
404
- print(f"{DIM} or: make quality-strict / make quality-relaxed{RESET}")
405
- print()
406
- return
407
-
408
- if preset_name not in QUALITY_PRESETS:
409
- print(f"Unknown preset: {preset_name}", file=sys.stderr)
410
- print(f"Available: {', '.join(QUALITY_PRESETS.keys())}", file=sys.stderr)
411
- sys.exit(1)
412
-
413
- preset = QUALITY_PRESETS[preset_name]
414
- languages = config.get("languages", {})
415
-
416
- # Apply preset to all enabled languages
417
- applied = []
418
- for lang_key in languages:
419
- if lang_key in preset:
420
- languages[lang_key]["quality"] = preset[lang_key].copy()
421
- if languages[lang_key].get("enabled", False):
422
- applied.append(lang_key)
423
-
424
- config["languages"] = languages
425
- save_config(config)
426
-
427
- print()
428
- print(f"{GREEN}✓{RESET} Applied '{preset_name}' preset")
429
- print(f" {preset['description']}")
430
- print()
431
- if applied:
432
- print(f" Updated: {', '.join(applied)}")
433
- else:
434
- print(f" {DIM}No languages enabled. Enable with: make lang-add LANG=python{RESET}")
435
- print()
436
-
437
-
438
- def cmd_lang_add(config: dict, lang: str) -> None:
439
- """Enable a language in the project."""
440
- valid_langs = ["python", "go", "node", "rust"]
441
- if lang not in valid_langs:
442
- print(f"Unknown language: {lang}", file=sys.stderr)
443
- print(f"Available: {', '.join(valid_langs)}", file=sys.stderr)
444
- sys.exit(1)
445
-
446
- languages = config.get("languages", {})
447
- if lang not in languages:
448
- languages[lang] = DEFAULT_CONFIG["languages"].get(lang, {"enabled": True})
449
-
450
- if languages[lang].get("enabled", False):
451
- print(f"{lang} is already enabled")
452
- return
453
-
454
- languages[lang]["enabled"] = True
455
- config["languages"] = languages
456
- save_config(config)
457
-
458
- version = languages[lang].get("version", "")
459
- quality = languages[lang].get("quality", {})
460
- cov = quality.get("coverage", 0)
461
-
462
- print()
463
- print(f"{GREEN}✓{RESET} Enabled {lang}")
464
- print(f" Version: {version}")
465
- print(f" Coverage: {cov}%")
466
- print()
467
- print(f"{DIM}Run 'make info' to see full configuration{RESET}")
468
- print()
469
-
470
-
471
- def cmd_lang_remove(config: dict, lang: str) -> None:
472
- """Disable a language in the project."""
473
- languages = config.get("languages", {})
474
-
475
- if lang not in languages:
476
- print(f"Unknown language: {lang}", file=sys.stderr)
477
- sys.exit(1)
478
-
479
- if not languages[lang].get("enabled", False):
480
- print(f"{lang} is already disabled")
481
- return
482
-
483
- languages[lang]["enabled"] = False
484
- config["languages"] = languages
485
- save_config(config)
486
-
487
- print()
488
- print(f"{GREEN}✓{RESET} Disabled {lang}")
489
- print()
490
-
491
-
492
- def cmd_lang_list(config: dict) -> None:
493
- """List available languages and their status."""
494
- languages = config.get("languages", {})
495
- print()
496
- print(f"{BOLD}Languages{RESET}")
497
- print("=" * 40)
498
- print()
499
- for lang_key, lang_config in languages.items():
500
- enabled = lang_config.get("enabled", False)
501
- version = lang_config.get("version", "")
502
- status = f"{GREEN}enabled{RESET}" if enabled else f"{DIM}disabled{RESET}"
503
- print(f" {lang_key:<10} {version:<10} {status}")
504
- print()
505
- print(f"{DIM}Add: make lang-add LANG=python{RESET}")
506
- print(f"{DIM}Remove: make lang-remove LANG=python{RESET}")
507
- print()
508
-
509
-
510
- def cmd_sync(config: dict, dry_run: bool = False) -> None:
511
- """Sync config to language-specific files."""
512
- languages = config.get("languages", {})
513
- project_name = config.get("project", {}).get("name", "my-project")
514
-
515
- synced = []
516
- created = []
517
-
518
- for lang_key, lang_config in languages.items():
519
- if not lang_config.get("enabled", False):
520
- continue
521
-
522
- version = lang_config.get("version", "")
523
- quality = lang_config.get("quality", {})
524
-
525
- if lang_key == "python":
526
- src_dir = lang_config.get("src_dir", "src")
527
- result = _sync_python(project_name, version, quality, src_dir, dry_run)
528
- synced.extend(result.get("synced", []))
529
- created.extend(result.get("created", []))
530
-
531
- elif lang_key == "go":
532
- result = _sync_go(project_name, version, quality, dry_run)
533
- synced.extend(result.get("synced", []))
534
- created.extend(result.get("created", []))
535
-
536
- elif lang_key == "node":
537
- result = _sync_node(project_name, version, quality, dry_run)
538
- synced.extend(result.get("synced", []))
539
- created.extend(result.get("created", []))
540
-
541
- elif lang_key == "rust":
542
- result = _sync_rust(project_name, version, quality, dry_run)
543
- synced.extend(result.get("synced", []))
544
- created.extend(result.get("created", []))
545
-
546
- print()
547
- if dry_run:
548
- print(f"{CYAN}[DRY RUN]{RESET} Would sync the following:")
549
- else:
550
- print(f"{GREEN}✓{RESET} Config synced")
551
-
552
- if created:
553
- print(f"\n {BOLD}Created:{RESET}")
554
- for f in created:
555
- print(f" + {f}")
556
-
557
- if synced:
558
- print(f"\n {BOLD}Updated:{RESET}")
559
- for f in synced:
560
- print(f" ~ {f}")
561
-
562
- if not created and not synced:
563
- print(f" {DIM}No changes needed{RESET}")
564
-
565
- print()
566
-
567
-
568
- def _sync_python(name: str, version: str, quality: dict, src_dir: str, dry_run: bool) -> dict:
569
- """Sync Python configuration files."""
570
- result = {"synced": [], "created": []}
571
- pyproject_path = Path("pyproject.toml")
572
- coverage = quality.get("coverage", 80)
573
- lint = quality.get("lint", True)
574
- typecheck = quality.get("typecheck", True)
575
- fmt = quality.get("format", True)
576
-
577
- # Generate pyproject.toml content
578
- content = f'''[project]
579
- name = "{name}"
580
- version = "0.1.0"
581
- description = ""
582
- requires-python = ">={version}"
583
-
584
- [project.optional-dependencies]
585
- dev = [
586
- "pytest>=7.4.0",
587
- "pytest-cov>=4.1.0",
588
- "pytest-asyncio>=0.21.0",
589
- "ruff>=0.8.0",
590
- "mypy>=1.8.0",
591
- ]
592
-
593
- [build-system]
594
- requires = ["hatchling"]
595
- build-backend = "hatchling.build"
596
-
597
- [tool.hatch.build.targets.wheel]
598
- include = ["**/*.py"]
599
- exclude = [
600
- "tests/**",
601
- "**/test_*.py",
602
- "**/*_test.py",
603
- "conftest.py",
604
- ".template/**",
605
- ".project/**",
606
- "scripts/**",
607
- "docs/**",
608
- "examples/**",
609
- ]
610
-
611
- [tool.pytest.ini_options]
612
- testpaths = ["tests"]
613
- addopts = "--cov={src_dir} --cov-report=term-missing --cov-fail-under={coverage}"
614
-
615
- [tool.coverage.run]
616
- source = ["{src_dir}"]
617
- branch = true
618
-
619
- [tool.coverage.report]
620
- fail_under = {coverage}
621
- show_missing = true
622
- '''
623
-
624
- if lint or fmt:
625
- content += f'''
626
- [tool.ruff]
627
- line-length = 88
628
- target-version = "py{version.replace(".", "")[:3]}"
629
- '''
630
- if lint:
631
- content += '''
632
- [tool.ruff.lint]
633
- select = ["E", "F", "W", "I", "UP", "B", "C4"]
634
- ignore = []
635
- '''
636
-
637
- if typecheck:
638
- content += f'''
639
- [tool.mypy]
640
- python_version = "{version}"
641
- strict = true
642
- warn_return_any = true
643
- warn_unused_configs = true
644
- '''
645
-
646
- if dry_run:
647
- if pyproject_path.exists():
648
- result["synced"].append("pyproject.toml")
649
- else:
650
- result["created"].append("pyproject.toml")
651
- else:
652
- existed = pyproject_path.exists()
653
- with open(pyproject_path, "w") as f:
654
- f.write(content)
655
- if existed:
656
- result["synced"].append("pyproject.toml")
657
- else:
658
- result["created"].append("pyproject.toml")
659
-
660
- return result
661
-
662
-
663
- def _sync_go(name: str, version: str, quality: dict, dry_run: bool) -> dict:
664
- """Sync Go configuration files."""
665
- result = {"synced": [], "created": []}
666
- gomod_path = Path("go.mod")
667
- golangci_path = Path(".golangci.yml")
668
- lint = quality.get("lint", True)
669
-
670
- # go.mod
671
- gomod_content = f'''module {name}
672
-
673
- go {version}
674
- '''
675
-
676
- if dry_run:
677
- if gomod_path.exists():
678
- result["synced"].append("go.mod")
679
- else:
680
- result["created"].append("go.mod")
681
- else:
682
- existed = gomod_path.exists()
683
- with open(gomod_path, "w") as f:
684
- f.write(gomod_content)
685
- if existed:
686
- result["synced"].append("go.mod")
687
- else:
688
- result["created"].append("go.mod")
689
-
690
- # .golangci.yml for linting
691
- if lint:
692
- golangci_content = '''run:
693
- timeout: 5m
694
-
695
- linters:
696
- enable:
697
- - errcheck
698
- - gosimple
699
- - govet
700
- - ineffassign
701
- - staticcheck
702
- - unused
703
- - gofmt
704
- - goimports
705
-
706
- linters-settings:
707
- errcheck:
708
- check-type-assertions: true
709
- '''
710
- if dry_run:
711
- if golangci_path.exists():
712
- result["synced"].append(".golangci.yml")
713
- else:
714
- result["created"].append(".golangci.yml")
715
- else:
716
- existed = golangci_path.exists()
717
- with open(golangci_path, "w") as f:
718
- f.write(golangci_content)
719
- if existed:
720
- result["synced"].append(".golangci.yml")
721
- else:
722
- result["created"].append(".golangci.yml")
723
-
724
- return result
725
-
726
-
727
- def _sync_node(name: str, version: str, quality: dict, dry_run: bool) -> dict:
728
- """Sync Node.js configuration files."""
729
- result = {"synced": [], "created": []}
730
- pkg_path = Path("package.json")
731
- tsconfig_path = Path("tsconfig.json")
732
- coverage = quality.get("coverage", 75)
733
- lint = quality.get("lint", True)
734
- typecheck = quality.get("typecheck", True)
735
-
736
- # package.json
737
- pkg_content = {
738
- "name": name,
739
- "version": "0.1.0",
740
- "type": "module",
741
- "engines": {"node": f">={version}"},
742
- "scripts": {
743
- "build": "tsc" if typecheck else "echo 'No build step'",
744
- "test": f"vitest run --coverage --coverage.thresholds.statements={coverage}",
745
- "lint": "eslint src" if lint else "echo 'Lint disabled'",
746
- "format": "prettier --write src",
747
- },
748
- }
749
-
750
- if dry_run:
751
- if pkg_path.exists():
752
- result["synced"].append("package.json")
753
- else:
754
- result["created"].append("package.json")
755
- else:
756
- existed = pkg_path.exists()
757
- with open(pkg_path, "w") as f:
758
- json.dump(pkg_content, f, indent=2)
759
- f.write("\n")
760
- if existed:
761
- result["synced"].append("package.json")
762
- else:
763
- result["created"].append("package.json")
764
-
765
- # tsconfig.json for TypeScript
766
- if typecheck:
767
- tsconfig_content = {
768
- "compilerOptions": {
769
- "target": "ES2022",
770
- "module": "NodeNext",
771
- "moduleResolution": "NodeNext",
772
- "strict": True,
773
- "esModuleInterop": True,
774
- "skipLibCheck": True,
775
- "outDir": "dist",
776
- "rootDir": "src",
777
- },
778
- "include": ["src"],
779
- "exclude": ["node_modules", "dist"],
780
- }
781
-
782
- if dry_run:
783
- if tsconfig_path.exists():
784
- result["synced"].append("tsconfig.json")
785
- else:
786
- result["created"].append("tsconfig.json")
787
- else:
788
- existed = tsconfig_path.exists()
789
- with open(tsconfig_path, "w") as f:
790
- json.dump(tsconfig_content, f, indent=2)
791
- f.write("\n")
792
- if existed:
793
- result["synced"].append("tsconfig.json")
794
- else:
795
- result["created"].append("tsconfig.json")
796
-
797
- return result
798
-
799
-
800
- def _sync_rust(name: str, version: str, quality: dict, dry_run: bool) -> dict:
801
- """Sync Rust configuration files."""
802
- result = {"synced": [], "created": []}
803
- cargo_path = Path("Cargo.toml")
804
-
805
- # Cargo.toml
806
- cargo_content = f'''[package]
807
- name = "{name.replace("-", "_")}"
808
- version = "0.1.0"
809
- edition = "2021"
810
- rust-version = "{version if version != "stable" else "1.75"}"
811
-
812
- [dependencies]
813
-
814
- [dev-dependencies]
815
-
816
- [lints.rust]
817
- unsafe_code = "forbid"
818
-
819
- [lints.clippy]
820
- all = "warn"
821
- pedantic = "warn"
822
- '''
823
-
824
- if dry_run:
825
- if cargo_path.exists():
826
- result["synced"].append("Cargo.toml")
827
- else:
828
- result["created"].append("Cargo.toml")
829
- else:
830
- existed = cargo_path.exists()
831
- with open(cargo_path, "w") as f:
832
- f.write(cargo_content)
833
- if existed:
834
- result["synced"].append("Cargo.toml")
835
- else:
836
- result["created"].append("Cargo.toml")
837
-
838
- return result
839
-
840
-
841
- def main() -> int:
842
- """CLI entry point."""
843
- if len(sys.argv) < 2:
844
- print(__doc__)
845
- return 1
846
-
847
- cmd = sys.argv[1]
848
- config = load_config()
849
-
850
- if cmd == "info":
851
- cmd_info(config)
852
- elif cmd == "get":
853
- if len(sys.argv) < 3:
854
- print("Usage: project_config.py get <path>", file=sys.stderr)
855
- return 1
856
- cmd_get(config, sys.argv[2])
857
- elif cmd == "set":
858
- if len(sys.argv) < 4:
859
- print("Usage: project_config.py set <path> <value>", file=sys.stderr)
860
- return 1
861
- cmd_set(config, sys.argv[2], sys.argv[3])
862
- elif cmd == "export":
863
- lang_filter = sys.argv[2] if len(sys.argv) > 2 else None
864
- cmd_export(config, lang_filter)
865
- elif cmd == "init":
866
- cmd_init(config)
867
- elif cmd == "preset":
868
- preset_name = sys.argv[2] if len(sys.argv) > 2 else None
869
- cmd_preset(config, preset_name)
870
- elif cmd == "lang-add":
871
- if len(sys.argv) < 3:
872
- print("Usage: project_config.py lang-add <language>", file=sys.stderr)
873
- print("Languages: python, go, node, rust", file=sys.stderr)
874
- return 1
875
- cmd_lang_add(config, sys.argv[2])
876
- elif cmd == "lang-remove":
877
- if len(sys.argv) < 3:
878
- print("Usage: project_config.py lang-remove <language>", file=sys.stderr)
879
- return 1
880
- cmd_lang_remove(config, sys.argv[2])
881
- elif cmd == "lang-list":
882
- cmd_lang_list(config)
883
- elif cmd == "sync":
884
- dry_run = "--dry-run" in sys.argv
885
- cmd_sync(config, dry_run)
886
- else:
887
- print(f"Unknown command: {cmd}", file=sys.stderr)
888
- return 1
889
-
890
- return 0
891
-
892
-
893
- if __name__ == "__main__":
894
- sys.exit(main())