codeforge-dev 1.7.0 → 1.9.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.
- package/.devcontainer/.env +4 -6
- package/.devcontainer/.env.example +29 -0
- package/.devcontainer/.gitignore +8 -0
- package/.devcontainer/.secrets.example +12 -0
- package/.devcontainer/CHANGELOG.md +181 -0
- package/.devcontainer/CLAUDE.md +57 -20
- package/.devcontainer/README.md +111 -56
- package/.devcontainer/config/{main-system-prompt.md → defaults/main-system-prompt.md} +72 -0
- package/.devcontainer/config/defaults/rules/spec-workflow.md +67 -0
- package/.devcontainer/config/defaults/rules/workspace-scope.md +7 -0
- package/.devcontainer/config/defaults/settings.json +67 -0
- package/.devcontainer/config/file-manifest.json +32 -0
- package/.devcontainer/devcontainer.json +20 -0
- package/.devcontainer/docs/configuration-reference.md +90 -0
- package/.devcontainer/docs/keybindings.md +100 -0
- package/.devcontainer/docs/optional-features.md +129 -0
- package/.devcontainer/docs/plugins.md +154 -0
- package/.devcontainer/docs/troubleshooting.md +128 -0
- package/.devcontainer/features/agent-browser/install.sh +6 -0
- package/.devcontainer/features/ast-grep/install.sh +6 -0
- package/.devcontainer/features/biome/README.md +27 -0
- package/.devcontainer/features/biome/install.sh +6 -0
- package/.devcontainer/features/ccburn/install.sh +6 -0
- package/.devcontainer/features/ccstatusline/devcontainer-feature.json +5 -0
- package/.devcontainer/features/ccstatusline/install.sh +7 -0
- package/.devcontainer/features/ccusage/install.sh +6 -0
- package/.devcontainer/features/claude-monitor/install.sh +6 -0
- package/.devcontainer/features/dprint/README.md +30 -0
- package/.devcontainer/features/dprint/devcontainer-feature.json +18 -0
- package/.devcontainer/features/dprint/install.sh +131 -0
- package/.devcontainer/features/hadolint/README.md +35 -0
- package/.devcontainer/features/hadolint/devcontainer-feature.json +13 -0
- package/.devcontainer/features/hadolint/install.sh +86 -0
- package/.devcontainer/features/lsp-servers/devcontainer-feature.json +5 -0
- package/.devcontainer/features/lsp-servers/install.sh +7 -0
- package/.devcontainer/features/mcp-qdrant/devcontainer-feature.json +5 -0
- package/.devcontainer/features/mcp-qdrant/install.sh +13 -6
- package/.devcontainer/features/mcp-reasoner/devcontainer-feature.json +5 -0
- package/.devcontainer/features/mcp-reasoner/install.sh +8 -1
- package/.devcontainer/features/notify-hook/devcontainer-feature.json +5 -0
- package/.devcontainer/features/notify-hook/install.sh +7 -0
- package/.devcontainer/features/ruff/README.md +26 -0
- package/.devcontainer/features/ruff/devcontainer-feature.json +21 -0
- package/.devcontainer/features/ruff/install.sh +74 -0
- package/.devcontainer/features/shellcheck/README.md +38 -0
- package/.devcontainer/features/shellcheck/devcontainer-feature.json +13 -0
- package/.devcontainer/features/shellcheck/install.sh +24 -0
- package/.devcontainer/features/shfmt/README.md +37 -0
- package/.devcontainer/features/shfmt/devcontainer-feature.json +13 -0
- package/.devcontainer/features/shfmt/install.sh +85 -0
- package/.devcontainer/features/splitrail/devcontainer-feature.json +5 -0
- package/.devcontainer/features/splitrail/install.sh +7 -0
- package/.devcontainer/features/tmux/install.sh +8 -0
- package/.devcontainer/features/tree-sitter/install.sh +6 -0
- package/.devcontainer/plugins/devs-marketplace/.claude-plugin/marketplace.json +104 -104
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-code-quality/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-code-quality/README.md +158 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-code-quality/hooks/hooks.json +39 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-code-quality/scripts/collect-edited-files.py +47 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-code-quality/scripts/format-on-stop.py +297 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-code-quality/scripts/lint-file.py +536 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-code-quality/scripts/syntax-validator.py +146 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/.claude-plugin/plugin.json +1 -1
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/scripts/__pycache__/format-on-stop.cpython-314.pyc +0 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/scripts/format-on-stop.py +114 -9
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/.claude-plugin/plugin.json +1 -1
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/hooks/hooks.json +4 -5
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/scripts/__pycache__/lint-file.cpython-314.pyc +0 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/scripts/lint-file.py +478 -76
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/.claude-plugin/plugin.json +1 -1
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/AGENT-REDIRECTION.md +226 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/architect.md +94 -1
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/bash-exec.md +4 -4
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/claude-guide.md +14 -23
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/debug-logs.md +20 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/dependency-analyst.md +20 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/doc-writer.md +99 -1
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/explorer.md +20 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/generalist.md +152 -9
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/git-archaeologist.md +18 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/migrator.md +114 -1
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/perf-profiler.md +24 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/refactorer.md +101 -1
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/researcher.md +33 -1
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/security-auditor.md +24 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/spec-writer.md +65 -24
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/statusline-config.md +3 -3
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/test-writer.md +99 -1
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/hooks/hooks.json +100 -56
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/__pycache__/advisory-test-runner.cpython-314.pyc +0 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/__pycache__/collect-edited-files.cpython-314.pyc +0 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/__pycache__/commit-reminder.cpython-314.pyc +0 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/__pycache__/git-state-injector.cpython-314.pyc +0 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/__pycache__/redirect-builtin-agents.cpython-314.pyc +0 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/__pycache__/ticket-linker.cpython-314.pyc +0 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/__pycache__/todo-harvester.cpython-314.pyc +0 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/advisory-test-runner.py +174 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/collect-edited-files.py +8 -6
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/commit-reminder.py +90 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/git-state-injector.py +114 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/skill-suggester.py +61 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/spec-reminder.py +121 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/ticket-linker.py +137 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/todo-harvester.py +130 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/api-design/SKILL.md +224 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/api-design/references/error-handling.md +166 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/api-design/references/rest-conventions.md +215 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/ast-grep-patterns/SKILL.md +211 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/ast-grep-patterns/references/language-patterns.md +327 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/dependency-management/SKILL.md +134 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/dependency-management/references/ecosystem-commands.md +264 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/dependency-management/references/license-compliance.md +80 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/documentation-patterns/SKILL.md +153 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/documentation-patterns/references/api-doc-templates.md +221 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/documentation-patterns/references/docstring-formats.md +296 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/migration-patterns/SKILL.md +150 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/migration-patterns/references/javascript-migrations.md +179 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/migration-patterns/references/python-migrations.md +141 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/spec-check/SKILL.md +86 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/spec-init/SKILL.md +97 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/spec-init/references/backlog-template.md +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/spec-init/references/roadmap-template.md +13 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/spec-new/SKILL.md +101 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/spec-new/references/template.md +110 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/spec-update/SKILL.md +124 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/specification-writing/SKILL.md +32 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/dangerous-command-blocker/scripts/__pycache__/block-dangerous.cpython-314.pyc +0 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/notify-hook/hooks/hooks.json +1 -1
- package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/scripts/__pycache__/guard-protected.cpython-314.pyc +0 -0
- package/.devcontainer/scripts/check-setup.sh +72 -0
- package/.devcontainer/scripts/setup-aliases.sh +43 -3
- package/.devcontainer/scripts/setup-auth.sh +74 -0
- package/.devcontainer/scripts/setup-config.sh +117 -24
- package/.devcontainer/scripts/setup-update-claude.sh +8 -0
- package/.devcontainer/scripts/setup.sh +46 -13
- package/README.md +23 -190
- package/package.json +42 -42
- package/setup.js +245 -71
- package/.devcontainer/config/settings.json +0 -70
- package/.devcontainer/features/claude-code/README.md +0 -498
- package/.devcontainer/features/claude-code/config/settings.json +0 -72
- package/.devcontainer/features/claude-code/config/system-prompt.md +0 -118
- package/.devcontainer/features/claude-code/config/world-building-sp.md +0 -1432
- package/.devcontainer/features/claude-code/devcontainer-feature.json +0 -42
- package/.devcontainer/features/claude-code/install.sh +0 -466
- package/.devcontainer/plugins/devs-marketplace/plugins/planning-reminder/.claude-plugin/plugin.json +0 -7
- package/.devcontainer/plugins/devs-marketplace/plugins/planning-reminder/hooks/hooks.json +0 -17
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/.claude-plugin/plugin.json +0 -6
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/config/planning-instructions.md +0 -14
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/functional-conjuring-map.md +0 -989
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/hooks/hooks.json +0 -33
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/__pycache__/post-enhance-task.cpython-314.pyc +0 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/enhance-planning.py +0 -71
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/enhancers/enhance-plan.sh +0 -68
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/enhancers/enhance-task.sh +0 -120
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/post-enhance-plan.py +0 -133
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/post-enhance-task.py +0 -253
- /package/.devcontainer/config/{keybindings.json → defaults/keybindings.json} +0 -0
package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/commit-reminder.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Commit reminder — Stop hook that advises about uncommitted changes.
|
|
4
|
+
|
|
5
|
+
On Stop, checks for uncommitted changes (staged + unstaged) and injects
|
|
6
|
+
an advisory reminder as additionalContext. Claude sees it and can
|
|
7
|
+
naturally ask the user if they want to commit.
|
|
8
|
+
|
|
9
|
+
Reads hook input from stdin (JSON). Returns JSON on stdout.
|
|
10
|
+
Always exits 0 (advisory, never blocking).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import subprocess
|
|
15
|
+
import sys
|
|
16
|
+
|
|
17
|
+
GIT_CMD_TIMEOUT = 5
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _run_git(args: list[str]) -> str | None:
|
|
21
|
+
"""Run a git command and return stdout, or None on any failure."""
|
|
22
|
+
try:
|
|
23
|
+
result = subprocess.run(
|
|
24
|
+
["git"] + args,
|
|
25
|
+
capture_output=True,
|
|
26
|
+
text=True,
|
|
27
|
+
timeout=GIT_CMD_TIMEOUT,
|
|
28
|
+
)
|
|
29
|
+
if result.returncode == 0:
|
|
30
|
+
return result.stdout.strip()
|
|
31
|
+
except (FileNotFoundError, OSError, subprocess.TimeoutExpired):
|
|
32
|
+
pass
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def main():
|
|
37
|
+
try:
|
|
38
|
+
input_data = json.load(sys.stdin)
|
|
39
|
+
except (json.JSONDecodeError, ValueError):
|
|
40
|
+
sys.exit(0)
|
|
41
|
+
|
|
42
|
+
# Skip if another Stop hook is already blocking
|
|
43
|
+
if input_data.get("stop_hook_active"):
|
|
44
|
+
sys.exit(0)
|
|
45
|
+
|
|
46
|
+
# Check if there are any changes at all
|
|
47
|
+
porcelain = _run_git(["status", "--porcelain"])
|
|
48
|
+
if porcelain is None:
|
|
49
|
+
# Not a git repo or git not available
|
|
50
|
+
sys.exit(0)
|
|
51
|
+
if not porcelain.strip():
|
|
52
|
+
# Working tree clean
|
|
53
|
+
sys.exit(0)
|
|
54
|
+
|
|
55
|
+
lines = porcelain.strip().splitlines()
|
|
56
|
+
total = len(lines)
|
|
57
|
+
|
|
58
|
+
# Count staged vs unstaged
|
|
59
|
+
staged = 0
|
|
60
|
+
unstaged = 0
|
|
61
|
+
for line in lines:
|
|
62
|
+
index_status = line[0:1] if len(line) > 0 else " "
|
|
63
|
+
worktree_status = line[1:2] if len(line) > 1 else " "
|
|
64
|
+
|
|
65
|
+
if index_status not in (" ", "?"):
|
|
66
|
+
staged += 1
|
|
67
|
+
if worktree_status not in (" ", "?"):
|
|
68
|
+
unstaged += 1
|
|
69
|
+
if line[0:2] == "??":
|
|
70
|
+
unstaged += 1
|
|
71
|
+
|
|
72
|
+
parts = []
|
|
73
|
+
if staged:
|
|
74
|
+
parts.append(f"{staged} staged")
|
|
75
|
+
if unstaged:
|
|
76
|
+
parts.append(f"{unstaged} unstaged")
|
|
77
|
+
|
|
78
|
+
summary = ", ".join(parts) if parts else f"{total} changed"
|
|
79
|
+
|
|
80
|
+
message = (
|
|
81
|
+
f"[Uncommitted Changes] {total} files with changes ({summary}).\n"
|
|
82
|
+
"Consider asking the user if they'd like to commit before finishing."
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
json.dump({"additionalContext": message}, sys.stdout)
|
|
86
|
+
sys.exit(0)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
if __name__ == "__main__":
|
|
90
|
+
main()
|
package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/git-state-injector.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Git state injector — SessionStart hook that injects repo state as context.
|
|
4
|
+
|
|
5
|
+
Runs git commands to gather branch, status, recent commits, and uncommitted
|
|
6
|
+
changes. Injects the results as additionalContext so Claude starts every
|
|
7
|
+
session knowing the current git state.
|
|
8
|
+
|
|
9
|
+
Reads hook input from stdin (JSON). Returns JSON on stdout.
|
|
10
|
+
Always exits 0 (advisory, never blocking).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import subprocess
|
|
16
|
+
import sys
|
|
17
|
+
|
|
18
|
+
GIT_CMD_TIMEOUT = 5
|
|
19
|
+
STATUS_LINE_CAP = 20
|
|
20
|
+
DIFF_STAT_LINE_CAP = 15
|
|
21
|
+
TOTAL_OUTPUT_CAP = 2000
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _run_git(args: list[str], cwd: str) -> str | None:
|
|
25
|
+
"""Run a git command and return stdout, or None on any failure."""
|
|
26
|
+
try:
|
|
27
|
+
result = subprocess.run(
|
|
28
|
+
["git"] + args,
|
|
29
|
+
cwd=cwd,
|
|
30
|
+
capture_output=True,
|
|
31
|
+
text=True,
|
|
32
|
+
timeout=GIT_CMD_TIMEOUT,
|
|
33
|
+
)
|
|
34
|
+
if result.returncode == 0:
|
|
35
|
+
return result.stdout.strip()
|
|
36
|
+
except (FileNotFoundError, OSError, subprocess.TimeoutExpired):
|
|
37
|
+
pass
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _cap_lines(text: str, limit: int) -> str:
|
|
42
|
+
"""Truncate text to a maximum number of lines."""
|
|
43
|
+
lines = text.splitlines()
|
|
44
|
+
if len(lines) <= limit:
|
|
45
|
+
return text
|
|
46
|
+
return "\n".join(lines[:limit]) + f"\n...({len(lines) - limit} more lines)"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def main():
|
|
50
|
+
try:
|
|
51
|
+
json.load(sys.stdin)
|
|
52
|
+
except (json.JSONDecodeError, ValueError):
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
cwd = os.getcwd()
|
|
56
|
+
|
|
57
|
+
# Check if we're in a git repo at all
|
|
58
|
+
branch = _run_git(["branch", "--show-current"], cwd)
|
|
59
|
+
if branch is None:
|
|
60
|
+
# Not a git repo or git not available
|
|
61
|
+
sys.exit(0)
|
|
62
|
+
|
|
63
|
+
sections = []
|
|
64
|
+
sections.append(f"Branch: {branch or '(detached HEAD)'}")
|
|
65
|
+
|
|
66
|
+
# Git status
|
|
67
|
+
status = _run_git(["status", "--short"], cwd)
|
|
68
|
+
if status:
|
|
69
|
+
status_lines = status.splitlines()
|
|
70
|
+
modified = sum(1 for l in status_lines if l.strip() and l[0:1] == "M")
|
|
71
|
+
added = sum(1 for l in status_lines if l[0:1] == "A")
|
|
72
|
+
deleted = sum(1 for l in status_lines if l[0:1] == "D")
|
|
73
|
+
untracked = sum(1 for l in status_lines if l[0:2] == "??")
|
|
74
|
+
|
|
75
|
+
counts = []
|
|
76
|
+
if modified:
|
|
77
|
+
counts.append(f"{modified} modified")
|
|
78
|
+
if added:
|
|
79
|
+
counts.append(f"{added} added")
|
|
80
|
+
if deleted:
|
|
81
|
+
counts.append(f"{deleted} deleted")
|
|
82
|
+
if untracked:
|
|
83
|
+
counts.append(f"{untracked} untracked")
|
|
84
|
+
|
|
85
|
+
summary = ", ".join(counts) if counts else f"{len(status_lines)} changed"
|
|
86
|
+
sections.append(f"Status: {summary}")
|
|
87
|
+
sections.append(_cap_lines(status, STATUS_LINE_CAP))
|
|
88
|
+
else:
|
|
89
|
+
sections.append("Status: clean")
|
|
90
|
+
|
|
91
|
+
# Recent commits
|
|
92
|
+
log = _run_git(["log", "--oneline", "-5"], cwd)
|
|
93
|
+
if log:
|
|
94
|
+
sections.append(f"Recent commits:\n{log}")
|
|
95
|
+
|
|
96
|
+
# Uncommitted diff stats
|
|
97
|
+
diff_stat = _run_git(["diff", "--stat"], cwd)
|
|
98
|
+
if diff_stat:
|
|
99
|
+
sections.append(
|
|
100
|
+
f"Uncommitted changes:\n{_cap_lines(diff_stat, DIFF_STAT_LINE_CAP)}"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
output = "[Git State]\n" + "\n".join(sections)
|
|
104
|
+
|
|
105
|
+
# Cap total output to avoid context bloat
|
|
106
|
+
if len(output) > TOTAL_OUTPUT_CAP:
|
|
107
|
+
output = output[:TOTAL_OUTPUT_CAP] + "\n...(truncated)"
|
|
108
|
+
|
|
109
|
+
json.dump({"additionalContext": output}, sys.stdout)
|
|
110
|
+
sys.exit(0)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
if __name__ == "__main__":
|
|
114
|
+
main()
|
package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/skill-suggester.py
CHANGED
|
@@ -235,6 +235,67 @@ SKILLS = {
|
|
|
235
235
|
"throughput",
|
|
236
236
|
],
|
|
237
237
|
},
|
|
238
|
+
"api-design": {
|
|
239
|
+
"phrases": [
|
|
240
|
+
"api design",
|
|
241
|
+
"rest api design",
|
|
242
|
+
"design an api",
|
|
243
|
+
"design a rest",
|
|
244
|
+
"api convention",
|
|
245
|
+
"endpoint design",
|
|
246
|
+
"api versioning",
|
|
247
|
+
"pagination pattern",
|
|
248
|
+
"error response format",
|
|
249
|
+
],
|
|
250
|
+
"terms": ["openapi", "swagger", "rfc7807"],
|
|
251
|
+
},
|
|
252
|
+
"ast-grep-patterns": {
|
|
253
|
+
"phrases": [
|
|
254
|
+
"ast-grep",
|
|
255
|
+
"ast grep",
|
|
256
|
+
"structural search",
|
|
257
|
+
"structural code search",
|
|
258
|
+
"syntax-aware search",
|
|
259
|
+
"tree-sitter",
|
|
260
|
+
],
|
|
261
|
+
"terms": ["sg run", "ast-grep", "tree-sitter"],
|
|
262
|
+
},
|
|
263
|
+
"dependency-management": {
|
|
264
|
+
"phrases": [
|
|
265
|
+
"check dependencies",
|
|
266
|
+
"audit dependencies",
|
|
267
|
+
"outdated packages",
|
|
268
|
+
"dependency health",
|
|
269
|
+
"license check",
|
|
270
|
+
"unused dependencies",
|
|
271
|
+
"vulnerability scan",
|
|
272
|
+
],
|
|
273
|
+
"terms": ["pip-audit", "npm audit", "cargo audit", "govulncheck"],
|
|
274
|
+
},
|
|
275
|
+
"documentation-patterns": {
|
|
276
|
+
"phrases": [
|
|
277
|
+
"write a readme",
|
|
278
|
+
"write documentation",
|
|
279
|
+
"add docstrings",
|
|
280
|
+
"add jsdoc",
|
|
281
|
+
"document the api",
|
|
282
|
+
"documentation template",
|
|
283
|
+
"update the docs",
|
|
284
|
+
],
|
|
285
|
+
"terms": ["docstring", "jsdoc", "tsdoc", "godoc", "rustdoc"],
|
|
286
|
+
},
|
|
287
|
+
"migration-patterns": {
|
|
288
|
+
"phrases": [
|
|
289
|
+
"migrate from",
|
|
290
|
+
"upgrade to",
|
|
291
|
+
"version upgrade",
|
|
292
|
+
"framework migration",
|
|
293
|
+
"bump python",
|
|
294
|
+
"upgrade pydantic",
|
|
295
|
+
"migrate express",
|
|
296
|
+
],
|
|
297
|
+
"terms": ["migrate", "migration", "upgrade"],
|
|
298
|
+
},
|
|
238
299
|
}
|
|
239
300
|
|
|
240
301
|
# Pre-compile term patterns for whole-word matching
|
package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/spec-reminder.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Spec reminder — Stop hook that advises about spec updates after code changes.
|
|
4
|
+
|
|
5
|
+
On Stop, checks if source code was modified but no .specs/ files were updated.
|
|
6
|
+
Injects an advisory reminder as additionalContext pointing the user to
|
|
7
|
+
/spec-update.
|
|
8
|
+
|
|
9
|
+
Only fires when a .specs/ directory exists (project uses the spec system).
|
|
10
|
+
|
|
11
|
+
Reads hook input from stdin (JSON). Returns JSON on stdout.
|
|
12
|
+
Always exits 0 (advisory, never blocking).
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import os
|
|
17
|
+
import subprocess
|
|
18
|
+
import sys
|
|
19
|
+
|
|
20
|
+
GIT_CMD_TIMEOUT = 5
|
|
21
|
+
|
|
22
|
+
# Directories whose changes should trigger the spec reminder
|
|
23
|
+
CODE_DIRS = (
|
|
24
|
+
"src/",
|
|
25
|
+
"lib/",
|
|
26
|
+
"app/",
|
|
27
|
+
"pkg/",
|
|
28
|
+
"internal/",
|
|
29
|
+
"cmd/",
|
|
30
|
+
"tests/",
|
|
31
|
+
"api/",
|
|
32
|
+
"frontend/",
|
|
33
|
+
"backend/",
|
|
34
|
+
"packages/",
|
|
35
|
+
"services/",
|
|
36
|
+
"components/",
|
|
37
|
+
"pages/",
|
|
38
|
+
"routes/",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _run_git(args: list[str]) -> str | None:
|
|
43
|
+
"""Run a git command and return stdout, or None on any failure."""
|
|
44
|
+
try:
|
|
45
|
+
result = subprocess.run(
|
|
46
|
+
["git"] + args,
|
|
47
|
+
capture_output=True,
|
|
48
|
+
text=True,
|
|
49
|
+
timeout=GIT_CMD_TIMEOUT,
|
|
50
|
+
)
|
|
51
|
+
if result.returncode == 0:
|
|
52
|
+
return result.stdout.strip()
|
|
53
|
+
except (FileNotFoundError, OSError, subprocess.TimeoutExpired):
|
|
54
|
+
pass
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def main():
|
|
59
|
+
try:
|
|
60
|
+
input_data = json.load(sys.stdin)
|
|
61
|
+
except (json.JSONDecodeError, ValueError):
|
|
62
|
+
sys.exit(0)
|
|
63
|
+
|
|
64
|
+
# Skip if another Stop hook is already blocking
|
|
65
|
+
if input_data.get("stop_hook_active"):
|
|
66
|
+
sys.exit(0)
|
|
67
|
+
|
|
68
|
+
cwd = os.getcwd()
|
|
69
|
+
|
|
70
|
+
# Only fire if this project uses the spec system
|
|
71
|
+
if not os.path.isdir(os.path.join(cwd, ".specs")):
|
|
72
|
+
sys.exit(0)
|
|
73
|
+
|
|
74
|
+
# Get all changed files (staged + unstaged)
|
|
75
|
+
diff_output = _run_git(["diff", "--name-only", "HEAD"])
|
|
76
|
+
staged_output = _run_git(["diff", "--name-only", "--cached"])
|
|
77
|
+
|
|
78
|
+
# Also include untracked files in source dirs
|
|
79
|
+
untracked = _run_git(["ls-files", "--others", "--exclude-standard"])
|
|
80
|
+
|
|
81
|
+
all_files: set[str] = set()
|
|
82
|
+
for output in (diff_output, staged_output, untracked):
|
|
83
|
+
if output:
|
|
84
|
+
all_files.update(output.splitlines())
|
|
85
|
+
|
|
86
|
+
if not all_files:
|
|
87
|
+
sys.exit(0)
|
|
88
|
+
|
|
89
|
+
# Check if any code directories have changes
|
|
90
|
+
has_code_changes = any(f.startswith(d) for f in all_files for d in CODE_DIRS)
|
|
91
|
+
|
|
92
|
+
if not has_code_changes:
|
|
93
|
+
sys.exit(0)
|
|
94
|
+
|
|
95
|
+
# Check if any spec files were also changed
|
|
96
|
+
has_spec_changes = any(f.startswith(".specs/") for f in all_files)
|
|
97
|
+
|
|
98
|
+
if has_spec_changes:
|
|
99
|
+
# Specs were updated alongside code — no reminder needed
|
|
100
|
+
sys.exit(0)
|
|
101
|
+
|
|
102
|
+
# Code changed but specs didn't — inject reminder
|
|
103
|
+
code_dirs_touched = sorted(
|
|
104
|
+
{f.split("/")[0] + "/" for f in all_files if "/" in f}
|
|
105
|
+
& {d.rstrip("/") + "/" for d in CODE_DIRS}
|
|
106
|
+
)
|
|
107
|
+
dirs_str = ", ".join(code_dirs_touched[:3])
|
|
108
|
+
|
|
109
|
+
message = (
|
|
110
|
+
f"[Spec Reminder] Code was modified in {dirs_str} "
|
|
111
|
+
"but no specs were updated. "
|
|
112
|
+
"Use /spec-update to update the relevant spec, "
|
|
113
|
+
"or /spec-new if no spec exists for this feature."
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
json.dump({"additionalContext": message}, sys.stdout)
|
|
117
|
+
sys.exit(0)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
if __name__ == "__main__":
|
|
121
|
+
main()
|
package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/ticket-linker.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Ticket linker — UserPromptSubmit hook that auto-fetches GitHub issues/PRs.
|
|
4
|
+
|
|
5
|
+
When the user's prompt contains #123 or a full GitHub issue/PR URL,
|
|
6
|
+
fetches the ticket body via `gh` and injects it as additionalContext
|
|
7
|
+
so Claude has the full ticket context without the user copy-pasting.
|
|
8
|
+
|
|
9
|
+
Reads hook input from stdin (JSON). Returns JSON on stdout.
|
|
10
|
+
Always exits 0 (advisory, never blocking).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import re
|
|
15
|
+
import subprocess
|
|
16
|
+
import sys
|
|
17
|
+
|
|
18
|
+
GH_CMD_TIMEOUT = 5
|
|
19
|
+
MAX_REFS = 3
|
|
20
|
+
BODY_CHAR_CAP = 1500
|
|
21
|
+
TOTAL_OUTPUT_CAP = 3000
|
|
22
|
+
|
|
23
|
+
# Short refs: #123 (but not inside URLs or hex colors)
|
|
24
|
+
SHORT_REF_RE = re.compile(r"(?<![/\w])#(\d+)\b")
|
|
25
|
+
|
|
26
|
+
# Full GitHub URLs: github.com/owner/repo/issues/123 or .../pull/123
|
|
27
|
+
URL_REF_RE = re.compile(r"github\.com/[^/\s]+/[^/\s]+/(?:issues|pull)/(\d+)")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def extract_refs(prompt: str) -> list[int]:
|
|
31
|
+
"""Extract deduplicated issue/PR numbers from the prompt."""
|
|
32
|
+
seen: set[int] = set()
|
|
33
|
+
refs: list[int] = []
|
|
34
|
+
|
|
35
|
+
for match in SHORT_REF_RE.finditer(prompt):
|
|
36
|
+
num = int(match.group(1))
|
|
37
|
+
if num not in seen and num > 0:
|
|
38
|
+
seen.add(num)
|
|
39
|
+
refs.append(num)
|
|
40
|
+
|
|
41
|
+
for match in URL_REF_RE.finditer(prompt):
|
|
42
|
+
num = int(match.group(1))
|
|
43
|
+
if num not in seen and num > 0:
|
|
44
|
+
seen.add(num)
|
|
45
|
+
refs.append(num)
|
|
46
|
+
|
|
47
|
+
return refs[:MAX_REFS]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def fetch_ticket(number: int) -> str | None:
|
|
51
|
+
"""Fetch a GitHub issue/PR via gh CLI. Returns formatted string or None."""
|
|
52
|
+
try:
|
|
53
|
+
result = subprocess.run(
|
|
54
|
+
[
|
|
55
|
+
"gh",
|
|
56
|
+
"issue",
|
|
57
|
+
"view",
|
|
58
|
+
str(number),
|
|
59
|
+
"--json",
|
|
60
|
+
"number,title,body,state,labels",
|
|
61
|
+
],
|
|
62
|
+
capture_output=True,
|
|
63
|
+
text=True,
|
|
64
|
+
timeout=GH_CMD_TIMEOUT,
|
|
65
|
+
)
|
|
66
|
+
except (FileNotFoundError, OSError, subprocess.TimeoutExpired):
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
if result.returncode != 0:
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
data = json.loads(result.stdout)
|
|
74
|
+
except (json.JSONDecodeError, ValueError):
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
title = data.get("title", "(no title)")
|
|
78
|
+
state = data.get("state", "UNKNOWN")
|
|
79
|
+
body = data.get("body", "") or ""
|
|
80
|
+
labels = data.get("labels", [])
|
|
81
|
+
|
|
82
|
+
label_names = [lb.get("name", "") for lb in labels if lb.get("name")]
|
|
83
|
+
label_str = ", ".join(label_names) if label_names else "none"
|
|
84
|
+
|
|
85
|
+
# Truncate body
|
|
86
|
+
if len(body) > BODY_CHAR_CAP:
|
|
87
|
+
body = body[:BODY_CHAR_CAP] + "\n...(truncated)"
|
|
88
|
+
|
|
89
|
+
parts = [
|
|
90
|
+
f"[Ticket #{number}] {title}",
|
|
91
|
+
f"State: {state} | Labels: {label_str}",
|
|
92
|
+
]
|
|
93
|
+
if body.strip():
|
|
94
|
+
parts.append(body.strip())
|
|
95
|
+
|
|
96
|
+
return "\n".join(parts)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def main():
|
|
100
|
+
raw = sys.stdin.read().strip()
|
|
101
|
+
if not raw:
|
|
102
|
+
sys.exit(0)
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
data = json.loads(raw)
|
|
106
|
+
except (json.JSONDecodeError, ValueError):
|
|
107
|
+
sys.exit(0)
|
|
108
|
+
|
|
109
|
+
prompt = data.get("prompt", "")
|
|
110
|
+
if not prompt:
|
|
111
|
+
sys.exit(0)
|
|
112
|
+
|
|
113
|
+
refs = extract_refs(prompt)
|
|
114
|
+
if not refs:
|
|
115
|
+
sys.exit(0)
|
|
116
|
+
|
|
117
|
+
tickets: list[str] = []
|
|
118
|
+
for number in refs:
|
|
119
|
+
ticket = fetch_ticket(number)
|
|
120
|
+
if ticket:
|
|
121
|
+
tickets.append(ticket)
|
|
122
|
+
|
|
123
|
+
if not tickets:
|
|
124
|
+
sys.exit(0)
|
|
125
|
+
|
|
126
|
+
output = "\n\n---\n\n".join(tickets)
|
|
127
|
+
|
|
128
|
+
# Cap total output
|
|
129
|
+
if len(output) > TOTAL_OUTPUT_CAP:
|
|
130
|
+
output = output[:TOTAL_OUTPUT_CAP] + "\n...(truncated)"
|
|
131
|
+
|
|
132
|
+
json.dump({"additionalContext": output}, sys.stdout)
|
|
133
|
+
sys.exit(0)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
if __name__ == "__main__":
|
|
137
|
+
main()
|
package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/todo-harvester.py
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
TODO/FIXME harvester — SessionStart hook that surfaces tech debt markers.
|
|
4
|
+
|
|
5
|
+
Greps the codebase for TODO/FIXME/HACK/XXX comments and injects a count
|
|
6
|
+
plus the top items as additionalContext so Claude can proactively mention
|
|
7
|
+
tech debt when relevant.
|
|
8
|
+
|
|
9
|
+
Reads hook input from stdin (JSON). Returns JSON on stdout.
|
|
10
|
+
Always exits 0 (advisory, never blocking).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import subprocess
|
|
16
|
+
import sys
|
|
17
|
+
|
|
18
|
+
GREP_TIMEOUT = 5
|
|
19
|
+
MAX_ITEMS = 10
|
|
20
|
+
TOTAL_OUTPUT_CAP = 800
|
|
21
|
+
|
|
22
|
+
SOURCE_INCLUDES = [
|
|
23
|
+
"--include=*.py",
|
|
24
|
+
"--include=*.ts",
|
|
25
|
+
"--include=*.tsx",
|
|
26
|
+
"--include=*.js",
|
|
27
|
+
"--include=*.jsx",
|
|
28
|
+
"--include=*.go",
|
|
29
|
+
"--include=*.rs",
|
|
30
|
+
"--include=*.sh",
|
|
31
|
+
"--include=*.svelte",
|
|
32
|
+
"--include=*.vue",
|
|
33
|
+
"--include=*.rb",
|
|
34
|
+
"--include=*.java",
|
|
35
|
+
"--include=*.kt",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
EXCLUDE_DIRS = [
|
|
39
|
+
"--exclude-dir=node_modules",
|
|
40
|
+
"--exclude-dir=.git",
|
|
41
|
+
"--exclude-dir=__pycache__",
|
|
42
|
+
"--exclude-dir=.venv",
|
|
43
|
+
"--exclude-dir=venv",
|
|
44
|
+
"--exclude-dir=dist",
|
|
45
|
+
"--exclude-dir=build",
|
|
46
|
+
"--exclude-dir=vendor",
|
|
47
|
+
"--exclude-dir=.next",
|
|
48
|
+
"--exclude-dir=.nuxt",
|
|
49
|
+
"--exclude-dir=target",
|
|
50
|
+
"--exclude-dir=.mypy_cache",
|
|
51
|
+
"--exclude-dir=.pytest_cache",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def main():
|
|
56
|
+
try:
|
|
57
|
+
json.load(sys.stdin)
|
|
58
|
+
except (json.JSONDecodeError, ValueError):
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
cwd = os.getcwd()
|
|
62
|
+
|
|
63
|
+
cmd = (
|
|
64
|
+
["grep", "-rn", "-E", r"\b(TODO|FIXME|HACK|XXX)\b"]
|
|
65
|
+
+ SOURCE_INCLUDES
|
|
66
|
+
+ EXCLUDE_DIRS
|
|
67
|
+
+ [cwd]
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
result = subprocess.run(
|
|
72
|
+
cmd,
|
|
73
|
+
capture_output=True,
|
|
74
|
+
text=True,
|
|
75
|
+
timeout=GREP_TIMEOUT,
|
|
76
|
+
)
|
|
77
|
+
except (FileNotFoundError, OSError, subprocess.TimeoutExpired):
|
|
78
|
+
sys.exit(0)
|
|
79
|
+
|
|
80
|
+
# grep returns 1 for no matches — that's fine
|
|
81
|
+
output = result.stdout.strip()
|
|
82
|
+
if not output:
|
|
83
|
+
sys.exit(0)
|
|
84
|
+
|
|
85
|
+
lines = output.splitlines()
|
|
86
|
+
total_count = len(lines)
|
|
87
|
+
|
|
88
|
+
# Count unique files
|
|
89
|
+
files_seen: set[str] = set()
|
|
90
|
+
items: list[str] = []
|
|
91
|
+
|
|
92
|
+
for line in lines:
|
|
93
|
+
# Format: filepath:linenum:content
|
|
94
|
+
parts = line.split(":", 2)
|
|
95
|
+
if len(parts) >= 3:
|
|
96
|
+
filepath = parts[0]
|
|
97
|
+
files_seen.add(filepath)
|
|
98
|
+
|
|
99
|
+
if len(items) < MAX_ITEMS:
|
|
100
|
+
# Make path relative to cwd for readability
|
|
101
|
+
rel_path = os.path.relpath(filepath, cwd)
|
|
102
|
+
line_num = parts[1]
|
|
103
|
+
content = parts[2].strip()
|
|
104
|
+
|
|
105
|
+
# Trim long lines
|
|
106
|
+
if len(content) > 80:
|
|
107
|
+
content = content[:77] + "..."
|
|
108
|
+
|
|
109
|
+
items.append(f" {rel_path}:{line_num}: {content}")
|
|
110
|
+
|
|
111
|
+
file_count = len(files_seen)
|
|
112
|
+
header = (
|
|
113
|
+
f"[Tech Debt] {total_count} TODO/FIXME items found across {file_count} files"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
if items:
|
|
117
|
+
body = header + "\nTop items:\n" + "\n".join(items)
|
|
118
|
+
else:
|
|
119
|
+
body = header
|
|
120
|
+
|
|
121
|
+
# Cap total output
|
|
122
|
+
if len(body) > TOTAL_OUTPUT_CAP:
|
|
123
|
+
body = body[:TOTAL_OUTPUT_CAP] + "\n...(truncated)"
|
|
124
|
+
|
|
125
|
+
json.dump({"additionalContext": body}, sys.stdout)
|
|
126
|
+
sys.exit(0)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
if __name__ == "__main__":
|
|
130
|
+
main()
|