@valentia-ai-skills/framework 1.0.7 → 1.0.9
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/bin/cli.js +169 -6
- package/hooks/do-analysis.js +125 -0
- package/hooks/install-hook.js +75 -0
- package/hooks/post-commit-analyze.js +29 -0
- package/package.json +3 -2
package/bin/cli.js
CHANGED
|
@@ -26,6 +26,8 @@ const CONFIG_PATH = path.join(PROJECT_ROOT, ".ai-skills.json");
|
|
|
26
26
|
// UPDATE THIS with your actual Supabase project URL
|
|
27
27
|
const SUPABASE_FUNCTION_URL =
|
|
28
28
|
process.env.AI_SKILLS_API_URL || "https://znshdhjquohrzvbnloki.supabase.co/functions/v1/lookup-team";
|
|
29
|
+
const ANALYZE_FUNCTION_URL =
|
|
30
|
+
process.env.AI_SKILLS_ANALYZE_URL || "https://znshdhjquohrzvbnloki.supabase.co/functions/v1/analyze-commit";
|
|
29
31
|
|
|
30
32
|
const TOOL_CONFIGS = {
|
|
31
33
|
"claude-code": {
|
|
@@ -248,9 +250,15 @@ async function requestOtpForEmail(emailInput) {
|
|
|
248
250
|
|
|
249
251
|
// OTP sent successfully
|
|
250
252
|
console.log(c("green", `\n✓ Found: ${response.user_name}`));
|
|
251
|
-
console.log(c("dim", ` A verification code has been sent to ${email}\n`));
|
|
252
253
|
|
|
253
|
-
|
|
254
|
+
if (response.fallback_code) {
|
|
255
|
+
// Email delivery not configured — show code in terminal
|
|
256
|
+
console.log(c("yellow", `\n Your verification code: ${c("bold", response.fallback_code)}\n`));
|
|
257
|
+
} else {
|
|
258
|
+
console.log(c("dim", ` A verification code has been sent to ${email}\n`));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return { email, fallbackCode: response.fallback_code || null };
|
|
254
262
|
}
|
|
255
263
|
}
|
|
256
264
|
|
|
@@ -301,7 +309,8 @@ async function cmdSetup() {
|
|
|
301
309
|
|
|
302
310
|
try {
|
|
303
311
|
// 3. Request OTP (with 1 retry for wrong email)
|
|
304
|
-
|
|
312
|
+
const otpResult = await requestOtpForEmail(email);
|
|
313
|
+
email = otpResult.email;
|
|
305
314
|
|
|
306
315
|
// 4. Verify OTP and get skills
|
|
307
316
|
const response = await verifyOtp(email);
|
|
@@ -360,14 +369,26 @@ async function cmdSetup() {
|
|
|
360
369
|
email,
|
|
361
370
|
team: teamName,
|
|
362
371
|
source: "supabase",
|
|
372
|
+
analyzeUrl: ANALYZE_FUNCTION_URL,
|
|
363
373
|
tools,
|
|
364
374
|
skills: skills.map((s) => ({ name: s.name, scope: s.scope, version: s.version })),
|
|
365
375
|
installedAt: new Date().toISOString(),
|
|
366
376
|
};
|
|
367
377
|
saveConfig(config);
|
|
368
378
|
|
|
369
|
-
// 7.
|
|
370
|
-
|
|
379
|
+
// 7. Install post-commit analysis hook
|
|
380
|
+
try {
|
|
381
|
+
const { installHook } = require(path.join(__dirname, "..", "hooks", "install-hook.js"));
|
|
382
|
+
const hookResult = installHook(PROJECT_ROOT);
|
|
383
|
+
if (hookResult.installed) {
|
|
384
|
+
console.log(`${c("green", "✓")} Post-commit analysis hook installed`);
|
|
385
|
+
}
|
|
386
|
+
} catch {
|
|
387
|
+
// Hook installation is optional — don't fail setup
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// 8. Summary
|
|
391
|
+
console.log(c("blue", "\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
|
|
371
392
|
console.log(c("green", "✅ Setup complete!"));
|
|
372
393
|
console.log(` ${skills.length} skills installed for ${tools.length} tool(s)`);
|
|
373
394
|
if (teamName) console.log(` Team: ${teamName}`);
|
|
@@ -392,7 +413,8 @@ async function cmdUpdate() {
|
|
|
392
413
|
|
|
393
414
|
try {
|
|
394
415
|
// Request OTP for the saved email
|
|
395
|
-
await requestOtpForEmail(email);
|
|
416
|
+
const otpResult = await requestOtpForEmail(email);
|
|
417
|
+
email = otpResult.email;
|
|
396
418
|
|
|
397
419
|
// Verify OTP
|
|
398
420
|
const response = await verifyOtp(email);
|
|
@@ -543,6 +565,142 @@ function cmdDoctor() {
|
|
|
543
565
|
}
|
|
544
566
|
}
|
|
545
567
|
|
|
568
|
+
// ── Analyze Command ──
|
|
569
|
+
|
|
570
|
+
async function cmdAnalyze() {
|
|
571
|
+
console.log(c("blue", "\n━━━ AI Skills Framework — Analyze ━━━\n"));
|
|
572
|
+
|
|
573
|
+
const config = loadConfig();
|
|
574
|
+
if (!config || !config.email) {
|
|
575
|
+
console.log(c("yellow", "No .ai-skills.json found. Run 'npx ai-skills setup' first."));
|
|
576
|
+
process.exit(1);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const skillNames = (config.skills || []).map((s) => s.name);
|
|
580
|
+
if (skillNames.length === 0) {
|
|
581
|
+
console.log(c("yellow", "No skills configured. Run 'npx ai-skills setup' first."));
|
|
582
|
+
process.exit(1);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
const analyzeUrl = config.analyzeUrl || ANALYZE_FUNCTION_URL;
|
|
586
|
+
|
|
587
|
+
// Check if we're in a git repo
|
|
588
|
+
try {
|
|
589
|
+
require("child_process").execSync("git rev-parse --git-dir", { stdio: "ignore", cwd: PROJECT_ROOT });
|
|
590
|
+
} catch {
|
|
591
|
+
console.log(c("red", "Not a git repository."));
|
|
592
|
+
process.exit(1);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
const execOpt = { cwd: PROJECT_ROOT, encoding: "utf-8", timeout: 10000 };
|
|
596
|
+
|
|
597
|
+
// Collect git data
|
|
598
|
+
const useLastCommit = process.argv.includes("--last");
|
|
599
|
+
let diff, commitHash, commitMessage, branch, filesChanged;
|
|
600
|
+
|
|
601
|
+
try {
|
|
602
|
+
if (useLastCommit) {
|
|
603
|
+
diff = require("child_process").execSync("git diff HEAD~1 HEAD", execOpt).trim();
|
|
604
|
+
commitHash = require("child_process").execSync("git rev-parse HEAD", execOpt).trim();
|
|
605
|
+
commitMessage = require("child_process").execSync('git log -1 --format="%s"', execOpt).trim();
|
|
606
|
+
filesChanged = require("child_process").execSync("git diff --name-only HEAD~1 HEAD", execOpt).trim().split("\n").filter(Boolean);
|
|
607
|
+
} else {
|
|
608
|
+
// Analyze staged changes or last commit
|
|
609
|
+
diff = require("child_process").execSync("git diff --cached", execOpt).trim();
|
|
610
|
+
if (!diff) {
|
|
611
|
+
diff = require("child_process").execSync("git diff HEAD~1 HEAD", execOpt).trim();
|
|
612
|
+
commitHash = require("child_process").execSync("git rev-parse HEAD", execOpt).trim();
|
|
613
|
+
commitMessage = require("child_process").execSync('git log -1 --format="%s"', execOpt).trim();
|
|
614
|
+
filesChanged = require("child_process").execSync("git diff --name-only HEAD~1 HEAD", execOpt).trim().split("\n").filter(Boolean);
|
|
615
|
+
} else {
|
|
616
|
+
commitHash = "(staged)";
|
|
617
|
+
commitMessage = "(staged changes)";
|
|
618
|
+
filesChanged = require("child_process").execSync("git diff --cached --name-only", execOpt).trim().split("\n").filter(Boolean);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
branch = require("child_process").execSync("git branch --show-current", execOpt).trim();
|
|
622
|
+
} catch (err) {
|
|
623
|
+
console.log(c("red", `Failed to collect git data: ${err.message}`));
|
|
624
|
+
process.exit(1);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
if (!diff) {
|
|
628
|
+
console.log(c("yellow", "No changes to analyze."));
|
|
629
|
+
process.exit(0);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// Truncate diff if needed
|
|
633
|
+
if (diff.length > 10000) {
|
|
634
|
+
diff = diff.slice(0, 4000) + "\n\n[...truncated...]\n\n" + diff.slice(-4000);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
console.log(`Analyzing ${c("bold", filesChanged.length)} file(s) against ${c("bold", skillNames.length)} skill(s)...`);
|
|
638
|
+
console.log(c("dim", `Commit: ${commitHash?.slice(0, 8)} — ${commitMessage}`));
|
|
639
|
+
console.log(c("dim", `Branch: ${branch}\n`));
|
|
640
|
+
|
|
641
|
+
// Get project name
|
|
642
|
+
let projectName = null;
|
|
643
|
+
try {
|
|
644
|
+
const pkgPath = path.join(PROJECT_ROOT, "package.json");
|
|
645
|
+
if (fs.existsSync(pkgPath)) {
|
|
646
|
+
projectName = JSON.parse(fs.readFileSync(pkgPath, "utf-8")).name || null;
|
|
647
|
+
}
|
|
648
|
+
} catch { /* ignore */ }
|
|
649
|
+
|
|
650
|
+
try {
|
|
651
|
+
console.log(c("dim", "Sending to AI for analysis (this may take 10-30 seconds)...\n"));
|
|
652
|
+
|
|
653
|
+
const result = await fetchJSON(analyzeUrl, {
|
|
654
|
+
email: config.email,
|
|
655
|
+
commit_hash: commitHash,
|
|
656
|
+
commit_message: commitMessage,
|
|
657
|
+
branch,
|
|
658
|
+
project_name: projectName,
|
|
659
|
+
files_changed: filesChanged,
|
|
660
|
+
diff,
|
|
661
|
+
skill_names: skillNames,
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
if (result.error) {
|
|
665
|
+
throw new Error(result.error);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// Display results
|
|
669
|
+
console.log(c("blue", "━━━ Analysis Results ━━━\n"));
|
|
670
|
+
const score = result.overall_score || 0;
|
|
671
|
+
const scoreColor = score >= 80 ? "green" : score >= 50 ? "yellow" : "red";
|
|
672
|
+
console.log(`Overall Score: ${c(scoreColor, `${score}/100`)}\n`);
|
|
673
|
+
|
|
674
|
+
if (result.skill_scores) {
|
|
675
|
+
for (const [skillName, data] of Object.entries(result.skill_scores)) {
|
|
676
|
+
const s = data;
|
|
677
|
+
const sColor = s.score >= 80 ? "green" : s.score >= 50 ? "yellow" : "red";
|
|
678
|
+
console.log(`${c("bold", skillName)}: ${c(sColor, `${s.score}/100`)}`);
|
|
679
|
+
if (s.passed?.length) {
|
|
680
|
+
for (const p of s.passed) console.log(` ${c("green", "✓")} ${p}`);
|
|
681
|
+
}
|
|
682
|
+
if (s.failed?.length) {
|
|
683
|
+
for (const f of s.failed) console.log(` ${c("red", "✗")} ${f}`);
|
|
684
|
+
}
|
|
685
|
+
if (s.suggestions?.length) {
|
|
686
|
+
for (const sg of s.suggestions) console.log(` ${c("yellow", "→")} ${sg}`);
|
|
687
|
+
}
|
|
688
|
+
console.log("");
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
if (result.summary) {
|
|
693
|
+
console.log(c("dim", `Summary: ${result.summary}`));
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
console.log(c("dim", `\nTokens used: ${result.tokens_used || "?"} | Duration: ${result.duration_ms || "?"}ms\n`));
|
|
697
|
+
|
|
698
|
+
} catch (err) {
|
|
699
|
+
console.log(c("red", `\n✗ Analysis failed: ${err.message}`));
|
|
700
|
+
process.exit(1);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
546
704
|
// ── Main ──
|
|
547
705
|
|
|
548
706
|
const command = process.argv[2] || "setup";
|
|
@@ -553,6 +711,7 @@ switch (command) {
|
|
|
553
711
|
case "status": cmdStatus(); break;
|
|
554
712
|
case "list": cmdList(); break;
|
|
555
713
|
case "doctor": cmdDoctor(); break;
|
|
714
|
+
case "analyze": cmdAnalyze(); break;
|
|
556
715
|
case "help": case "--help": case "-h":
|
|
557
716
|
console.log(`
|
|
558
717
|
${c("blue", "AI Skills Framework")} — @valentia-ai-skills/framework
|
|
@@ -562,8 +721,12 @@ Usage:
|
|
|
562
721
|
npx ai-skills update Re-fetch and update skills for your team
|
|
563
722
|
npx ai-skills status Show installed skills, team, and tools
|
|
564
723
|
npx ai-skills list List locally bundled skills
|
|
724
|
+
npx ai-skills analyze Analyze last commit against active skills
|
|
565
725
|
npx ai-skills doctor Health check (config + API + tools)
|
|
566
726
|
|
|
727
|
+
Flags:
|
|
728
|
+
analyze --last Analyze the most recent commit
|
|
729
|
+
|
|
567
730
|
Environment:
|
|
568
731
|
AI_SKILLS_API_URL Override the Supabase Edge Function URL
|
|
569
732
|
`); break;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Background commit analyzer.
|
|
5
|
+
* Collects git diff + metadata, sends to the analyze-commit Edge Function.
|
|
6
|
+
* Runs as a detached process — never shows output to the developer.
|
|
7
|
+
* Everything is wrapped in try/catch — fails silently on any error.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require("fs");
|
|
11
|
+
const path = require("path");
|
|
12
|
+
const { execSync } = require("child_process");
|
|
13
|
+
const https = require("https");
|
|
14
|
+
const http = require("http");
|
|
15
|
+
|
|
16
|
+
const PROJECT_ROOT = process.env.AI_SKILLS_CWD || process.cwd();
|
|
17
|
+
const CONFIG_PATH = path.join(PROJECT_ROOT, ".ai-skills.json");
|
|
18
|
+
|
|
19
|
+
// Default Edge Function URL (can be overridden in .ai-skills.json)
|
|
20
|
+
const DEFAULT_API_URL =
|
|
21
|
+
"https://znshdhjquohrzvbnloki.supabase.co/functions/v1/analyze-commit";
|
|
22
|
+
|
|
23
|
+
function loadConfig() {
|
|
24
|
+
try {
|
|
25
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
26
|
+
return JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8"));
|
|
27
|
+
}
|
|
28
|
+
} catch {
|
|
29
|
+
// ignore
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function git(cmd) {
|
|
35
|
+
try {
|
|
36
|
+
return execSync(cmd, { cwd: PROJECT_ROOT, encoding: "utf-8", timeout: 10000 }).trim();
|
|
37
|
+
} catch {
|
|
38
|
+
return "";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function truncateDiff(diff, maxLen = 10000) {
|
|
43
|
+
if (diff.length <= maxLen) return diff;
|
|
44
|
+
const half = Math.floor(maxLen / 2);
|
|
45
|
+
return diff.slice(0, half) + "\n\n[...truncated...]\n\n" + diff.slice(-half);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function postJSON(url, body) {
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
const parsed = new URL(url);
|
|
51
|
+
const mod = parsed.protocol === "https:" ? https : http;
|
|
52
|
+
const postData = JSON.stringify(body);
|
|
53
|
+
|
|
54
|
+
const req = mod.request(
|
|
55
|
+
{
|
|
56
|
+
hostname: parsed.hostname,
|
|
57
|
+
port: parsed.port,
|
|
58
|
+
path: parsed.pathname + parsed.search,
|
|
59
|
+
method: "POST",
|
|
60
|
+
headers: {
|
|
61
|
+
"Content-Type": "application/json",
|
|
62
|
+
"Content-Length": Buffer.byteLength(postData),
|
|
63
|
+
},
|
|
64
|
+
timeout: 60000, // 60s timeout for AI analysis
|
|
65
|
+
},
|
|
66
|
+
(res) => {
|
|
67
|
+
let data = "";
|
|
68
|
+
res.on("data", (chunk) => (data += chunk));
|
|
69
|
+
res.on("end", () => resolve(data));
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
req.on("error", reject);
|
|
73
|
+
req.on("timeout", () => { req.destroy(); reject(new Error("timeout")); });
|
|
74
|
+
req.write(postData);
|
|
75
|
+
req.end();
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function main() {
|
|
80
|
+
// 1. Read config
|
|
81
|
+
const config = loadConfig();
|
|
82
|
+
if (!config || !config.email) return; // No config = not set up, silently exit
|
|
83
|
+
|
|
84
|
+
const email = config.email;
|
|
85
|
+
const skillNames = (config.skills || []).map((s) => s.name);
|
|
86
|
+
if (skillNames.length === 0) return; // No skills to check against
|
|
87
|
+
|
|
88
|
+
const apiUrl = config.analyzeUrl || process.env.AI_SKILLS_ANALYZE_URL || DEFAULT_API_URL;
|
|
89
|
+
|
|
90
|
+
// 2. Collect git data
|
|
91
|
+
const diff = git("git diff HEAD~1 HEAD");
|
|
92
|
+
if (!diff) return; // No diff = nothing to analyze
|
|
93
|
+
|
|
94
|
+
const commitHash = git("git rev-parse HEAD");
|
|
95
|
+
const commitMessage = git('git log -1 --format="%s"');
|
|
96
|
+
const branch = git("git branch --show-current");
|
|
97
|
+
const filesChangedRaw = git("git diff --name-only HEAD~1 HEAD");
|
|
98
|
+
const filesChanged = filesChangedRaw ? filesChangedRaw.split("\n").filter(Boolean) : [];
|
|
99
|
+
|
|
100
|
+
// Try to get project name from package.json
|
|
101
|
+
let projectName = null;
|
|
102
|
+
try {
|
|
103
|
+
const pkgPath = path.join(PROJECT_ROOT, "package.json");
|
|
104
|
+
if (fs.existsSync(pkgPath)) {
|
|
105
|
+
projectName = JSON.parse(fs.readFileSync(pkgPath, "utf-8")).name || null;
|
|
106
|
+
}
|
|
107
|
+
} catch {
|
|
108
|
+
// ignore
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 3. Send to Edge Function
|
|
112
|
+
await postJSON(apiUrl, {
|
|
113
|
+
email,
|
|
114
|
+
commit_hash: commitHash,
|
|
115
|
+
commit_message: commitMessage,
|
|
116
|
+
branch,
|
|
117
|
+
project_name: projectName,
|
|
118
|
+
files_changed: filesChanged,
|
|
119
|
+
diff: truncateDiff(diff),
|
|
120
|
+
skill_names: skillNames,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Run and exit silently regardless of outcome
|
|
125
|
+
main().catch(() => {}).finally(() => process.exit(0));
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Installs the post-commit analysis hook into .git/hooks/post-commit.
|
|
5
|
+
* Called during `npx ai-skills setup`.
|
|
6
|
+
*
|
|
7
|
+
* The hook script delegates to the npm package's hooks/post-commit-analyze.js,
|
|
8
|
+
* so updates to the analysis logic are picked up automatically on npm update.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require("fs");
|
|
12
|
+
const path = require("path");
|
|
13
|
+
|
|
14
|
+
function installHook(projectRoot) {
|
|
15
|
+
const gitHooksDir = path.join(projectRoot, ".git", "hooks");
|
|
16
|
+
|
|
17
|
+
// Check if this is a git repo
|
|
18
|
+
if (!fs.existsSync(path.join(projectRoot, ".git"))) {
|
|
19
|
+
return { installed: false, reason: "Not a git repository" };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Ensure hooks directory exists
|
|
23
|
+
if (!fs.existsSync(gitHooksDir)) {
|
|
24
|
+
fs.mkdirSync(gitHooksDir, { recursive: true });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const hookPath = path.join(gitHooksDir, "post-commit");
|
|
28
|
+
|
|
29
|
+
// Find the path to the hook script in the npm package
|
|
30
|
+
// Works whether running from node_modules or directly
|
|
31
|
+
const hookScript = path.join(__dirname, "post-commit-analyze.js");
|
|
32
|
+
|
|
33
|
+
// Generate the hook content — delegates to the npm package script
|
|
34
|
+
const hookContent = `#!/bin/sh
|
|
35
|
+
# AI Skills Framework — post-commit analysis hook
|
|
36
|
+
# Auto-installed by npx ai-skills setup
|
|
37
|
+
# Runs code analysis in the background after each commit (non-blocking)
|
|
38
|
+
|
|
39
|
+
node "${hookScript}" &
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
// Check if an existing post-commit hook exists
|
|
43
|
+
if (fs.existsSync(hookPath)) {
|
|
44
|
+
const existing = fs.readFileSync(hookPath, "utf-8");
|
|
45
|
+
if (existing.includes("ai-skills")) {
|
|
46
|
+
// Already installed — update it
|
|
47
|
+
fs.writeFileSync(hookPath, hookContent);
|
|
48
|
+
fs.chmodSync(hookPath, "755");
|
|
49
|
+
return { installed: true, reason: "Updated existing hook" };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// There's a different post-commit hook — append our line
|
|
53
|
+
const appendLine = `\n# AI Skills Framework — post-commit analysis\nnode "${hookScript}" &\n`;
|
|
54
|
+
fs.appendFileSync(hookPath, appendLine);
|
|
55
|
+
fs.chmodSync(hookPath, "755");
|
|
56
|
+
return { installed: true, reason: "Appended to existing hook" };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// No existing hook — create new one
|
|
60
|
+
fs.writeFileSync(hookPath, hookContent);
|
|
61
|
+
fs.chmodSync(hookPath, "755");
|
|
62
|
+
return { installed: true, reason: "Hook installed" };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// If run directly (not required as module)
|
|
66
|
+
if (require.main === module) {
|
|
67
|
+
const result = installHook(process.cwd());
|
|
68
|
+
if (result.installed) {
|
|
69
|
+
console.log(`✓ Post-commit hook: ${result.reason}`);
|
|
70
|
+
} else {
|
|
71
|
+
console.log(`⚠ Post-commit hook skipped: ${result.reason}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports = { installHook };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Post-commit hook entry point.
|
|
5
|
+
* Spawns the actual analysis as a detached background process so git returns immediately.
|
|
6
|
+
* This file is copied into .git/hooks/post-commit during `npx ai-skills setup`.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { spawn } = require("child_process");
|
|
10
|
+
const path = require("path");
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
// Find do-analysis.js relative to this hook's location in the npm package
|
|
14
|
+
// When installed via npm, hooks/ is inside node_modules/@valentia-ai-skills/framework/hooks/
|
|
15
|
+
const analysisScript = path.join(__dirname, "do-analysis.js");
|
|
16
|
+
|
|
17
|
+
const child = spawn("node", [analysisScript], {
|
|
18
|
+
detached: true,
|
|
19
|
+
stdio: "ignore",
|
|
20
|
+
cwd: process.cwd(),
|
|
21
|
+
env: { ...process.env, AI_SKILLS_CWD: process.cwd() },
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
child.unref();
|
|
25
|
+
} catch {
|
|
26
|
+
// Never block the developer — silently exit
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
process.exit(0);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@valentia-ai-skills/framework",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "AI development skills framework
|
|
3
|
+
"version": "1.0.9",
|
|
4
|
+
"description": "AI development skills framework ÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂâÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂàcentralized coding standards, security patterns, and SOPs for AI-assisted development. Works with Claude Code, Cursor, Copilot, Windsurf, and any AI coding tool.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-skills",
|
|
7
7
|
"claude-code",
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"src/",
|
|
35
35
|
"skills/",
|
|
36
36
|
"scripts/",
|
|
37
|
+
"hooks/",
|
|
37
38
|
"README.md"
|
|
38
39
|
],
|
|
39
40
|
"engines": {
|