codeforge-dev 1.5.8 → 1.8.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 (176) hide show
  1. package/.devcontainer/.env +4 -5
  2. package/.devcontainer/.env.example +29 -0
  3. package/.devcontainer/.gitignore +8 -0
  4. package/.devcontainer/.secrets.example +12 -0
  5. package/.devcontainer/CHANGELOG.md +186 -0
  6. package/.devcontainer/CLAUDE.md +108 -21
  7. package/.devcontainer/README.md +173 -57
  8. package/.devcontainer/config/defaults/keybindings.json +5 -0
  9. package/.devcontainer/config/{main-system-prompt.md → defaults/main-system-prompt.md} +135 -2
  10. package/.devcontainer/config/{settings.json → defaults/settings.json} +25 -6
  11. package/.devcontainer/config/file-manifest.json +20 -0
  12. package/.devcontainer/devcontainer.json +38 -2
  13. package/.devcontainer/docs/configuration-reference.md +90 -0
  14. package/.devcontainer/docs/keybindings.md +100 -0
  15. package/.devcontainer/docs/optional-features.md +129 -0
  16. package/.devcontainer/docs/plugins.md +154 -0
  17. package/.devcontainer/docs/troubleshooting.md +128 -0
  18. package/.devcontainer/features/README.md +21 -7
  19. package/.devcontainer/features/agent-browser/install.sh +6 -0
  20. package/.devcontainer/features/ast-grep/install.sh +6 -0
  21. package/.devcontainer/features/biome/README.md +27 -0
  22. package/.devcontainer/features/biome/install.sh +6 -0
  23. package/.devcontainer/features/ccburn/README.md +60 -0
  24. package/.devcontainer/features/ccburn/devcontainer-feature.json +38 -0
  25. package/.devcontainer/features/ccburn/install.sh +180 -0
  26. package/.devcontainer/features/ccstatusline/README.md +22 -21
  27. package/.devcontainer/features/ccstatusline/devcontainer-feature.json +6 -1
  28. package/.devcontainer/features/ccstatusline/install.sh +55 -16
  29. package/.devcontainer/features/ccusage/install.sh +6 -0
  30. package/.devcontainer/features/claude-monitor/install.sh +6 -0
  31. package/.devcontainer/features/dprint/README.md +30 -0
  32. package/.devcontainer/features/dprint/devcontainer-feature.json +18 -0
  33. package/.devcontainer/features/dprint/install.sh +131 -0
  34. package/.devcontainer/features/hadolint/README.md +35 -0
  35. package/.devcontainer/features/hadolint/devcontainer-feature.json +13 -0
  36. package/.devcontainer/features/hadolint/install.sh +86 -0
  37. package/.devcontainer/features/lsp-servers/devcontainer-feature.json +5 -0
  38. package/.devcontainer/features/lsp-servers/install.sh +7 -0
  39. package/.devcontainer/features/mcp-qdrant/devcontainer-feature.json +6 -1
  40. package/.devcontainer/features/mcp-qdrant/install.sh +13 -6
  41. package/.devcontainer/features/mcp-reasoner/devcontainer-feature.json +6 -1
  42. package/.devcontainer/features/mcp-reasoner/install.sh +8 -1
  43. package/.devcontainer/features/notify-hook/devcontainer-feature.json +5 -0
  44. package/.devcontainer/features/notify-hook/install.sh +7 -0
  45. package/.devcontainer/features/ruff/README.md +26 -0
  46. package/.devcontainer/features/ruff/devcontainer-feature.json +21 -0
  47. package/.devcontainer/features/ruff/install.sh +74 -0
  48. package/.devcontainer/features/shellcheck/README.md +38 -0
  49. package/.devcontainer/features/shellcheck/devcontainer-feature.json +13 -0
  50. package/.devcontainer/features/shellcheck/install.sh +24 -0
  51. package/.devcontainer/features/shfmt/README.md +37 -0
  52. package/.devcontainer/features/shfmt/devcontainer-feature.json +13 -0
  53. package/.devcontainer/features/shfmt/install.sh +85 -0
  54. package/.devcontainer/features/splitrail/devcontainer-feature.json +5 -0
  55. package/.devcontainer/features/splitrail/install.sh +7 -0
  56. package/.devcontainer/features/tmux/install.sh +8 -0
  57. package/.devcontainer/features/tree-sitter/install.sh +6 -0
  58. package/.devcontainer/plugins/devs-marketplace/.claude-plugin/marketplace.json +3 -10
  59. package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/.claude-plugin/plugin.json +1 -1
  60. package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/scripts/__pycache__/format-on-stop.cpython-314.pyc +0 -0
  61. package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/scripts/format-on-stop.py +133 -13
  62. package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/.claude-plugin/plugin.json +1 -1
  63. package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/hooks/hooks.json +4 -5
  64. package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/scripts/__pycache__/lint-file.cpython-314.pyc +0 -0
  65. package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/scripts/lint-file.py +477 -78
  66. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/.claude-plugin/plugin.json +1 -1
  67. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/AGENT-REDIRECTION.md +226 -0
  68. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/REVIEW-RUBRIC.md +440 -0
  69. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/architect.md +207 -0
  70. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/bash-exec.md +173 -0
  71. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/claude-guide.md +146 -0
  72. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/debug-logs.md +2 -0
  73. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/dependency-analyst.md +250 -0
  74. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/doc-writer.md +246 -0
  75. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/explorer.md +237 -0
  76. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/generalist.md +134 -0
  77. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/git-archaeologist.md +242 -0
  78. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/migrator.md +201 -0
  79. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/perf-profiler.md +265 -0
  80. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/refactorer.md +213 -0
  81. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/researcher.md +195 -0
  82. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/security-auditor.md +289 -0
  83. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/spec-writer.md +297 -0
  84. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/statusline-config.md +188 -0
  85. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/test-writer.md +248 -0
  86. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/hooks/hooks.json +51 -0
  87. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/__pycache__/advisory-test-runner.cpython-314.pyc +0 -0
  88. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/__pycache__/collect-edited-files.cpython-314.pyc +0 -0
  89. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/__pycache__/commit-reminder.cpython-314.pyc +0 -0
  90. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/__pycache__/git-state-injector.cpython-314.pyc +0 -0
  91. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/__pycache__/guard-readonly-bash.cpython-314.pyc +0 -0
  92. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/__pycache__/redirect-builtin-agents.cpython-314.pyc +0 -0
  93. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/__pycache__/skill-suggester.cpython-314.pyc +0 -0
  94. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/__pycache__/syntax-validator.cpython-314.pyc +0 -0
  95. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/__pycache__/ticket-linker.cpython-314.pyc +0 -0
  96. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/__pycache__/todo-harvester.cpython-314.pyc +0 -0
  97. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/__pycache__/verify-no-regression.cpython-314.pyc +0 -0
  98. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/__pycache__/verify-tests-pass.cpython-314.pyc +0 -0
  99. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/advisory-test-runner.py +174 -0
  100. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/collect-edited-files.py +8 -6
  101. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/commit-reminder.py +90 -0
  102. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/git-state-injector.py +114 -0
  103. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/guard-readonly-bash.py +611 -0
  104. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/redirect-builtin-agents.py +83 -0
  105. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/skill-suggester.py +146 -2
  106. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/syntax-validator.py +9 -4
  107. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/ticket-linker.py +137 -0
  108. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/todo-harvester.py +130 -0
  109. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/verify-no-regression.py +221 -0
  110. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/verify-tests-pass.py +176 -0
  111. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/api-design/SKILL.md +224 -0
  112. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/api-design/references/error-handling.md +166 -0
  113. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/api-design/references/rest-conventions.md +215 -0
  114. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/ast-grep-patterns/SKILL.md +211 -0
  115. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/ast-grep-patterns/references/language-patterns.md +327 -0
  116. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/claude-agent-sdk/SKILL.md +599 -0
  117. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/claude-agent-sdk/references/sdk-typescript-reference.md +954 -0
  118. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/dependency-management/SKILL.md +134 -0
  119. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/dependency-management/references/ecosystem-commands.md +264 -0
  120. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/dependency-management/references/license-compliance.md +80 -0
  121. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/documentation-patterns/SKILL.md +153 -0
  122. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/documentation-patterns/references/api-doc-templates.md +221 -0
  123. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/documentation-patterns/references/docstring-formats.md +296 -0
  124. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/git-forensics/SKILL.md +276 -0
  125. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/git-forensics/references/advanced-commands.md +332 -0
  126. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/git-forensics/references/investigation-playbooks.md +319 -0
  127. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/migration-patterns/SKILL.md +150 -0
  128. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/migration-patterns/references/javascript-migrations.md +179 -0
  129. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/migration-patterns/references/python-migrations.md +141 -0
  130. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/performance-profiling/SKILL.md +341 -0
  131. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/performance-profiling/references/interpreting-results.md +235 -0
  132. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/performance-profiling/references/tool-commands.md +395 -0
  133. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/refactoring-patterns/SKILL.md +344 -0
  134. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/refactoring-patterns/references/safe-transformations.md +247 -0
  135. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/refactoring-patterns/references/smell-catalog.md +332 -0
  136. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/security-checklist/SKILL.md +277 -0
  137. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/security-checklist/references/owasp-patterns.md +269 -0
  138. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/security-checklist/references/secrets-patterns.md +253 -0
  139. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/specification-writing/SKILL.md +320 -0
  140. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/specification-writing/references/criteria-patterns.md +245 -0
  141. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/specification-writing/references/ears-templates.md +239 -0
  142. package/.devcontainer/plugins/devs-marketplace/plugins/dangerous-command-blocker/scripts/__pycache__/block-dangerous.cpython-314.pyc +0 -0
  143. package/.devcontainer/plugins/devs-marketplace/plugins/notify-hook/hooks/hooks.json +1 -1
  144. package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/scripts/__pycache__/guard-protected.cpython-314.pyc +0 -0
  145. package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/scripts/guard-protected.py +40 -39
  146. package/.devcontainer/scripts/check-setup.sh +72 -0
  147. package/.devcontainer/scripts/setup-aliases.sh +51 -6
  148. package/.devcontainer/scripts/setup-auth.sh +74 -0
  149. package/.devcontainer/scripts/setup-config.sh +112 -20
  150. package/.devcontainer/scripts/setup-plugins.sh +38 -46
  151. package/.devcontainer/scripts/setup-projects.sh +175 -0
  152. package/.devcontainer/scripts/setup-symlink-claude.sh +36 -0
  153. package/.devcontainer/scripts/setup-update-claude.sh +19 -8
  154. package/.devcontainer/scripts/setup.sh +49 -14
  155. package/README.md +23 -190
  156. package/package.json +1 -1
  157. package/setup.js +245 -71
  158. package/.devcontainer/features/claude-code/README.md +0 -498
  159. package/.devcontainer/features/claude-code/config/settings.json +0 -36
  160. package/.devcontainer/features/claude-code/config/system-prompt.md +0 -118
  161. package/.devcontainer/features/claude-code/config/world-building-sp.md +0 -1432
  162. package/.devcontainer/features/claude-code/devcontainer-feature.json +0 -42
  163. package/.devcontainer/features/claude-code/install.sh +0 -466
  164. package/.devcontainer/plugins/devs-marketplace/plugins/planning-reminder/.claude-plugin/plugin.json +0 -7
  165. package/.devcontainer/plugins/devs-marketplace/plugins/planning-reminder/hooks/hooks.json +0 -17
  166. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/.claude-plugin/plugin.json +0 -6
  167. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/config/planning-instructions.md +0 -14
  168. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/functional-conjuring-map.md +0 -989
  169. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/hooks/hooks.json +0 -33
  170. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/__pycache__/post-enhance-task.cpython-314.pyc +0 -0
  171. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/enhance-planning.py +0 -71
  172. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/enhancers/enhance-plan.sh +0 -68
  173. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/enhancers/enhance-task.sh +0 -120
  174. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/post-enhance-plan.py +0 -133
  175. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/post-enhance-task.py +0 -253
  176. package/.devcontainer/scripts/setup-irie-claude.sh +0 -32
