claude-code-starter 0.14.0 → 0.15.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/dist/cli.js +482 -245
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { execSync, spawn } from "child_process";
|
|
5
|
-
import
|
|
6
|
-
import
|
|
5
|
+
import fs5 from "fs";
|
|
6
|
+
import path5 from "path";
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
8
8
|
import ora from "ora";
|
|
9
9
|
import pc from "picocolors";
|
|
@@ -511,38 +511,12 @@ function countSourceFiles(rootDir, _languages) {
|
|
|
511
511
|
countFiles(rootDir);
|
|
512
512
|
return count;
|
|
513
513
|
}
|
|
514
|
-
function summarizeTechStack(stack) {
|
|
515
|
-
const parts = [];
|
|
516
|
-
if (stack.primaryLanguage) {
|
|
517
|
-
parts.push(`Language: ${stack.primaryLanguage}`);
|
|
518
|
-
}
|
|
519
|
-
if (stack.primaryFramework) {
|
|
520
|
-
parts.push(`Framework: ${stack.primaryFramework}`);
|
|
521
|
-
}
|
|
522
|
-
if (stack.packageManager) {
|
|
523
|
-
parts.push(`Package Manager: ${stack.packageManager}`);
|
|
524
|
-
}
|
|
525
|
-
if (stack.testingFramework) {
|
|
526
|
-
parts.push(`Testing: ${stack.testingFramework}`);
|
|
527
|
-
}
|
|
528
|
-
if (stack.isMonorepo) {
|
|
529
|
-
parts.push("Monorepo: yes");
|
|
530
|
-
}
|
|
531
|
-
return parts.join(" | ");
|
|
532
|
-
}
|
|
533
514
|
|
|
534
515
|
// src/generator.ts
|
|
535
516
|
import fs2 from "fs";
|
|
536
517
|
import path2 from "path";
|
|
537
518
|
function ensureDirectories(rootDir) {
|
|
538
|
-
const dirs = [
|
|
539
|
-
".claude",
|
|
540
|
-
".claude/skills",
|
|
541
|
-
".claude/agents",
|
|
542
|
-
".claude/rules",
|
|
543
|
-
".claude/commands",
|
|
544
|
-
".claude/state"
|
|
545
|
-
];
|
|
519
|
+
const dirs = [".claude", ".claude/skills", ".claude/agents", ".claude/rules", ".claude/commands"];
|
|
546
520
|
for (const dir of dirs) {
|
|
547
521
|
fs2.mkdirSync(path2.join(rootDir, dir), { recursive: true });
|
|
548
522
|
}
|
|
@@ -625,6 +599,239 @@ function writeSettings(rootDir, stack) {
|
|
|
625
599
|
fs2.writeFileSync(fullPath, content);
|
|
626
600
|
}
|
|
627
601
|
|
|
602
|
+
// src/hooks.ts
|
|
603
|
+
import fs3 from "fs";
|
|
604
|
+
import path3 from "path";
|
|
605
|
+
var HOOK_SCRIPT = String.raw`#!/usr/bin/env node
|
|
606
|
+
/**
|
|
607
|
+
* Block Dangerous Commands - PreToolUse Hook for Bash
|
|
608
|
+
* Blocks dangerous patterns before execution.
|
|
609
|
+
*
|
|
610
|
+
* SAFETY_LEVEL: 'critical' | 'high' | 'strict'
|
|
611
|
+
* critical - Only catastrophic: rm -rf ~, dd to disk, fork bombs
|
|
612
|
+
* high - + risky: force push main, secrets exposure, git reset --hard
|
|
613
|
+
* strict - + cautionary: any force push, sudo rm, docker prune
|
|
614
|
+
*/
|
|
615
|
+
|
|
616
|
+
const fs = require('fs');
|
|
617
|
+
const path = require('path');
|
|
618
|
+
|
|
619
|
+
const SAFETY_LEVEL = 'high';
|
|
620
|
+
|
|
621
|
+
const PATTERNS = [
|
|
622
|
+
// CRITICAL — Catastrophic, unrecoverable
|
|
623
|
+
|
|
624
|
+
// Filesystem destruction
|
|
625
|
+
{ level: 'critical', id: 'rm-home', regex: /\brm\s+(-.+\s+)*["']?~\/?["']?(\s|$|[;&|])/, reason: 'rm targeting home directory' },
|
|
626
|
+
{ level: 'critical', id: 'rm-home-var', regex: /\brm\s+(-.+\s+)*["']?\$HOME["']?(\s|$|[;&|])/, reason: 'rm targeting $HOME' },
|
|
627
|
+
{ level: 'critical', id: 'rm-home-trailing', regex: /\brm\s+.+\s+["']?(~\/?|\$HOME)["']?(\s*$|[;&|])/, reason: 'rm with trailing ~/ or $HOME' },
|
|
628
|
+
{ level: 'critical', id: 'rm-root', regex: /\brm\s+(-.+\s+)*\/(\*|\s|$|[;&|])/, reason: 'rm targeting root filesystem' },
|
|
629
|
+
{ level: 'critical', id: 'rm-system', regex: /\brm\s+(-.+\s+)*\/(etc|usr|var|bin|sbin|lib|boot|dev|proc|sys)(\/|\s|$)/, reason: 'rm targeting system directory' },
|
|
630
|
+
{ level: 'critical', id: 'rm-cwd', regex: /\brm\s+(-.+\s+)*(\.\/?|\*|\.\/\*)(\s|$|[;&|])/, reason: 'rm deleting current directory contents' },
|
|
631
|
+
|
|
632
|
+
// Disk operations
|
|
633
|
+
{ level: 'critical', id: 'dd-disk', regex: /\bdd\b.+of=\/dev\/(sd[a-z]|nvme|hd[a-z]|vd[a-z]|xvd[a-z])/, reason: 'dd writing to disk device' },
|
|
634
|
+
{ level: 'critical', id: 'mkfs', regex: /\bmkfs(\.\w+)?\s+\/dev\/(sd[a-z]|nvme|hd[a-z]|vd[a-z])/, reason: 'mkfs formatting disk' },
|
|
635
|
+
{ level: 'critical', id: 'fdisk', regex: /\b(fdisk|wipefs|parted)\s+\/dev\//, reason: 'disk partitioning/wiping operation' },
|
|
636
|
+
|
|
637
|
+
// Shell exploits
|
|
638
|
+
{ level: 'critical', id: 'fork-bomb', regex: /:\(\)\s*\{.*:\s*\|\s*:.*&/, reason: 'fork bomb detected' },
|
|
639
|
+
|
|
640
|
+
// Git — history destruction
|
|
641
|
+
{ level: 'critical', id: 'git-filter', regex: /\bgit\s+(filter-branch|filter-repo)\b/, reason: 'git history rewriting blocked' },
|
|
642
|
+
{ level: 'critical', id: 'git-reflog-exp', regex: /\bgit\s+(reflog\s+expire|gc\s+--prune|prune)\b/, reason: 'removes git recovery safety net' },
|
|
643
|
+
|
|
644
|
+
// HIGH — Significant risk, data loss, security exposure
|
|
645
|
+
|
|
646
|
+
// Remote code execution
|
|
647
|
+
{ level: 'high', id: 'curl-pipe-sh', regex: /\b(curl|wget)\b.+\|\s*(ba)?sh\b/, reason: 'piping URL to shell (RCE risk)' },
|
|
648
|
+
|
|
649
|
+
// Git — destructive operations
|
|
650
|
+
{ level: 'high', id: 'git-force-main', regex: /\bgit\s+push\b(?!.+--force-with-lease).+(--force|-f)\b.+\b(main|master)\b/, reason: 'force push to main/master' },
|
|
651
|
+
{ level: 'high', id: 'git-reset-hard', regex: /\bgit\s+reset\s+--hard/, reason: 'git reset --hard loses uncommitted work' },
|
|
652
|
+
{ level: 'high', id: 'git-clean-f', regex: /\bgit\s+clean\s+(-\w*f|-f)/, reason: 'git clean -f deletes untracked files' },
|
|
653
|
+
{ level: 'high', id: 'git-no-verify', regex: /\bgit\b.+--no-verify/, reason: '--no-verify skips safety hooks' },
|
|
654
|
+
{ level: 'high', id: 'git-stash-destruct', regex: /\bgit\s+stash\s+(drop|clear|pop)\b/, reason: 'destructive git stash operation' },
|
|
655
|
+
{ level: 'high', id: 'git-branch-D', regex: /\bgit\s+branch\s+(-D|--delete\s+--force)\b/, reason: 'git branch -D force-deletes branch' },
|
|
656
|
+
{ level: 'high', id: 'git-checkout-force', regex: /\bgit\s+checkout\s+(-f|--\s+\.)/, reason: 'git checkout -f/-- . discards changes' },
|
|
657
|
+
{ level: 'high', id: 'git-restore-destruct', regex: /\bgit\s+restore\s+(--staged\s+--worktree|\.)/, reason: 'git restore discards changes' },
|
|
658
|
+
{ level: 'high', id: 'git-update-ref', regex: /\bgit\s+(update-ref|symbolic-ref|replace)\b/, reason: 'git ref manipulation blocked' },
|
|
659
|
+
{ level: 'high', id: 'git-config-global', regex: /\bgit\s+config\s+--(global|system)\b/, reason: 'git global/system config blocked' },
|
|
660
|
+
{ level: 'high', id: 'git-tag-delete', regex: /\bgit\s+tag\s+(-d|--delete)\b/, reason: 'git tag deletion blocked' },
|
|
661
|
+
|
|
662
|
+
// Git — write operations (user handles manually)
|
|
663
|
+
{ level: 'high', id: 'git-push', regex: /\bgit\s+push\b/, reason: 'git push blocked — user handles manually' },
|
|
664
|
+
{ level: 'high', id: 'git-pull', regex: /\bgit\s+pull\b/, reason: 'git pull blocked — user handles manually' },
|
|
665
|
+
{ level: 'high', id: 'git-fetch', regex: /\bgit\s+fetch\b/, reason: 'git fetch blocked — user handles manually' },
|
|
666
|
+
{ level: 'high', id: 'git-clone', regex: /\bgit\s+clone\b/, reason: 'git clone blocked — user handles manually' },
|
|
667
|
+
{ level: 'high', id: 'git-add', regex: /\bgit\s+(add|stage)\b/, reason: 'git add/stage blocked — user handles manually' },
|
|
668
|
+
{ level: 'high', id: 'git-commit', regex: /\bgit\s+commit\b/, reason: 'git commit blocked — user handles manually' },
|
|
669
|
+
{ level: 'high', id: 'git-merge', regex: /\bgit\s+merge\b/, reason: 'git merge blocked — user handles manually' },
|
|
670
|
+
{ level: 'high', id: 'git-rebase', regex: /\bgit\s+rebase\b/, reason: 'git rebase blocked — user handles manually' },
|
|
671
|
+
{ level: 'high', id: 'git-reset', regex: /\bgit\s+reset\b/, reason: 'git reset blocked — user handles manually' },
|
|
672
|
+
{ level: 'high', id: 'git-remote-mod', regex: /\bgit\s+remote\s+(add|set-url|remove)\b/, reason: 'git remote modification blocked' },
|
|
673
|
+
{ level: 'high', id: 'git-submodule', regex: /\bgit\s+submodule\s+(add|update)\b/, reason: 'git submodule operation blocked' },
|
|
674
|
+
|
|
675
|
+
// Credentials & secrets
|
|
676
|
+
{ level: 'high', id: 'chmod-777', regex: /\bchmod\b.+\b777\b/, reason: 'chmod 777 is a security risk' },
|
|
677
|
+
{ level: 'high', id: 'cat-env', regex: /\b(cat|less|head|tail|more)\s+\.env\b/, reason: 'reading .env file exposes secrets' },
|
|
678
|
+
{ level: 'high', id: 'cat-secrets', regex: /\b(cat|less|head|tail|more)\b.+(credentials|secrets?|\.pem|\.key|id_rsa|id_ed25519)/i, reason: 'reading secrets file' },
|
|
679
|
+
{ level: 'high', id: 'env-dump', regex: /\b(printenv|^env)\s*([;&|]|$)/, reason: 'env dump may expose secrets' },
|
|
680
|
+
{ level: 'high', id: 'echo-secret', regex: /\becho\b.+\$\w*(SECRET|KEY|TOKEN|PASSWORD|API_|PRIVATE)/i, reason: 'echoing secret variable' },
|
|
681
|
+
{ level: 'high', id: 'rm-ssh', regex: /\brm\b.+\.ssh\/(id_|authorized_keys|known_hosts)/, reason: 'deleting SSH keys' },
|
|
682
|
+
{ level: 'high', id: 'security-keychain', regex: /\bsecurity\s+find-generic-password\b/, reason: 'keychain access blocked' },
|
|
683
|
+
{ level: 'high', id: 'gpg-export-secret', regex: /\bgpg\s+--export-secret-keys\b/, reason: 'GPG secret key export blocked' },
|
|
684
|
+
{ level: 'high', id: 'history-cmd', regex: /\bhistory\b/, reason: 'history may expose secrets' },
|
|
685
|
+
|
|
686
|
+
// Destructive system commands
|
|
687
|
+
{ level: 'high', id: 'elevated-priv', regex: /\b(sudo|doas|pkexec)\b/, reason: 'elevated privilege command blocked' },
|
|
688
|
+
{ level: 'high', id: 'su-cmd', regex: /\bsu\b/, reason: 'su (switch user) blocked' },
|
|
689
|
+
{ level: 'high', id: 'chmod-R', regex: /\bchmod\s+(-\w*R|-R)/, reason: 'recursive chmod blocked' },
|
|
690
|
+
{ level: 'high', id: 'chown-R', regex: /\bchown\s+(-\w*R|-R)/, reason: 'recursive chown blocked' },
|
|
691
|
+
{ level: 'high', id: 'kill-all', regex: /\bkill\s+-9\s+-1\b/, reason: 'kill all processes blocked' },
|
|
692
|
+
{ level: 'high', id: 'killall', regex: /\b(killall|pkill\s+-9)\b/, reason: 'mass process killing blocked' },
|
|
693
|
+
{ level: 'high', id: 'truncate-zero', regex: /\btruncate\s+-s\s*0\b/, reason: 'truncating file to zero blocked' },
|
|
694
|
+
{ level: 'high', id: 'empty-file', regex: /\bcat\s+\/dev\/null\s*>/, reason: 'emptying file via /dev/null blocked' },
|
|
695
|
+
{ level: 'high', id: 'crontab-r', regex: /\bcrontab\s+-r/, reason: 'removes all cron jobs' },
|
|
696
|
+
|
|
697
|
+
// Docker
|
|
698
|
+
{ level: 'high', id: 'docker-vol-rm', regex: /\bdocker\s+volume\s+(rm|prune)/, reason: 'docker volume deletion loses data' },
|
|
699
|
+
{ level: 'high', id: 'docker-push', regex: /\bdocker\s+push\b/, reason: 'docker push blocked' },
|
|
700
|
+
{ level: 'high', id: 'docker-rm-all', regex: /\bdocker\s+rm\s+-f\b.+\$\(docker\s+ps/, reason: 'docker rm all containers blocked' },
|
|
701
|
+
{ level: 'high', id: 'docker-sys-prune-a', regex: /\bdocker\s+system\s+prune\s+-a/, reason: 'docker system prune -a blocked' },
|
|
702
|
+
{ level: 'high', id: 'docker-compose-destr', regex: /\bdocker[\s-]compose\s+down\s+(-v|--rmi)/, reason: 'docker-compose destructive down blocked' },
|
|
703
|
+
|
|
704
|
+
// Publishing & deployment
|
|
705
|
+
{ level: 'high', id: 'npm-publish', regex: /\bnpm\s+(publish|unpublish|deprecate)\b/, reason: 'npm publishing blocked' },
|
|
706
|
+
{ level: 'high', id: 'npm-audit-force', regex: /\bnpm\s+audit\s+fix\s+--force\b/, reason: 'npm audit fix --force can break deps' },
|
|
707
|
+
{ level: 'high', id: 'cargo-publish', regex: /\bcargo\s+publish\b/, reason: 'cargo publish blocked' },
|
|
708
|
+
{ level: 'high', id: 'pip-twine-upload', regex: /\b(pip|twine)\s+upload\b/, reason: 'Python package upload blocked' },
|
|
709
|
+
{ level: 'high', id: 'gem-push', regex: /\bgem\s+push\b/, reason: 'gem push blocked' },
|
|
710
|
+
{ level: 'high', id: 'pod-push', regex: /\bpod\s+trunk\s+push\b/, reason: 'pod trunk push blocked' },
|
|
711
|
+
{ level: 'high', id: 'vercel-prod', regex: /\bvercel\b.+--prod/, reason: 'vercel production deploy blocked' },
|
|
712
|
+
{ level: 'high', id: 'netlify-prod', regex: /\bnetlify\s+deploy\b.+--prod/, reason: 'netlify production deploy blocked' },
|
|
713
|
+
{ level: 'high', id: 'fly-deploy', regex: /\bfly\s+deploy\b/, reason: 'fly deploy blocked' },
|
|
714
|
+
{ level: 'high', id: 'firebase-deploy', regex: /\bfirebase\s+deploy\b/, reason: 'firebase deploy blocked' },
|
|
715
|
+
{ level: 'high', id: 'terraform', regex: /\bterraform\s+(apply|destroy)\b/, reason: 'terraform apply/destroy blocked' },
|
|
716
|
+
{ level: 'high', id: 'pulumi-cdktf', regex: /\b(pulumi|cdktf)\s+destroy\b/, reason: 'infrastructure destroy blocked' },
|
|
717
|
+
{ level: 'high', id: 'kubectl-mutate', regex: /\bkubectl\s+(apply|delete|drain)\b/, reason: 'kubectl mutating operation blocked' },
|
|
718
|
+
{ level: 'high', id: 'kubectl-scale-zero', regex: /\bkubectl\s+scale\b.+--replicas=0/, reason: 'kubectl scale to zero blocked' },
|
|
719
|
+
{ level: 'high', id: 'helm-ops', regex: /\bhelm\s+(install|uninstall|upgrade)\b/, reason: 'helm operation blocked' },
|
|
720
|
+
{ level: 'high', id: 'heroku', regex: /\bheroku\b/, reason: 'heroku command blocked' },
|
|
721
|
+
{ level: 'high', id: 'eb-terminate', regex: /\beb\s+terminate\b/, reason: 'eb terminate blocked' },
|
|
722
|
+
{ level: 'high', id: 'serverless-remove', regex: /\bserverless\s+remove\b/, reason: 'serverless remove blocked' },
|
|
723
|
+
{ level: 'high', id: 'cap-prod-deploy', regex: /\bcap\s+production\s+deploy\b/, reason: 'production deploy blocked' },
|
|
724
|
+
{ level: 'high', id: 'cloud-delete', regex: /\b(aws\s+cloudformation\s+delete-stack|gcloud\s+projects\s+delete|az\s+group\s+delete)\b/, reason: 'cloud resource deletion blocked' },
|
|
725
|
+
|
|
726
|
+
// Network & infrastructure
|
|
727
|
+
{ level: 'high', id: 'curl-mutating', regex: /\bcurl\b.+-X\s*(POST|PUT|DELETE|PATCH)\b/, reason: 'mutating HTTP request blocked' },
|
|
728
|
+
{ level: 'high', id: 'ssh-remote', regex: /\bssh\s/, reason: 'SSH remote connection blocked' },
|
|
729
|
+
{ level: 'high', id: 'scp-remote', regex: /\bscp\s/, reason: 'SCP remote copy blocked' },
|
|
730
|
+
{ level: 'high', id: 'rsync-delete', regex: /\brsync\b.+--delete/, reason: 'rsync --delete blocked' },
|
|
731
|
+
{ level: 'high', id: 'firewall', regex: /\b(iptables\s+-F|ufw\s+disable)\b/, reason: 'firewall manipulation blocked' },
|
|
732
|
+
{ level: 'high', id: 'network-kill', regex: /\bifconfig\s+\w+\s+down\b/, reason: 'network interface down blocked' },
|
|
733
|
+
{ level: 'high', id: 'route-delete', regex: /\broute\s+del\s+default\b/, reason: 'default route deletion blocked' },
|
|
734
|
+
|
|
735
|
+
// Database
|
|
736
|
+
{ level: 'high', id: 'sql-drop', regex: /\b(DROP\s+(DATABASE|TABLE)|TRUNCATE\s+TABLE)\b/i, reason: 'SQL drop/truncate blocked' },
|
|
737
|
+
{ level: 'high', id: 'sql-mass-delete', regex: /\bDELETE\s+FROM\b.+\bWHERE\s+1\s*=\s*1/i, reason: 'SQL mass delete blocked' },
|
|
738
|
+
{ level: 'high', id: 'redis-flush', regex: /\bredis-cli\s+(FLUSHALL|FLUSHDB)\b/, reason: 'redis flush blocked' },
|
|
739
|
+
{ level: 'high', id: 'orm-reset', regex: /\b(prisma\s+migrate\s+reset|rails\s+db:(drop|reset)|django\s+flush)\b/, reason: 'ORM database reset blocked' },
|
|
740
|
+
{ level: 'high', id: 'alembic-downgrade', regex: /\balembic\s+downgrade\s+base\b/, reason: 'alembic downgrade base blocked' },
|
|
741
|
+
{ level: 'high', id: 'mongo-drop', regex: /\bmongosh\b.+dropDatabase/, reason: 'MongoDB drop database blocked' },
|
|
742
|
+
|
|
743
|
+
// STRICT — Cautionary, context-dependent
|
|
744
|
+
{ level: 'strict', id: 'git-checkout-dot', regex: /\bgit\s+checkout\s+\./, reason: 'git checkout . discards changes' },
|
|
745
|
+
{ level: 'strict', id: 'docker-prune', regex: /\bdocker\s+(system|image)\s+prune/, reason: 'docker prune removes images' },
|
|
746
|
+
];
|
|
747
|
+
|
|
748
|
+
const LEVELS = { critical: 1, high: 2, strict: 3 };
|
|
749
|
+
const EMOJIS = { critical: '\u{1F6A8}', high: '\u26D4', strict: '\u26A0\uFE0F' };
|
|
750
|
+
const LOG_DIR = path.join(process.env.HOME || '/tmp', '.claude', 'hooks-logs');
|
|
751
|
+
|
|
752
|
+
function log(data) {
|
|
753
|
+
try {
|
|
754
|
+
if (!fs.existsSync(LOG_DIR)) fs.mkdirSync(LOG_DIR, { recursive: true });
|
|
755
|
+
const file = path.join(LOG_DIR, new Date().toISOString().slice(0, 10) + '.jsonl');
|
|
756
|
+
fs.appendFileSync(file, JSON.stringify({ ts: new Date().toISOString(), ...data }) + '\n');
|
|
757
|
+
} catch {}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
function checkCommand(cmd, safetyLevel) {
|
|
761
|
+
safetyLevel = safetyLevel || SAFETY_LEVEL;
|
|
762
|
+
const threshold = LEVELS[safetyLevel] || 2;
|
|
763
|
+
for (const p of PATTERNS) {
|
|
764
|
+
if (LEVELS[p.level] <= threshold && p.regex.test(cmd)) {
|
|
765
|
+
return { blocked: true, pattern: p };
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
return { blocked: false, pattern: null };
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
async function main() {
|
|
772
|
+
let input = '';
|
|
773
|
+
for await (const chunk of process.stdin) input += chunk;
|
|
774
|
+
|
|
775
|
+
try {
|
|
776
|
+
const data = JSON.parse(input);
|
|
777
|
+
const { tool_name, tool_input, session_id, cwd, permission_mode } = data;
|
|
778
|
+
if (tool_name !== 'Bash') return console.log('{}');
|
|
779
|
+
|
|
780
|
+
const cmd = tool_input?.command || '';
|
|
781
|
+
const result = checkCommand(cmd);
|
|
782
|
+
|
|
783
|
+
if (result.blocked) {
|
|
784
|
+
const p = result.pattern;
|
|
785
|
+
log({ level: 'BLOCKED', id: p.id, priority: p.level, cmd, session_id, cwd, permission_mode });
|
|
786
|
+
return console.log(JSON.stringify({
|
|
787
|
+
hookSpecificOutput: {
|
|
788
|
+
hookEventName: 'PreToolUse',
|
|
789
|
+
permissionDecision: 'deny',
|
|
790
|
+
permissionDecisionReason: EMOJIS[p.level] + ' [' + p.id + '] ' + p.reason
|
|
791
|
+
}
|
|
792
|
+
}));
|
|
793
|
+
}
|
|
794
|
+
console.log('{}');
|
|
795
|
+
} catch (e) {
|
|
796
|
+
log({ level: 'ERROR', error: e.message });
|
|
797
|
+
console.log('{}');
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
if (require.main === module) {
|
|
802
|
+
main();
|
|
803
|
+
} else {
|
|
804
|
+
module.exports = { PATTERNS, LEVELS, SAFETY_LEVEL, checkCommand };
|
|
805
|
+
}
|
|
806
|
+
`;
|
|
807
|
+
function installHook(rootDir) {
|
|
808
|
+
const hooksDir = path3.join(rootDir, ".claude", "hooks");
|
|
809
|
+
const hookPath = path3.join(hooksDir, "block-dangerous-commands.js");
|
|
810
|
+
const settingsPath = path3.join(rootDir, ".claude", "settings.json");
|
|
811
|
+
fs3.mkdirSync(hooksDir, { recursive: true });
|
|
812
|
+
fs3.writeFileSync(hookPath, HOOK_SCRIPT);
|
|
813
|
+
fs3.chmodSync(hookPath, 493);
|
|
814
|
+
try {
|
|
815
|
+
const existing = fs3.existsSync(settingsPath) ? JSON.parse(fs3.readFileSync(settingsPath, "utf-8")) : {};
|
|
816
|
+
existing.hooks = {
|
|
817
|
+
...existing.hooks,
|
|
818
|
+
PreToolUse: [
|
|
819
|
+
{
|
|
820
|
+
matcher: "Bash",
|
|
821
|
+
hooks: [
|
|
822
|
+
{
|
|
823
|
+
type: "command",
|
|
824
|
+
command: "node .claude/hooks/block-dangerous-commands.js"
|
|
825
|
+
}
|
|
826
|
+
]
|
|
827
|
+
}
|
|
828
|
+
]
|
|
829
|
+
};
|
|
830
|
+
fs3.writeFileSync(settingsPath, JSON.stringify(existing, null, 2));
|
|
831
|
+
} catch {
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
628
835
|
// src/prompt.ts
|
|
629
836
|
function getAnalysisPrompt(projectInfo) {
|
|
630
837
|
const context = buildContextSection(projectInfo);
|
|
@@ -658,14 +865,14 @@ ${templateVars}
|
|
|
658
865
|
|
|
659
866
|
1. Read this entire prompt to understand all phases
|
|
660
867
|
2. Execute Phase 1 completely - read files, analyze code, gather all data
|
|
661
|
-
3. Execute Phase 2 - generate the CLAUDE.md using only discovered information
|
|
868
|
+
3. Execute Phase 2 - generate the CLAUDE.md (max 120 lines) using only discovered information
|
|
662
869
|
4. Execute Phase 3 - verify quality before writing
|
|
663
870
|
5. Use the Write tool to create \`.claude/CLAUDE.md\` with the final content
|
|
664
|
-
6. Execute Phase 4 - generate ALL skill files
|
|
871
|
+
6. Execute Phase 4 - generate ALL skill files (4 core + framework-specific if detected)
|
|
665
872
|
7. Execute Phase 5 - generate agent files
|
|
666
873
|
8. Execute Phase 6 - generate rule files
|
|
667
|
-
9. Execute Phase 7 - generate command files
|
|
668
|
-
10. Run the Anti-Redundancy
|
|
874
|
+
9. Execute Phase 7 - generate command files (2 commands: analyze, code-review)
|
|
875
|
+
10. Run the Anti-Redundancy Enforcement checks one final time across ALL generated files \u2014 if any convention is restated, any command is duplicated, or any rule lacks a \`paths:\` filter, fix it before proceeding
|
|
669
876
|
11. Output a brief summary of what was generated and any gaps found
|
|
670
877
|
|
|
671
878
|
Do NOT output file contents to stdout. Write all files to disk using the Write tool.
|
|
@@ -830,14 +1037,26 @@ frequencies, so place information where it costs the least while remaining acces
|
|
|
830
1037
|
5. **Agents have zero main-context cost.** Put detailed checklists and review criteria in agent files \u2014 they run in subprocesses and don't consume the user's context window.
|
|
831
1038
|
6. **Each piece of information must live in exactly ONE place.** If it's in CLAUDE.md, don't repeat it in rules, skills, or commands.
|
|
832
1039
|
|
|
833
|
-
### Anti-Redundancy
|
|
1040
|
+
### Anti-Redundancy Enforcement
|
|
834
1041
|
|
|
835
|
-
Before writing EACH artifact,
|
|
836
|
-
|
|
837
|
-
-
|
|
838
|
-
-
|
|
839
|
-
-
|
|
840
|
-
-
|
|
1042
|
+
Before writing EACH artifact, apply these hard constraints:
|
|
1043
|
+
|
|
1044
|
+
- **REJECT** any artifact that restates a convention from CLAUDE.md. If a convention appears in CLAUDE.md, it MUST NOT appear in any other file. Not paraphrased, not summarized, not restated in different words.
|
|
1045
|
+
- **Test commands, lint commands, and build commands** MUST appear in exactly ONE place: CLAUDE.md's Common Commands section. Skills and agents MUST write "See Common Commands in CLAUDE.md" instead.
|
|
1046
|
+
- **All rules MUST have a \`paths:\` filter** \u2014 no unfiltered rules.
|
|
1047
|
+
- **Cross-references replace copies** \u2014 write "Follow conventions in CLAUDE.md" instead of restating any convention.
|
|
1048
|
+
|
|
1049
|
+
#### Forbidden Duplication List
|
|
1050
|
+
|
|
1051
|
+
The following MUST NOT appear in skills, agents, rules, or commands \u2014 they belong exclusively in CLAUDE.md:
|
|
1052
|
+
- Test commands (the literal test runner invocation)
|
|
1053
|
+
- Lint commands (the literal linter invocation)
|
|
1054
|
+
- Build commands (the literal build invocation)
|
|
1055
|
+
- Import convention descriptions (absolute vs relative, ordering, type imports)
|
|
1056
|
+
- Naming convention descriptions (camelCase, PascalCase, file naming)
|
|
1057
|
+
- Commit format descriptions (conventional commits, message format)
|
|
1058
|
+
- Anti-patterns list (things to avoid)
|
|
1059
|
+
- Testing framework syntax examples (describe/it/expect \u2014 belongs in test-writer agent only)
|
|
841
1060
|
|
|
842
1061
|
---
|
|
843
1062
|
|
|
@@ -936,9 +1155,13 @@ Read at least 3-5 source files and document the ACTUAL patterns used:
|
|
|
936
1155
|
Using ONLY information discovered in Phase 1, generate the \`.claude/CLAUDE.md\` file.
|
|
937
1156
|
Every section must contain PROJECT-SPECIFIC content. Skip sections that don't apply.
|
|
938
1157
|
|
|
1158
|
+
**The CLAUDE.md MUST NOT exceed 120 lines. Prioritize density over completeness.**
|
|
1159
|
+
|
|
1160
|
+
Do NOT include sections that duplicate information available in package.json, tsconfig.json, or other config files the agent can read directly.
|
|
1161
|
+
|
|
939
1162
|
### Output Structure
|
|
940
1163
|
|
|
941
|
-
The CLAUDE.md MUST follow this structure:
|
|
1164
|
+
The CLAUDE.md MUST follow this compact structure:
|
|
942
1165
|
|
|
943
1166
|
\`\`\`markdown
|
|
944
1167
|
# {Project Name}
|
|
@@ -952,17 +1175,9 @@ Written for an AI assistant that needs to understand PURPOSE to make good decisi
|
|
|
952
1175
|
|
|
953
1176
|
## Architecture
|
|
954
1177
|
|
|
955
|
-
{
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
\\\`\\\`\\\`
|
|
960
|
-
{Actual directory tree, depth 3, with annotations}
|
|
961
|
-
\\\`\\\`\\\`
|
|
962
|
-
|
|
963
|
-
### Data Flow
|
|
964
|
-
|
|
965
|
-
{How a typical request flows through the system}
|
|
1178
|
+
{1-2 sentences describing the actual architecture pattern found, then the Key Files table.
|
|
1179
|
+
Do NOT include a Directory Structure ASCII tree or Data Flow subsection \u2014 the agent can
|
|
1180
|
+
read the filesystem directly.}
|
|
966
1181
|
|
|
967
1182
|
### Key Files
|
|
968
1183
|
|
|
@@ -970,42 +1185,18 @@ Written for an AI assistant that needs to understand PURPOSE to make good decisi
|
|
|
970
1185
|
|------|---------|
|
|
971
1186
|
| \`path/to/file\` | What it does |
|
|
972
1187
|
|
|
973
|
-
## Tech Stack
|
|
974
|
-
|
|
975
|
-
| Category | Technology | Notes |
|
|
976
|
-
|----------|-----------|-------|
|
|
977
|
-
| Language | X | Config details |
|
|
978
|
-
| Framework | Y | How it's used |
|
|
979
|
-
|
|
980
|
-
## Development Setup
|
|
981
|
-
|
|
982
|
-
### Prerequisites
|
|
983
|
-
|
|
984
|
-
{Exact versions and tools needed}
|
|
985
|
-
|
|
986
|
-
### Getting Started
|
|
987
|
-
|
|
988
|
-
\\\`\\\`\\\`bash
|
|
989
|
-
{Actual commands to get running}
|
|
990
|
-
\\\`\\\`\\\`
|
|
991
|
-
|
|
992
|
-
### Environment Variables
|
|
993
|
-
|
|
994
|
-
| Variable | Description | Example |
|
|
995
|
-
|----------|-------------|---------|
|
|
996
|
-
| \`VAR_NAME\` | What it's for | \`example_value\` |
|
|
997
|
-
|
|
998
1188
|
## Common Commands
|
|
999
1189
|
|
|
1000
1190
|
\\\`\\\`\\\`bash
|
|
1001
|
-
{
|
|
1191
|
+
{5 critical commands max, from package.json scripts or equivalent.
|
|
1192
|
+
Only the commands developers use daily \u2014 not every script.}
|
|
1002
1193
|
\\\`\\\`\\\`
|
|
1003
1194
|
|
|
1004
1195
|
## Code Conventions
|
|
1005
1196
|
|
|
1006
1197
|
### Naming
|
|
1007
1198
|
|
|
1008
|
-
{ACTUAL naming patterns found}
|
|
1199
|
+
{ACTUAL naming patterns found \u2014 be brief}
|
|
1009
1200
|
|
|
1010
1201
|
### Patterns to Follow
|
|
1011
1202
|
|
|
@@ -1018,45 +1209,16 @@ Written for an AI assistant that needs to understand PURPOSE to make good decisi
|
|
|
1018
1209
|
> **This Code Conventions section is the single source of truth.**
|
|
1019
1210
|
> Rules and skills cross-reference this section \u2014 they do not repeat it.
|
|
1020
1211
|
|
|
1021
|
-
## Skills
|
|
1022
|
-
|
|
1023
|
-
{List each generated skill with a one-line description}
|
|
1024
|
-
|
|
1025
|
-
| Skill | Purpose |
|
|
1026
|
-
|-------|---------|
|
|
1027
|
-
| \`pattern-discovery\` | Discover and document codebase patterns |
|
|
1028
|
-
| ... | ... |
|
|
1029
|
-
|
|
1030
|
-
## Agents
|
|
1031
|
-
|
|
1032
|
-
{List each generated agent with a one-line description}
|
|
1033
|
-
|
|
1034
|
-
| Agent | Purpose |
|
|
1035
|
-
|-------|---------|
|
|
1036
|
-
| \`code-reviewer\` | Reviews code for quality and security |
|
|
1037
|
-
| \`test-writer\` | Generates tests for code |
|
|
1038
|
-
|
|
1039
1212
|
## Testing
|
|
1040
1213
|
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
\\\`\\\`\\\`bash
|
|
1044
|
-
{actual test commands}
|
|
1045
|
-
\\\`\\\`\\\`
|
|
1046
|
-
|
|
1047
|
-
### Writing Tests
|
|
1048
|
-
|
|
1049
|
-
{Testing patterns, utilities, fixtures available}
|
|
1214
|
+
{2-3 lines: test command, test file location, key testing pattern.
|
|
1215
|
+
NOT a full guide \u2014 the test-writer agent handles detailed test methodology.}
|
|
1050
1216
|
|
|
1051
1217
|
## Domain Knowledge
|
|
1052
1218
|
|
|
1053
1219
|
### Core Entities
|
|
1054
1220
|
|
|
1055
|
-
{
|
|
1056
|
-
|
|
1057
|
-
### Key Workflows
|
|
1058
|
-
|
|
1059
|
-
{3-5 most important workflows}
|
|
1221
|
+
{Brief list of main domain objects and relationships}
|
|
1060
1222
|
|
|
1061
1223
|
## Gotchas & Important Notes
|
|
1062
1224
|
|
|
@@ -1070,12 +1232,22 @@ Written for an AI assistant that needs to understand PURPOSE to make good decisi
|
|
|
1070
1232
|
4. {project-specific rules discovered during analysis}
|
|
1071
1233
|
\`\`\`
|
|
1072
1234
|
|
|
1235
|
+
**Sections NOT to include** (the agent can read these from config files directly):
|
|
1236
|
+
- Directory Structure ASCII tree (agent uses Glob/Read)
|
|
1237
|
+
- Tech Stack table (available in package.json, tsconfig.json, etc.)
|
|
1238
|
+
- Development Setup / Getting Started / Prerequisites
|
|
1239
|
+
- Environment Variables table (available in .env.example)
|
|
1240
|
+
- Skills index table
|
|
1241
|
+
- Agents index table
|
|
1242
|
+
- Key Workflows (duplicates Data Flow / Architecture)
|
|
1243
|
+
|
|
1073
1244
|
---
|
|
1074
1245
|
|
|
1075
1246
|
## Phase 3: Quality Checklist
|
|
1076
1247
|
|
|
1077
1248
|
Before writing the CLAUDE.md, verify:
|
|
1078
1249
|
|
|
1250
|
+
- [ ] The file does NOT exceed 120 lines
|
|
1079
1251
|
- [ ] Every section contains PROJECT-SPECIFIC information (not generic boilerplate)
|
|
1080
1252
|
- [ ] File paths referenced actually exist in the project
|
|
1081
1253
|
- [ ] File references use \`path/to/file.ts (functionName)\` format, not line numbers
|
|
@@ -1084,12 +1256,13 @@ Before writing the CLAUDE.md, verify:
|
|
|
1084
1256
|
- [ ] The "Gotchas" section contains genuinely useful, non-obvious information
|
|
1085
1257
|
- [ ] An AI reading this CLAUDE.md could add a new feature following existing patterns
|
|
1086
1258
|
- [ ] Sections without real content have been omitted entirely
|
|
1259
|
+
- [ ] No section duplicates information available in config files the agent can read
|
|
1087
1260
|
|
|
1088
1261
|
### Cross-Artifact Deduplication Check
|
|
1089
1262
|
|
|
1090
1263
|
Before writing ANY artifact (rule, skill, agent, command), verify:
|
|
1091
1264
|
- [ ] No conventions from CLAUDE.md are restated (naming, commit format, import order, style)
|
|
1092
|
-
- [ ] No
|
|
1265
|
+
- [ ] No item from the Forbidden Duplication List appears outside CLAUDE.md
|
|
1093
1266
|
- [ ] No content from one artifact is duplicated in another
|
|
1094
1267
|
- [ ] Cross-references are used instead of copies (e.g., "Follow conventions in CLAUDE.md")
|
|
1095
1268
|
- [ ] Every rule file has a \`paths:\` filter \u2014 no unfiltered rules
|
|
@@ -1117,57 +1290,38 @@ var SKILLS_PROMPT = `---
|
|
|
1117
1290
|
Write each skill file to \`.claude/skills/\` using the Write tool. Every skill must have
|
|
1118
1291
|
YAML frontmatter with \`name\`, \`description\`, and optionally \`globs\` for file matching.
|
|
1119
1292
|
|
|
1120
|
-
**Tailor ALL skills to this specific project** \u2014 use the actual
|
|
1121
|
-
|
|
1293
|
+
**Tailor ALL skills to this specific project** \u2014 use the actual file patterns and
|
|
1294
|
+
conventions discovered during Phase 1.
|
|
1122
1295
|
|
|
1123
1296
|
### Skill Content Rules
|
|
1124
1297
|
|
|
1125
1298
|
1. **Cross-reference, don't copy** \u2014 write "Follow conventions in CLAUDE.md" instead of restating naming, style, or commit conventions. Skills focus on methodology (HOW to do something), not conventions (WHAT the conventions are).
|
|
1126
1299
|
2. **Use stable references** \u2014 reference code as \`path/to/file.ts (functionName)\`, not line numbers which become stale.
|
|
1127
1300
|
3. **No convention duplication** \u2014 if CLAUDE.md already documents commit format, import order, or naming rules, the skill must not repeat them.
|
|
1301
|
+
4. **No command duplication** \u2014 for test, lint, and build commands, write "See Common Commands in CLAUDE.md" instead of repeating the literal command.
|
|
1128
1302
|
|
|
1129
|
-
### 4.1 Core Skills (ALWAYS generate all
|
|
1130
|
-
|
|
1131
|
-
**\`.claude/skills/pattern-discovery.md\`**
|
|
1132
|
-
- Name: pattern-discovery
|
|
1133
|
-
- Description: Analyze codebase to discover and document patterns
|
|
1134
|
-
- Content: How to search for patterns in THIS project's structure. Include the actual source directories, key file patterns, and import conventions found.
|
|
1135
|
-
|
|
1136
|
-
**\`.claude/skills/systematic-debugging.md\`**
|
|
1137
|
-
- Name: systematic-debugging
|
|
1138
|
-
- Description: 4-phase debugging methodology \u2014 Reproduce, Locate, Diagnose, Fix
|
|
1139
|
-
- Content: Tailor reproduction steps to the project's actual test runner and dev server commands. Include how to use the project's logging/debugging setup.
|
|
1140
|
-
|
|
1141
|
-
**\`.claude/skills/testing-methodology.md\`**
|
|
1142
|
-
- Name: testing-methodology
|
|
1143
|
-
- Description: AAA testing pattern with project-specific framework syntax
|
|
1144
|
-
- Content: Use the project's actual testing framework syntax. Include real examples of test patterns found in the codebase (describe/it blocks, pytest fixtures, etc.). Reference the actual test command. Include mocking/stubbing patterns specific to the stack.
|
|
1303
|
+
### 4.1 Core Skills (ALWAYS generate all 4)
|
|
1145
1304
|
|
|
1146
1305
|
**\`.claude/skills/iterative-development.md\`**
|
|
1147
1306
|
- Name: iterative-development
|
|
1148
|
-
- Description: TDD workflow with
|
|
1149
|
-
- Content: The TDD loop
|
|
1150
|
-
|
|
1151
|
-
**\`.claude/skills/commit-hygiene.md\`**
|
|
1152
|
-
- Name: commit-hygiene
|
|
1153
|
-
- Description: Atomic commits, conventional format, size thresholds
|
|
1154
|
-
- Content: Size thresholds (\xB1300 lines per commit), when-to-commit triggers. For commit format, write "Follow the commit conventions in CLAUDE.md" \u2014 do NOT restate the format here. If the project uses commitlint or similar, reference its config.
|
|
1307
|
+
- Description: TDD workflow with debugging methodology and verification chain
|
|
1308
|
+
- Content: The TDD loop referencing "See Common Commands in CLAUDE.md" for actual commands. Include the project's verification steps (typecheck, build, etc.). Add a Debugging section with: 4-phase methodology (Reproduce, Locate, Diagnose, Fix), project-specific file-to-module mapping for tracing bugs, how to use the project's logging/debugging setup. Add commit guidance: size thresholds (\xB1300 lines per commit), when-to-commit triggers, "Follow commit conventions in CLAUDE.md" for format.
|
|
1155
1309
|
|
|
1156
1310
|
**\`.claude/skills/code-deduplication.md\`**
|
|
1157
1311
|
- Name: code-deduplication
|
|
1158
|
-
- Description: Check-before-write principle and
|
|
1159
|
-
- Content: Search existing code before writing new code. Include project-specific glob patterns for source files. Reference the actual directory structure for where to look.
|
|
1160
|
-
|
|
1161
|
-
**\`.claude/skills/simplicity-rules.md\`**
|
|
1162
|
-
- Name: simplicity-rules
|
|
1163
|
-
- Description: Function and file size limits, decomposition patterns
|
|
1164
|
-
- Content: Function length limits (40 lines), file limits (300 lines), cyclomatic complexity. Decomposition patterns appropriate for the project's architecture style.
|
|
1312
|
+
- Description: Check-before-write principle, search checklist, and size limits
|
|
1313
|
+
- Content: Search existing code before writing new code. Include project-specific glob patterns for source files. Reference the actual directory structure for where to look. Include a "Where to Look" checklist for discovering patterns in THIS project's structure (source directories, key file patterns). Add size limits: function length (40 lines), file length (300 lines), decomposition patterns appropriate for the project's architecture style.
|
|
1165
1314
|
|
|
1166
1315
|
**\`.claude/skills/security.md\`**
|
|
1167
1316
|
- Name: security
|
|
1168
1317
|
- Description: Security patterns and secrets management for this stack
|
|
1169
1318
|
- Content: .gitignore entries appropriate for the detected stack. Environment variable handling patterns. OWASP checklist items relevant to the detected framework. Include actual secrets patterns to watch for (API keys, database URLs, etc.).
|
|
1170
1319
|
|
|
1320
|
+
**\`.claude/skills/testing-methodology.md\`**
|
|
1321
|
+
- Name: testing-methodology
|
|
1322
|
+
- Description: Test design methodology \u2014 what to test, edge cases, test organization
|
|
1323
|
+
- Content: Focus on test DESIGN: what to test, how to identify edge cases, test organization strategy, when to use unit vs integration tests. Include project-specific test file naming and location conventions. Reference "See Common Commands in CLAUDE.md" for the test command. Do NOT include testing framework syntax examples (describe/it/expect, pytest fixtures, etc.) \u2014 those belong in the test-writer agent, not here. The \`testing-methodology\` skill focuses on test DESIGN (what to test, edge cases, test organization). The \`test-writer\` agent focuses on test EXECUTION (writing code, running tests). They must not overlap.
|
|
1324
|
+
|
|
1171
1325
|
### 4.2 Framework-Specific Skills (ONLY if detected)
|
|
1172
1326
|
|
|
1173
1327
|
Generate the matching skill ONLY if the framework was detected in the tech stack:
|
|
@@ -1255,7 +1409,8 @@ Body content \u2014 instructions for the test writer agent:
|
|
|
1255
1409
|
- Follow existing test file naming conventions
|
|
1256
1410
|
- Include edge cases: empty inputs, nulls, errors, boundaries
|
|
1257
1411
|
- Mock external dependencies following project patterns
|
|
1258
|
-
- Run tests after writing to verify they pass
|
|
1412
|
+
- Run tests after writing to verify they pass
|
|
1413
|
+
- Do NOT duplicate the testing-methodology skill content. The skill covers test design (what to test, edge cases, organization); this agent covers writing and running tests (framework syntax, assertions, execution).`;
|
|
1259
1414
|
var RULES_PROMPT = `---
|
|
1260
1415
|
|
|
1261
1416
|
## Phase 6: Generate Rules
|
|
@@ -1320,39 +1475,11 @@ var COMMANDS_PROMPT = `---
|
|
|
1320
1475
|
|
|
1321
1476
|
## Phase 7: Generate Commands
|
|
1322
1477
|
|
|
1323
|
-
Write
|
|
1478
|
+
Write 2 command files to \`.claude/commands/\`. Each needs YAML frontmatter with
|
|
1324
1479
|
\`allowed-tools\`, \`description\`, and optionally \`argument-hint\`.
|
|
1325
1480
|
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
---
|
|
1329
|
-
allowed-tools: ["Read", "Write", "Edit", "Glob"]
|
|
1330
|
-
description: "Start or switch to a new task"
|
|
1331
|
-
argument-hint: "<task description>"
|
|
1332
|
-
---
|
|
1333
|
-
\`\`\`
|
|
1334
|
-
Body: Instructions to read current \`.claude/state/task.md\`, update status to "In Progress",
|
|
1335
|
-
record the task description and timestamp. If starting a new task, archive the previous one.
|
|
1336
|
-
|
|
1337
|
-
### \`.claude/commands/status.md\`
|
|
1338
|
-
\`\`\`yaml
|
|
1339
|
-
---
|
|
1340
|
-
allowed-tools: ["Read", "Glob", "Grep", "Bash(git status)", "Bash(git diff --stat)"]
|
|
1341
|
-
description: "Show current task and session state"
|
|
1342
|
-
---
|
|
1343
|
-
\`\`\`
|
|
1344
|
-
Body: Read \`.claude/state/task.md\`, show git status, list recently modified files,
|
|
1345
|
-
summarize current state in a concise format.
|
|
1346
|
-
|
|
1347
|
-
### \`.claude/commands/done.md\`
|
|
1348
|
-
\`\`\`yaml
|
|
1349
|
-
---
|
|
1350
|
-
allowed-tools: ["Read", "Write", "Edit", "Glob", "Grep", "Bash(git:*)", "Bash({test_command})", "Bash({lint_command})"]
|
|
1351
|
-
description: "Mark current task complete"
|
|
1352
|
-
---
|
|
1353
|
-
\`\`\`
|
|
1354
|
-
Body: Run tests and lint checks. If they pass, update \`.claude/state/task.md\`
|
|
1355
|
-
status to "Done". Show a summary of what was accomplished. Suggest next steps.
|
|
1481
|
+
Do NOT generate task management commands (\`task.md\`, \`status.md\`, \`done.md\`) \u2014
|
|
1482
|
+
Claude Code has built-in TaskCreate/TaskUpdate/TaskList tools for task management.
|
|
1356
1483
|
|
|
1357
1484
|
### \`.claude/commands/analyze.md\`
|
|
1358
1485
|
\`\`\`yaml
|
|
@@ -1378,10 +1505,154 @@ Body: This command delegates to the code-reviewer agent for thorough review.
|
|
|
1378
1505
|
3. If the agent is unavailable, perform a lightweight review: run the linter and check for obvious issues
|
|
1379
1506
|
Do NOT duplicate the code-reviewer agent's checklist here \u2014 the agent has the full review criteria.`;
|
|
1380
1507
|
|
|
1508
|
+
// src/validator.ts
|
|
1509
|
+
import fs4 from "fs";
|
|
1510
|
+
import path4 from "path";
|
|
1511
|
+
function extractCommands(claudeMd) {
|
|
1512
|
+
const commands = [];
|
|
1513
|
+
const match = claudeMd.match(/## Common Commands[\s\S]*?```(?:bash)?\n([\s\S]*?)```/);
|
|
1514
|
+
if (!match) return commands;
|
|
1515
|
+
for (const line of match[1].split("\n")) {
|
|
1516
|
+
const trimmed = line.trim();
|
|
1517
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
1518
|
+
const cmd = trimmed.split(/\s+#/)[0].trim();
|
|
1519
|
+
if (cmd.length > 3) commands.push(cmd);
|
|
1520
|
+
}
|
|
1521
|
+
return commands;
|
|
1522
|
+
}
|
|
1523
|
+
function extractConventionFingerprints(claudeMd) {
|
|
1524
|
+
const fingerprints = [];
|
|
1525
|
+
const startIdx = claudeMd.indexOf("## Code Conventions");
|
|
1526
|
+
if (startIdx === -1) return fingerprints;
|
|
1527
|
+
const rest = claudeMd.slice(startIdx + "## Code Conventions".length);
|
|
1528
|
+
const nextHeading = rest.match(/\n## [A-Z]/);
|
|
1529
|
+
const section = nextHeading ? claudeMd.slice(startIdx, startIdx + "## Code Conventions".length + nextHeading.index) : claudeMd.slice(startIdx);
|
|
1530
|
+
for (const kw of ["camelCase", "PascalCase", "kebab-case", "snake_case"]) {
|
|
1531
|
+
if (section.includes(kw)) fingerprints.push(kw);
|
|
1532
|
+
}
|
|
1533
|
+
if (/\bnamed exports?\b/i.test(section)) fingerprints.push("named export");
|
|
1534
|
+
if (/\bdefault exports?\b/i.test(section)) fingerprints.push("default export");
|
|
1535
|
+
if (section.includes("import type")) fingerprints.push("import type");
|
|
1536
|
+
for (const kw of [".skip()", ".only()", "console.log"]) {
|
|
1537
|
+
if (section.includes(kw)) fingerprints.push(kw);
|
|
1538
|
+
}
|
|
1539
|
+
return fingerprints;
|
|
1540
|
+
}
|
|
1541
|
+
var RULE_WORDS = /\b(verify|check|ensure|always|never|must|should|avoid)\b/i;
|
|
1542
|
+
function isConventionDuplication(line, fingerprints) {
|
|
1543
|
+
const trimmed = line.trim();
|
|
1544
|
+
if (!trimmed || trimmed.startsWith("#") || trimmed.includes("CLAUDE.md")) return false;
|
|
1545
|
+
if (!/^[-*]\s/.test(trimmed)) return false;
|
|
1546
|
+
const matchCount = fingerprints.filter((fp) => trimmed.includes(fp)).length;
|
|
1547
|
+
if (matchCount >= 2) return true;
|
|
1548
|
+
if (matchCount === 1 && RULE_WORDS.test(trimmed)) return true;
|
|
1549
|
+
return false;
|
|
1550
|
+
}
|
|
1551
|
+
function findLiteralCommand(line, commands) {
|
|
1552
|
+
const trimmed = line.trim();
|
|
1553
|
+
if (!trimmed || trimmed.startsWith("#") || trimmed.includes("CLAUDE.md")) return null;
|
|
1554
|
+
for (const cmd of commands) {
|
|
1555
|
+
if (trimmed.includes(cmd)) return cmd;
|
|
1556
|
+
}
|
|
1557
|
+
return null;
|
|
1558
|
+
}
|
|
1559
|
+
function separateFrontmatter(content) {
|
|
1560
|
+
const match = content.match(/^---\n[\s\S]*?\n---(?:\n|$)/);
|
|
1561
|
+
if (!match) {
|
|
1562
|
+
return { frontmatter: "", body: content };
|
|
1563
|
+
}
|
|
1564
|
+
return {
|
|
1565
|
+
frontmatter: match[0],
|
|
1566
|
+
body: content.slice(match[0].length)
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
function processFile(filePath, commands, fingerprints) {
|
|
1570
|
+
const content = fs4.readFileSync(filePath, "utf-8");
|
|
1571
|
+
const { frontmatter, body } = separateFrontmatter(content);
|
|
1572
|
+
const lines = body.split("\n");
|
|
1573
|
+
const changes = [];
|
|
1574
|
+
const newLines = [];
|
|
1575
|
+
let inCodeBlock = false;
|
|
1576
|
+
for (const line of lines) {
|
|
1577
|
+
if (line.trim().startsWith("```")) {
|
|
1578
|
+
inCodeBlock = !inCodeBlock;
|
|
1579
|
+
newLines.push(line);
|
|
1580
|
+
continue;
|
|
1581
|
+
}
|
|
1582
|
+
if (inCodeBlock) {
|
|
1583
|
+
newLines.push(line);
|
|
1584
|
+
continue;
|
|
1585
|
+
}
|
|
1586
|
+
if (isConventionDuplication(line, fingerprints)) {
|
|
1587
|
+
changes.push({ file: filePath, original: line.trim(), replacement: null });
|
|
1588
|
+
continue;
|
|
1589
|
+
}
|
|
1590
|
+
const cmd = findLiteralCommand(line, commands);
|
|
1591
|
+
if (cmd) {
|
|
1592
|
+
const newLine = line.replace(cmd, "see Common Commands in CLAUDE.md");
|
|
1593
|
+
changes.push({ file: filePath, original: line.trim(), replacement: newLine.trim() });
|
|
1594
|
+
newLines.push(newLine);
|
|
1595
|
+
continue;
|
|
1596
|
+
}
|
|
1597
|
+
newLines.push(line);
|
|
1598
|
+
}
|
|
1599
|
+
if (changes.length > 0) {
|
|
1600
|
+
fs4.writeFileSync(filePath, frontmatter + newLines.join("\n"));
|
|
1601
|
+
}
|
|
1602
|
+
return changes;
|
|
1603
|
+
}
|
|
1604
|
+
function walkMdFiles(dir) {
|
|
1605
|
+
const files = [];
|
|
1606
|
+
if (!fs4.existsSync(dir)) return files;
|
|
1607
|
+
const entries = fs4.readdirSync(dir, { withFileTypes: true });
|
|
1608
|
+
for (const entry of entries) {
|
|
1609
|
+
const fullPath = path4.join(dir, entry.name);
|
|
1610
|
+
if (entry.isDirectory()) {
|
|
1611
|
+
files.push(...walkMdFiles(fullPath));
|
|
1612
|
+
} else if (entry.name.endsWith(".md")) {
|
|
1613
|
+
files.push(fullPath);
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
return files;
|
|
1617
|
+
}
|
|
1618
|
+
function validateArtifacts(rootDir) {
|
|
1619
|
+
const result = {
|
|
1620
|
+
filesChecked: 0,
|
|
1621
|
+
filesModified: 0,
|
|
1622
|
+
duplicationsRemoved: 0,
|
|
1623
|
+
changes: []
|
|
1624
|
+
};
|
|
1625
|
+
const claudeMdPath = path4.join(rootDir, ".claude", "CLAUDE.md");
|
|
1626
|
+
if (!fs4.existsSync(claudeMdPath)) return result;
|
|
1627
|
+
try {
|
|
1628
|
+
const claudeMd = fs4.readFileSync(claudeMdPath, "utf-8");
|
|
1629
|
+
const commands = extractCommands(claudeMd);
|
|
1630
|
+
const fingerprints = extractConventionFingerprints(claudeMd);
|
|
1631
|
+
if (commands.length === 0 && fingerprints.length === 0) return result;
|
|
1632
|
+
const claudeDir = path4.join(rootDir, ".claude");
|
|
1633
|
+
const files = walkMdFiles(claudeDir).filter((f) => !f.endsWith("CLAUDE.md"));
|
|
1634
|
+
for (const filePath of files) {
|
|
1635
|
+
result.filesChecked++;
|
|
1636
|
+
const changes = processFile(filePath, commands, fingerprints);
|
|
1637
|
+
if (changes.length > 0) {
|
|
1638
|
+
result.filesModified++;
|
|
1639
|
+
result.duplicationsRemoved += changes.length;
|
|
1640
|
+
for (const change of changes) {
|
|
1641
|
+
change.file = path4.relative(rootDir, filePath);
|
|
1642
|
+
}
|
|
1643
|
+
result.changes.push(...changes);
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
} catch {
|
|
1647
|
+
return result;
|
|
1648
|
+
}
|
|
1649
|
+
return result;
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1381
1652
|
// src/cli.ts
|
|
1382
|
-
var __dirname2 =
|
|
1653
|
+
var __dirname2 = path5.dirname(fileURLToPath(import.meta.url));
|
|
1383
1654
|
var VERSION = JSON.parse(
|
|
1384
|
-
|
|
1655
|
+
fs5.readFileSync(path5.join(__dirname2, "..", "package.json"), "utf-8")
|
|
1385
1656
|
).version;
|
|
1386
1657
|
function parseArgs(args) {
|
|
1387
1658
|
return {
|
|
@@ -1419,7 +1690,7 @@ ${pc.bold("WHAT IT DOES")}
|
|
|
1419
1690
|
- Skills for your frameworks and workflows
|
|
1420
1691
|
- Agents for code review and testing
|
|
1421
1692
|
- Rules matching your code style
|
|
1422
|
-
- Commands for
|
|
1693
|
+
- Commands for analysis and code review
|
|
1423
1694
|
|
|
1424
1695
|
${pc.bold("REQUIREMENTS")}
|
|
1425
1696
|
Claude CLI must be installed: https://claude.ai/download
|
|
@@ -1756,61 +2027,6 @@ function mapFormatter(linter) {
|
|
|
1756
2027
|
};
|
|
1757
2028
|
return mapping[linter] ?? null;
|
|
1758
2029
|
}
|
|
1759
|
-
function createTaskFile(projectInfo, preferences) {
|
|
1760
|
-
const taskPath = path3.join(projectInfo.rootDir, ".claude", "state", "task.md");
|
|
1761
|
-
fs3.mkdirSync(path3.dirname(taskPath), { recursive: true });
|
|
1762
|
-
if (fs3.existsSync(taskPath)) {
|
|
1763
|
-
return;
|
|
1764
|
-
}
|
|
1765
|
-
let content;
|
|
1766
|
-
if (projectInfo.isExisting) {
|
|
1767
|
-
content = `# Current Task
|
|
1768
|
-
|
|
1769
|
-
## Status: Ready
|
|
1770
|
-
|
|
1771
|
-
No active task. Start one with \`/task <description>\`.
|
|
1772
|
-
|
|
1773
|
-
## Project Summary
|
|
1774
|
-
|
|
1775
|
-
${projectInfo.name}${projectInfo.description ? ` - ${projectInfo.description}` : ""}
|
|
1776
|
-
|
|
1777
|
-
**Tech Stack:** ${summarizeTechStack(projectInfo.techStack)}
|
|
1778
|
-
|
|
1779
|
-
## Quick Commands
|
|
1780
|
-
|
|
1781
|
-
- \`/task\` - Start working on something
|
|
1782
|
-
- \`/status\` - See current state
|
|
1783
|
-
- \`/analyze\` - Deep dive into code
|
|
1784
|
-
- \`/done\` - Mark task complete
|
|
1785
|
-
`;
|
|
1786
|
-
} else {
|
|
1787
|
-
const description = preferences?.description || "Explore and set up project";
|
|
1788
|
-
content = `# Current Task
|
|
1789
|
-
|
|
1790
|
-
## Status: In Progress
|
|
1791
|
-
|
|
1792
|
-
**Task:** ${description}
|
|
1793
|
-
|
|
1794
|
-
## Context
|
|
1795
|
-
|
|
1796
|
-
New project - setting up from scratch.
|
|
1797
|
-
|
|
1798
|
-
${preferences?.framework ? `**Framework:** ${formatFramework(preferences.framework)}` : ""}
|
|
1799
|
-
${preferences?.primaryLanguage ? `**Language:** ${formatLanguage(preferences.primaryLanguage)}` : ""}
|
|
1800
|
-
|
|
1801
|
-
## Next Steps
|
|
1802
|
-
|
|
1803
|
-
1. Define project structure
|
|
1804
|
-
2. Set up development environment
|
|
1805
|
-
3. Start implementation
|
|
1806
|
-
|
|
1807
|
-
## Decisions
|
|
1808
|
-
|
|
1809
|
-
(None yet - starting fresh)
|
|
1810
|
-
`;
|
|
1811
|
-
}
|
|
1812
|
-
fs3.writeFileSync(taskPath, content);
|
|
1813
|
-
}
|
|
1814
2030
|
function formatLanguage(lang) {
|
|
1815
2031
|
const names = {
|
|
1816
2032
|
typescript: "TypeScript",
|
|
@@ -1950,17 +2166,17 @@ function runClaudeAnalysis(projectDir, projectInfo) {
|
|
|
1950
2166
|
});
|
|
1951
2167
|
}
|
|
1952
2168
|
function getGeneratedFiles(projectDir) {
|
|
1953
|
-
const claudeDir =
|
|
2169
|
+
const claudeDir = path5.join(projectDir, ".claude");
|
|
1954
2170
|
const files = [];
|
|
1955
2171
|
function walk(dir) {
|
|
1956
|
-
if (!
|
|
1957
|
-
const entries =
|
|
2172
|
+
if (!fs5.existsSync(dir)) return;
|
|
2173
|
+
const entries = fs5.readdirSync(dir, { withFileTypes: true });
|
|
1958
2174
|
for (const entry of entries) {
|
|
1959
|
-
const fullPath =
|
|
2175
|
+
const fullPath = path5.join(dir, entry.name);
|
|
1960
2176
|
if (entry.isDirectory()) {
|
|
1961
2177
|
walk(fullPath);
|
|
1962
2178
|
} else {
|
|
1963
|
-
files.push(
|
|
2179
|
+
files.push(path5.relative(projectDir, fullPath));
|
|
1964
2180
|
}
|
|
1965
2181
|
}
|
|
1966
2182
|
}
|
|
@@ -2017,7 +2233,7 @@ async function main() {
|
|
|
2017
2233
|
const { proceed } = await prompts({
|
|
2018
2234
|
type: "confirm",
|
|
2019
2235
|
name: "proceed",
|
|
2020
|
-
message: "Update existing configuration?
|
|
2236
|
+
message: "Update existing configuration?",
|
|
2021
2237
|
initial: true
|
|
2022
2238
|
});
|
|
2023
2239
|
if (!proceed) {
|
|
@@ -2039,12 +2255,19 @@ async function main() {
|
|
|
2039
2255
|
console.log(pc.green("Created:"));
|
|
2040
2256
|
console.log(pc.green(" + .claude/settings.json"));
|
|
2041
2257
|
console.log();
|
|
2042
|
-
createTaskFile(projectInfo, preferences);
|
|
2043
2258
|
const success = await runClaudeAnalysis(projectDir, projectInfo);
|
|
2044
2259
|
if (!success) {
|
|
2045
2260
|
console.error(pc.red("Claude analysis failed. Please try again."));
|
|
2046
2261
|
process.exit(1);
|
|
2047
2262
|
}
|
|
2263
|
+
const validation = validateArtifacts(projectDir);
|
|
2264
|
+
if (validation.duplicationsRemoved > 0) {
|
|
2265
|
+
console.log(
|
|
2266
|
+
pc.gray(
|
|
2267
|
+
` Deduplication: removed ${validation.duplicationsRemoved} redundancies from ${validation.filesModified} files`
|
|
2268
|
+
)
|
|
2269
|
+
);
|
|
2270
|
+
}
|
|
2048
2271
|
const generatedFiles = getGeneratedFiles(projectDir);
|
|
2049
2272
|
console.log();
|
|
2050
2273
|
console.log(pc.green(`Done! (${generatedFiles.length} files)`));
|
|
@@ -2059,12 +2282,12 @@ async function main() {
|
|
|
2059
2282
|
}
|
|
2060
2283
|
if (skills.length > 0) {
|
|
2061
2284
|
console.log(
|
|
2062
|
-
` ${skills.length} skills (${skills.map((s) =>
|
|
2285
|
+
` ${skills.length} skills (${skills.map((s) => path5.basename(s, ".md")).join(", ")})`
|
|
2063
2286
|
);
|
|
2064
2287
|
}
|
|
2065
2288
|
if (agents.length > 0) {
|
|
2066
2289
|
console.log(
|
|
2067
|
-
` ${agents.length} agents (${agents.map((a) =>
|
|
2290
|
+
` ${agents.length} agents (${agents.map((a) => path5.basename(a, ".md")).join(", ")})`
|
|
2068
2291
|
);
|
|
2069
2292
|
}
|
|
2070
2293
|
if (rules.length > 0) {
|
|
@@ -2074,6 +2297,21 @@ async function main() {
|
|
|
2074
2297
|
console.log(` ${commands.length} commands`);
|
|
2075
2298
|
}
|
|
2076
2299
|
console.log();
|
|
2300
|
+
if (args.interactive) {
|
|
2301
|
+
console.log();
|
|
2302
|
+
const { installSafetyHook } = await prompts({
|
|
2303
|
+
type: "confirm",
|
|
2304
|
+
name: "installSafetyHook",
|
|
2305
|
+
message: "Add a safety hook to block dangerous commands? (git push, rm -rf, etc.)",
|
|
2306
|
+
initial: true
|
|
2307
|
+
});
|
|
2308
|
+
if (installSafetyHook) {
|
|
2309
|
+
installHook(projectDir);
|
|
2310
|
+
console.log(pc.green(" + .claude/hooks/block-dangerous-commands.js"));
|
|
2311
|
+
console.log(pc.gray(" Blocks destructive Bash commands before execution"));
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
console.log();
|
|
2077
2315
|
console.log(`${pc.cyan("Next step:")} Run ${pc.bold("claude")} to start working!`);
|
|
2078
2316
|
console.log();
|
|
2079
2317
|
console.log(
|
|
@@ -2083,7 +2321,7 @@ async function main() {
|
|
|
2083
2321
|
);
|
|
2084
2322
|
}
|
|
2085
2323
|
try {
|
|
2086
|
-
const isMain = process.argv[1] &&
|
|
2324
|
+
const isMain = process.argv[1] && fs5.realpathSync(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
2087
2325
|
if (isMain) {
|
|
2088
2326
|
main().catch((err) => {
|
|
2089
2327
|
console.error(pc.red("Error:"), err.message);
|
|
@@ -2097,7 +2335,6 @@ try {
|
|
|
2097
2335
|
}
|
|
2098
2336
|
export {
|
|
2099
2337
|
checkClaudeCli,
|
|
2100
|
-
createTaskFile,
|
|
2101
2338
|
formatFramework,
|
|
2102
2339
|
formatLanguage,
|
|
2103
2340
|
getVersion,
|