@@ -1,11 +1,20 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
- Auto-lint files after editing.
3
+ Batch linter runs as a Stop hook.
4
+
5
+ Reads file paths collected by collect-edited-files.py during the
6
+ conversation turn, deduplicates them, and lints each based on
7
+ extension:
8
+ .py / .pyi → Pyright (type checking) + Ruff check (style/correctness)
9
+ .js/.jsx/.ts/… → Biome lint
10
+ .css/.graphql/… → Biome lint
11
+ .sh/.bash/.zsh/… → ShellCheck
12
+ .go → go vet
13
+ Dockerfile → hadolint
14
+ .rs → clippy (conditional)
4
15
 
5
- Reads tool input from stdin, detects file type by extension,
6
- runs appropriate linter if available.
7
16
  Outputs JSON with additionalContext containing lint warnings.
8
- Non-blocking: exit 0 regardless of lint result.
17
+ Always cleans up the temp file. Always exits 0.
9
18
  """
10
19
 
11
20
  import json
@@ -14,124 +23,514 @@ import subprocess
14
23
  import sys
15
24
  from pathlib import Path
16
25
 
17
- # Linter configuration: extension -> (command, args, name, parser)
18
- PYTHON_EXTENSIONS = {".py", ".pyi"}
26
+ # ── Extension sets ──────────────────────────────────────────────────
19
27
 
28
+ PYTHON_EXTS = {".py", ".pyi"}
29
+ BIOME_EXTS = {
30
+ ".js",
31
+ ".jsx",
32
+ ".ts",
33
+ ".tsx",
34
+ ".mjs",
35
+ ".cjs",
36
+ ".mts",
37
+ ".cts",
38
+ ".css",
39
+ ".graphql",
40
+ ".gql",
41
+ }
42
+ SHELL_EXTS = {".sh", ".bash", ".zsh", ".mksh", ".bats"}
43
+ GO_EXTS = {".go"}
44
+ RUST_EXTS = {".rs"}
20
45
 
21
- def lint_python(file_path: str) -> tuple[bool, str]:
22
- """Run pyright on a Python file.
46
+ SUBPROCESS_TIMEOUT = 10
23
47
 
24
- Returns:
25
- (success, message)
26
- """
27
- pyright_cmd = "pyright"
28
48
 
29
- # Check if pyright is available
49
+ # ── Tool resolution ─────────────────────────────────────────────────
50
+
51
+
52
+ def _which(name: str) -> str | None:
53
+ """Check if a tool is available in PATH."""
54
+ try:
55
+ result = subprocess.run(["which", name], capture_output=True, text=True)
56
+ if result.returncode == 0:
57
+ return result.stdout.strip()
58
+ except Exception:
59
+ pass
60
+ return None
61
+
62
+
63
+ def _find_tool_upward(file_path: str, tool_name: str) -> str | None:
64
+ """Walk up from file directory looking for node_modules/.bin/<tool>."""
65
+ current = Path(file_path).parent
66
+ for _ in range(20):
67
+ candidate = current / "node_modules" / ".bin" / tool_name
68
+ if candidate.is_file():
69
+ return str(candidate)
70
+ parent = current.parent
71
+ if parent == current:
72
+ break
73
+ current = parent
74
+ return None
75
+
76
+
77
+ def _find_biome(file_path: str) -> str | None:
78
+ """Find biome binary: project-local first, then global."""
79
+ local = _find_tool_upward(file_path, "biome")
80
+ if local:
81
+ return local
82
+ return _which("biome")
83
+
84
+
85
+ # ── Diagnostic formatting ──────────────────────────────────────────
86
+
87
+
88
+ def _format_issues(filename: str, diagnostics: list[dict]) -> str:
89
+ """Format a list of {severity, line, message} dicts into display text."""
90
+ if not diagnostics:
91
+ return ""
92
+
93
+ issues = []
94
+ for diag in diagnostics[:5]:
95
+ severity = diag.get("severity", "info")
96
+ message = diag.get("message", "")
97
+ line = diag.get("line", 0)
98
+
99
+ if severity == "error":
100
+ icon = "\u2717"
101
+ elif severity == "warning":
102
+ icon = "!"
103
+ else:
104
+ icon = "\u2022"
105
+
106
+ issues.append(f" {icon} Line {line}: {message}")
107
+
108
+ total = len(diagnostics)
109
+ shown = min(5, total)
110
+ header = f" {filename}: {total} issue(s)"
111
+ if total > shown:
112
+ header += f" (showing first {shown})"
113
+
114
+ return header + "\n" + "\n".join(issues)
115
+
116
+
117
+ # ── Linters ─────────────────────────────────────────────────────────
118
+
119
+
120
+ def lint_python_pyright(file_path: str) -> str:
121
+ """Run Pyright type checker on a Python file."""
122
+ pyright = _which("pyright")
123
+ if not pyright:
124
+ return ""
125
+
126
+ try:
127
+ result = subprocess.run(
128
+ [pyright, "--outputjson", file_path],
129
+ capture_output=True,
130
+ text=True,
131
+ timeout=SUBPROCESS_TIMEOUT,
132
+ )
133
+
134
+ try:
135
+ output = json.loads(result.stdout)
136
+ except json.JSONDecodeError:
137
+ return ""
138
+
139
+ diagnostics = output.get("generalDiagnostics", [])
140
+ if not diagnostics:
141
+ return ""
142
+
143
+ parsed = [
144
+ {
145
+ "severity": d.get("severity", "info"),
146
+ "line": d.get("range", {}).get("start", {}).get("line", 0) + 1,
147
+ "message": d.get("message", ""),
148
+ }
149
+ for d in diagnostics
150
+ ]
151
+ return _format_issues(Path(file_path).name, parsed)
152
+
153
+ except subprocess.TimeoutExpired:
154
+ return f" {Path(file_path).name}: pyright timed out"
155
+ except Exception:
156
+ return ""
157
+
158
+
159
+ def lint_python_ruff(file_path: str) -> str:
160
+ """Run Ruff linter on a Python file."""
161
+ ruff = _which("ruff")
162
+ if not ruff:
163
+ return ""
164
+
30
165
  try:
31
- subprocess.run(
32
- ["which", pyright_cmd],
166
+ result = subprocess.run(
167
+ [ruff, "check", "--output-format=json", "--no-fix", file_path],
33
168
  capture_output=True,
34
- check=True
169
+ text=True,
170
+ timeout=SUBPROCESS_TIMEOUT,
35
171
  )
36
- except subprocess.CalledProcessError:
37
- return True, "" # Pyright not available
172
+
173
+ try:
174
+ issues = json.loads(result.stdout)
175
+ except json.JSONDecodeError:
176
+ return ""
177
+
178
+ if not issues:
179
+ return ""
180
+
181
+ parsed = [
182
+ {
183
+ "severity": "warning",
184
+ "line": issue.get("location", {}).get("row", 0),
185
+ "message": f"[{issue.get('code', '?')}] {issue.get('message', '')}",
186
+ }
187
+ for issue in issues
188
+ ]
189
+ return _format_issues(Path(file_path).name, parsed)
190
+
191
+ except subprocess.TimeoutExpired:
192
+ return f" {Path(file_path).name}: ruff timed out"
193
+ except Exception:
194
+ return ""
195
+
196
+
197
+ def lint_biome(file_path: str) -> str:
198
+ """Run Biome linter for JS/TS/CSS/GraphQL files."""
199
+ biome = _find_biome(file_path)
200
+ if not biome:
201
+ return ""
38
202
 
39
203
  try:
40
204
  result = subprocess.run(
41
- [pyright_cmd, "--outputjson", file_path],
205
+ [biome, "lint", "--reporter=json", file_path],
42
206
  capture_output=True,
43
207
  text=True,
44
- timeout=55
208
+ timeout=SUBPROCESS_TIMEOUT,
45
209
  )
46
210
 
47
- # Parse pyright JSON output
48
211
  try:
49
212
  output = json.loads(result.stdout)
50
- diagnostics = output.get("generalDiagnostics", [])
213
+ except json.JSONDecodeError:
214
+ return ""
215
+
216
+ diagnostics = output.get("diagnostics", [])
217
+ if not diagnostics:
218
+ return ""
219
+
220
+ parsed = [
221
+ {
222
+ "severity": d.get("severity", "warning"),
223
+ "line": (
224
+ d.get("location", {}).get("span", {}).get("start", {})
225
+ if isinstance(
226
+ d.get("location", {}).get("span", {}).get("start"), int
227
+ )
228
+ else 0
229
+ ),
230
+ "message": d.get("description", d.get("message", "")),
231
+ }
232
+ for d in diagnostics
233
+ ]
234
+ return _format_issues(Path(file_path).name, parsed)
235
+
236
+ except subprocess.TimeoutExpired:
237
+ return f" {Path(file_path).name}: biome lint timed out"
238
+ except Exception:
239
+ return ""
240
+
241
+
242
+ def lint_shellcheck(file_path: str) -> str:
243
+ """Run ShellCheck on a shell script."""
244
+ shellcheck = _which("shellcheck")
245
+ if not shellcheck:
246
+ return ""
247
+
248
+ try:
249
+ result = subprocess.run(
250
+ [shellcheck, "--format=json", file_path],
251
+ capture_output=True,
252
+ text=True,
253
+ timeout=SUBPROCESS_TIMEOUT,
254
+ )
51
255
 
52
- if not diagnostics:
53
- return True, "[Auto-linter] Pyright: No issues found"
256
+ try:
257
+ issues = json.loads(result.stdout)
258
+ except json.JSONDecodeError:
259
+ return ""
260
+
261
+ if not issues:
262
+ return ""
263
+
264
+ severity_map = {
265
+ "error": "error",
266
+ "warning": "warning",
267
+ "info": "info",
268
+ "style": "info",
269
+ }
270
+ parsed = [
271
+ {
272
+ "severity": severity_map.get(issue.get("level", "info"), "info"),
273
+ "line": issue.get("line", 0),
274
+ "message": f"[SC{issue.get('code', '?')}] {issue.get('message', '')}",
275
+ }
276
+ for issue in issues
277
+ ]
278
+ return _format_issues(Path(file_path).name, parsed)
279
+
280
+ except subprocess.TimeoutExpired:
281
+ return f" {Path(file_path).name}: shellcheck timed out"
282
+ except Exception:
283
+ return ""
284
+
285
+
286
+ def lint_go_vet(file_path: str) -> str:
287
+ """Run go vet on a Go file."""
288
+ go = _which("go")
289
+ if not go:
290
+ return ""
291
+
292
+ try:
293
+ result = subprocess.run(
294
+ [go, "vet", file_path],
295
+ capture_output=True,
296
+ text=True,
297
+ timeout=SUBPROCESS_TIMEOUT,
298
+ )
54
299
 
55
- # Format diagnostics
56
- issues = []
57
- for diag in diagnostics[:5]: # Limit to first 5 issues
58
- severity = diag.get("severity", "info")
59
- message = diag.get("message", "")
60
- line = diag.get("range", {}).get("start", {}).get("line", 0) + 1
300
+ # go vet outputs to stderr
301
+ output = result.stderr.strip()
302
+ if not output:
303
+ return ""
304
+
305
+ lines = output.splitlines()
306
+ parsed = []
307
+ for line in lines:
308
+ # Format: file.go:LINE:COL: message
309
+ parts = line.split(":", 3)
310
+ if len(parts) >= 4:
311
+ try:
312
+ line_num = int(parts[1])
313
+ except ValueError:
314
+ line_num = 0
315
+ parsed.append(
316
+ {
317
+ "severity": "warning",
318
+ "line": line_num,
319
+ "message": parts[3].strip(),
320
+ }
321
+ )
322
+ elif line.strip():
323
+ parsed.append(
324
+ {
325
+ "severity": "warning",
326
+ "line": 0,
327
+ "message": line.strip(),
328
+ }
329
+ )
330
+
331
+ return _format_issues(Path(file_path).name, parsed)
61
332
 
62
- if severity == "error":
63
- icon = "✗"
64
- elif severity == "warning":
65
- icon = "!"
66
- else:
67
- icon = "•"
333
+ except subprocess.TimeoutExpired:
334
+ return f" {Path(file_path).name}: go vet timed out"
335
+ except Exception:
336
+ return ""
68
337
 
69
- issues.append(f" {icon} Line {line}: {message}")
70
338
 
71
- total = len(diagnostics)
72
- shown = min(5, total)
73
- header = f"[Auto-linter] Pyright: {total} issue(s)"
74
- if total > shown:
75
- header += f" (showing first {shown})"
339
+ def lint_hadolint(file_path: str) -> str:
340
+ """Run hadolint on a Dockerfile."""
341
+ hadolint = _which("hadolint")
342
+ if not hadolint:
343
+ return ""
76
344
 
77
- return True, header + "\n" + "\n".join(issues)
345
+ try:
346
+ result = subprocess.run(
347
+ [hadolint, "--format", "json", file_path],
348
+ capture_output=True,
349
+ text=True,
350
+ timeout=SUBPROCESS_TIMEOUT,
351
+ )
78
352
 
353
+ try:
354
+ issues = json.loads(result.stdout)
79
355
  except json.JSONDecodeError:
80
- # Pyright output not JSON, might be an error
81
- if result.stderr:
82
- return True, f"[Auto-linter] Pyright error: {result.stderr.strip()[:100]}"
83
- return True, ""
356
+ return ""
357
+
358
+ if not issues:
359
+ return ""
360
+
361
+ severity_map = {
362
+ "error": "error",
363
+ "warning": "warning",
364
+ "info": "info",
365
+ "style": "info",
366
+ }
367
+ parsed = [
368
+ {
369
+ "severity": severity_map.get(issue.get("level", "info"), "info"),
370
+ "line": issue.get("line", 0),
371
+ "message": f"[{issue.get('code', '?')}] {issue.get('message', '')}",
372
+ }
373
+ for issue in issues
374
+ ]
375
+ return _format_issues(Path(file_path).name, parsed)
84
376
 
85
377
  except subprocess.TimeoutExpired:
86
- return True, "[Auto-linter] Pyright timed out"
87
- except Exception as e:
88
- return True, f"[Auto-linter] Error: {e}"
378
+ return f" {Path(file_path).name}: hadolint timed out"
379
+ except Exception:
380
+ return ""
89
381
 
90
382
 
91
- def lint_file(file_path: str) -> tuple[bool, str]:
92
- """Run appropriate linter for file.
383
+ def lint_clippy(file_path: str) -> str:
384
+ """Run clippy on a Rust file (conditional — only if cargo is in PATH)."""
385
+ cargo = _which("cargo")
386
+ if not cargo:
387
+ return ""
93
388
 
94
- Returns:
95
- (success, message)
96
- """
97
- ext = Path(file_path).suffix.lower()
389
+ try:
390
+ result = subprocess.run(
391
+ [cargo, "clippy", "--message-format=json", "--", "-W", "clippy::all"],
392
+ capture_output=True,
393
+ text=True,
394
+ timeout=SUBPROCESS_TIMEOUT,
395
+ cwd=str(Path(file_path).parent),
396
+ )
98
397
 
99
- if ext in PYTHON_EXTENSIONS:
100
- return lint_python(file_path)
398
+ lines = result.stdout.strip().splitlines()
399
+ parsed = []
400
+ target_name = Path(file_path).name
401
+
402
+ for line in lines:
403
+ try:
404
+ msg = json.loads(line)
405
+ except json.JSONDecodeError:
406
+ continue
407
+
408
+ if msg.get("reason") != "compiler-message":
409
+ continue
410
+ inner = msg.get("message", {})
411
+ level = inner.get("level", "")
412
+ if level not in ("warning", "error"):
413
+ continue
414
+
415
+ # Match diagnostics to the target file
416
+ spans = inner.get("spans", [])
417
+ line_num = 0
418
+ for span in spans:
419
+ if span.get("is_primary") and target_name in span.get("file_name", ""):
420
+ line_num = span.get("line_start", 0)
421
+ break
422
+
423
+ if line_num or not spans:
424
+ parsed.append(
425
+ {
426
+ "severity": level,
427
+ "line": line_num,
428
+ "message": inner.get("message", ""),
429
+ }
430
+ )
431
+
432
+ return _format_issues(Path(file_path).name, parsed)
101
433
 
102
- # No linter available for this file type
103
- return True, ""
434
+ except subprocess.TimeoutExpired:
435
+ return f" {Path(file_path).name}: clippy timed out"
436
+ except Exception:
437
+ return ""
438
+
439
+
440
+ # ── Main ────────────────────────────────────────────────────────────
104
441
 
105
442
 
106
443
  def main():
107
444
  try:
108
445
  input_data = json.load(sys.stdin)
109
- tool_input = input_data.get("tool_input", {})
110
- file_path = tool_input.get("file_path", "")
111
-
112
- if not file_path:
113
- sys.exit(0)
446
+ except (json.JSONDecodeError, ValueError):
447
+ sys.exit(0)
114
448
 
115
- # Check if file exists
116
- if not os.path.exists(file_path):
117
- sys.exit(0)
449
+ if input_data.get("stop_hook_active"):
450
+ sys.exit(0)
118
451
 
119
- _, message = lint_file(file_path)
452
+ session_id = input_data.get("session_id", "")
453
+ if not session_id:
454
+ sys.exit(0)
120
455
 
121
- if message:
122
- # Output context for Claude
123
- print(json.dumps({
124
- "additionalContext": message
125
- }))
456
+ tmp_path = f"/tmp/claude-lint-files-{session_id}"
126
457
 
458
+ try:
459
+ with open(tmp_path) as f:
460
+ raw_paths = f.read().splitlines()
461
+ except FileNotFoundError:
127
462
  sys.exit(0)
128
-
129
- except json.JSONDecodeError:
463
+ except OSError:
130
464
  sys.exit(0)
131
- except Exception as e:
132
- print(f"Hook error: {e}", file=sys.stderr)
465
+ finally:
466
+ try:
467
+ os.unlink(tmp_path)
468
+ except OSError:
469
+ pass
470
+
471
+ # Deduplicate, filter to existing files (no longer Python-only)
472
+ seen: set[str] = set()
473
+ paths: list[str] = []
474
+ for p in raw_paths:
475
+ p = p.strip()
476
+ if p and p not in seen and os.path.isfile(p):
477
+ seen.add(p)
478
+ paths.append(p)
479
+
480
+ if not paths:
133
481
  sys.exit(0)
134
482
 
483
+ # Collect results grouped by linter
484
+ all_results: dict[str, list[str]] = {}
485
+
486
+ for path in paths:
487
+ ext = Path(path).suffix.lower()
488
+ name = Path(path).name
489
+
490
+ if ext in PYTHON_EXTS:
491
+ msg = lint_python_pyright(path)
492
+ if msg:
493
+ all_results.setdefault("Pyright", []).append(msg)
494
+ msg = lint_python_ruff(path)
495
+ if msg:
496
+ all_results.setdefault("Ruff", []).append(msg)
497
+
498
+ elif ext in BIOME_EXTS:
499
+ msg = lint_biome(path)
500
+ if msg:
501
+ all_results.setdefault("Biome", []).append(msg)
502
+
503
+ elif ext in SHELL_EXTS:
504
+ msg = lint_shellcheck(path)
505
+ if msg:
506
+ all_results.setdefault("ShellCheck", []).append(msg)
507
+
508
+ elif ext in GO_EXTS:
509
+ msg = lint_go_vet(path)
510
+ if msg:
511
+ all_results.setdefault("go vet", []).append(msg)
512
+
513
+ elif name == "Dockerfile" or ext == ".dockerfile":
514
+ msg = lint_hadolint(path)
515
+ if msg:
516
+ all_results.setdefault("hadolint", []).append(msg)
517
+
518
+ elif ext in RUST_EXTS:
519
+ msg = lint_clippy(path)
520
+ if msg:
521
+ all_results.setdefault("clippy", []).append(msg)
522
+
523
+ if all_results:
524
+ sections = []
525
+ for linter_name, results in all_results.items():
526
+ sections.append(
527
+ f"[Auto-linter] {linter_name} results:\n" + "\n".join(results)
528
+ )
529
+ output = "\n\n".join(sections)
530
+ print(json.dumps({"additionalContext": output}))
531
+
532
+ sys.exit(0)
533
+
135
534
 
136
535
  if __name__ == "__main__":
137
536
  main()
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-directive",
3
- "description": "Coding standards and skills for the CodeDirective project",
3
+ "description": "17 custom agents, 16 coding skills, agent redirection, syntax validation, and skill auto-suggestion",
4
4
  "version": "1.0.0",
5
5
  "author": {
6
6
  "name": "AnExiledDev"