@codacy/gate-cli 0.2.0 → 0.4.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/README.md +31 -15
- package/bin/gate.js +181 -34
- package/data/skills/gate-analyze/SKILL.md +214 -0
- package/data/skills/gate-feedback/SKILL.md +12 -0
- package/data/skills/gate-setup/SKILL.md +402 -0
- package/data/skills/gate-setup/patterns-reference.yaml +586 -0
- package/data/skills/gate-setup/standard-template.yaml +141 -0
- package/data/skills/gate-status/SKILL.md +45 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,19 +1,46 @@
|
|
|
1
1
|
# @codacy/gate-cli
|
|
2
2
|
|
|
3
|
-
CLI for [GATE.md](https://
|
|
3
|
+
CLI for [GATE.md](https://gatemd.lovable.app) — the quality gate for AI-generated code.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
npm install -g @codacy/gate-cli
|
|
9
|
+
cd your-project
|
|
10
|
+
gate init
|
|
9
11
|
```
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
Then open the project in Claude Code and run `/gate-setup`.
|
|
14
|
+
|
|
15
|
+
### Permission denied?
|
|
16
|
+
|
|
17
|
+
If `npm install -g` fails with `EACCES`, your npm global directory needs fixing. Pick one:
|
|
18
|
+
|
|
19
|
+
**Option A — Fix npm prefix (recommended, one-time):**
|
|
20
|
+
```bash
|
|
21
|
+
mkdir -p ~/.npm-global
|
|
22
|
+
npm config set prefix ~/.npm-global
|
|
23
|
+
echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.zshrc # or ~/.bashrc
|
|
24
|
+
source ~/.zshrc
|
|
25
|
+
npm install -g @codacy/gate-cli
|
|
26
|
+
```
|
|
12
27
|
|
|
13
|
-
|
|
28
|
+
**Option B — Use npx (no global install):**
|
|
29
|
+
```bash
|
|
30
|
+
npx @codacy/gate-cli init
|
|
31
|
+
npx @codacy/gate-cli hooks install
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Option C — Use sudo (not recommended):**
|
|
35
|
+
```bash
|
|
36
|
+
sudo npm install -g @codacy/gate-cli
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Quick reference
|
|
14
40
|
|
|
15
41
|
| Command | Description |
|
|
16
42
|
|---------|-------------|
|
|
43
|
+
| `gate init` | Initialize GATE.md in current project (skills + hooks) |
|
|
17
44
|
| `gate analyze` | Run analysis on changed files (stop hook) |
|
|
18
45
|
| `gate review --files <paths>` | On-demand analysis (advisory) |
|
|
19
46
|
| `gate auth register` | Register a project |
|
|
@@ -23,7 +50,6 @@ Requires Node.js 20+.
|
|
|
23
50
|
| `gate config push` | Upload analysis config |
|
|
24
51
|
| `gate status` | Show project quality status |
|
|
25
52
|
| `gate feedback <msg>` | Send feedback |
|
|
26
|
-
| `gate intent capture` | Capture user intent (hook) |
|
|
27
53
|
|
|
28
54
|
## Global flags
|
|
29
55
|
|
|
@@ -43,14 +69,4 @@ GATE.md intercepts AI coding agent output, runs static analysis + independent Ge
|
|
|
43
69
|
Agent writes code → gate analyze → codacy-analysis + Gemini → PASS/FAIL
|
|
44
70
|
```
|
|
45
71
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
```bash
|
|
49
|
-
# Install
|
|
50
|
-
npm install -g @codacy/gate-cli @codacy/analysis-cli
|
|
51
|
-
|
|
52
|
-
# Wire Claude Code hooks
|
|
53
|
-
gate hooks install
|
|
54
|
-
|
|
55
|
-
# Then run /gate-setup in Claude Code to register + configure
|
|
56
|
-
```
|
|
72
|
+
Requires Node.js 20+.
|
package/bin/gate.js
CHANGED
|
@@ -10326,14 +10326,27 @@ var {
|
|
|
10326
10326
|
|
|
10327
10327
|
// src/commands/auth.ts
|
|
10328
10328
|
var import_promises3 = require("node:fs/promises");
|
|
10329
|
-
var
|
|
10330
|
-
var
|
|
10329
|
+
var import_node_child_process3 = require("node:child_process");
|
|
10330
|
+
var import_node_path2 = require("node:path");
|
|
10331
10331
|
|
|
10332
10332
|
// src/lib/auth.ts
|
|
10333
10333
|
var import_promises = require("node:fs/promises");
|
|
10334
|
-
var
|
|
10334
|
+
var import_node_child_process2 = require("node:child_process");
|
|
10335
10335
|
|
|
10336
10336
|
// src/constants.ts
|
|
10337
|
+
var import_node_child_process = require("node:child_process");
|
|
10338
|
+
var import_node_path = require("node:path");
|
|
10339
|
+
var _repoRoot = null;
|
|
10340
|
+
function repoRoot() {
|
|
10341
|
+
if (_repoRoot === null) {
|
|
10342
|
+
try {
|
|
10343
|
+
_repoRoot = (0, import_node_child_process.execSync)("git rev-parse --show-toplevel", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
10344
|
+
} catch {
|
|
10345
|
+
_repoRoot = process.cwd();
|
|
10346
|
+
}
|
|
10347
|
+
}
|
|
10348
|
+
return _repoRoot;
|
|
10349
|
+
}
|
|
10337
10350
|
var GATE_DIR = ".gate";
|
|
10338
10351
|
var CREDENTIALS_FILE = `${GATE_DIR}/credentials`;
|
|
10339
10352
|
var GLOBAL_CREDENTIALS_FILE = `${process.env.HOME}/.gate/credentials`;
|
|
@@ -10346,6 +10359,9 @@ var GATE_MD_FILE = "GATE.md";
|
|
|
10346
10359
|
var CLAUDE_SETTINGS_FILE = ".claude/settings.json";
|
|
10347
10360
|
var STANDARD_FILE = `${GATE_DIR}/standard.yaml`;
|
|
10348
10361
|
var CODACY_CONFIG_FILE = ".codacy/codacy.config.json";
|
|
10362
|
+
function projectPath(relativePath) {
|
|
10363
|
+
return (0, import_node_path.join)(repoRoot(), relativePath);
|
|
10364
|
+
}
|
|
10349
10365
|
var MAX_DELTA_BYTES = 204800;
|
|
10350
10366
|
var MAX_FILES = 20;
|
|
10351
10367
|
var MAX_FILE_BYTES = 51200;
|
|
@@ -10460,7 +10476,7 @@ async function resolveToken(flagToken) {
|
|
|
10460
10476
|
return { ok: true, data: { token: envToken, source: "env" } };
|
|
10461
10477
|
}
|
|
10462
10478
|
try {
|
|
10463
|
-
const content = await (0, import_promises.readFile)(CREDENTIALS_FILE, "utf-8");
|
|
10479
|
+
const content = await (0, import_promises.readFile)(projectPath(CREDENTIALS_FILE), "utf-8");
|
|
10464
10480
|
const match = content.match(/token:\s*(gate_[a-f0-9]+)/);
|
|
10465
10481
|
if (match) {
|
|
10466
10482
|
return { ok: true, data: { token: match[1], source: "local" } };
|
|
@@ -10472,7 +10488,7 @@ async function resolveToken(flagToken) {
|
|
|
10472
10488
|
const content = await (0, import_promises.readFile)(globalCredentials, "utf-8");
|
|
10473
10489
|
let remote = "";
|
|
10474
10490
|
try {
|
|
10475
|
-
remote = (0,
|
|
10491
|
+
remote = (0, import_node_child_process2.execSync)("git remote get-url origin", { encoding: "utf-8" }).trim();
|
|
10476
10492
|
} catch {
|
|
10477
10493
|
}
|
|
10478
10494
|
if (remote) {
|
|
@@ -10504,7 +10520,7 @@ async function resolveServiceUrl(flagUrl) {
|
|
|
10504
10520
|
return { ok: true, data: envUrl };
|
|
10505
10521
|
}
|
|
10506
10522
|
try {
|
|
10507
|
-
const creds = await (0, import_promises2.readFile)(CREDENTIALS_FILE, "utf-8");
|
|
10523
|
+
const creds = await (0, import_promises2.readFile)(projectPath(CREDENTIALS_FILE), "utf-8");
|
|
10508
10524
|
const match = creds.match(/service_url:\s*(https?:\/\/[^\s]+)/);
|
|
10509
10525
|
if (match) {
|
|
10510
10526
|
return { ok: true, data: match[1] };
|
|
@@ -10512,7 +10528,7 @@ async function resolveServiceUrl(flagUrl) {
|
|
|
10512
10528
|
} catch {
|
|
10513
10529
|
}
|
|
10514
10530
|
try {
|
|
10515
|
-
const content = await (0, import_promises2.readFile)(GATE_MD_FILE, "utf-8");
|
|
10531
|
+
const content = await (0, import_promises2.readFile)(projectPath(GATE_MD_FILE), "utf-8");
|
|
10516
10532
|
const boldMatch = content.match(/\*\*url\*\*/i);
|
|
10517
10533
|
if (boldMatch) {
|
|
10518
10534
|
const lineMatch = content.split("\n").find((l) => /\*\*url\*\*/i.test(l));
|
|
@@ -10621,7 +10637,7 @@ function registerAuthCommands(program2) {
|
|
|
10621
10637
|
let remote = opts.remote;
|
|
10622
10638
|
if (!remote) {
|
|
10623
10639
|
try {
|
|
10624
|
-
remote = (0,
|
|
10640
|
+
remote = (0, import_node_child_process3.execSync)("git remote get-url origin", { encoding: "utf-8" }).trim();
|
|
10625
10641
|
} catch {
|
|
10626
10642
|
printError("No git remote found. Use --remote to specify one.");
|
|
10627
10643
|
process.exit(1);
|
|
@@ -10644,7 +10660,7 @@ function registerAuthCommands(program2) {
|
|
|
10644
10660
|
service_url: ${service_url}
|
|
10645
10661
|
`);
|
|
10646
10662
|
try {
|
|
10647
|
-
await (0, import_promises3.mkdir)((0,
|
|
10663
|
+
await (0, import_promises3.mkdir)((0, import_node_path2.dirname)(GLOBAL_CREDENTIALS_FILE), { recursive: true });
|
|
10648
10664
|
await (0, import_promises3.appendFile)(GLOBAL_CREDENTIALS_FILE, `${remote} token: ${token}
|
|
10649
10665
|
`);
|
|
10650
10666
|
} catch {
|
|
@@ -10683,7 +10699,7 @@ service_url: ${service_url}
|
|
|
10683
10699
|
let remote = opts.remote;
|
|
10684
10700
|
if (!remote) {
|
|
10685
10701
|
try {
|
|
10686
|
-
remote = (0,
|
|
10702
|
+
remote = (0, import_node_child_process3.execSync)("git remote get-url origin", { encoding: "utf-8" }).trim();
|
|
10687
10703
|
} catch {
|
|
10688
10704
|
printError("No git remote found. Use --remote to specify one.");
|
|
10689
10705
|
process.exit(1);
|
|
@@ -10711,7 +10727,7 @@ service_url: ${service_url}
|
|
|
10711
10727
|
|
|
10712
10728
|
// src/lib/hooks.ts
|
|
10713
10729
|
var import_promises4 = require("node:fs/promises");
|
|
10714
|
-
var
|
|
10730
|
+
var import_node_path3 = require("node:path");
|
|
10715
10731
|
var GATE_STOP_HOOK = {
|
|
10716
10732
|
type: "command",
|
|
10717
10733
|
command: "gate analyze",
|
|
@@ -10734,7 +10750,7 @@ async function readSettings() {
|
|
|
10734
10750
|
}
|
|
10735
10751
|
}
|
|
10736
10752
|
async function writeSettings(settings) {
|
|
10737
|
-
await (0, import_promises4.mkdir)((0,
|
|
10753
|
+
await (0, import_promises4.mkdir)((0, import_node_path3.dirname)(CLAUDE_SETTINGS_FILE), { recursive: true });
|
|
10738
10754
|
await (0, import_promises4.writeFile)(CLAUDE_SETTINGS_FILE, JSON.stringify(settings, null, 2) + "\n");
|
|
10739
10755
|
}
|
|
10740
10756
|
function checkGateHooks(settings) {
|
|
@@ -11208,9 +11224,9 @@ function registerFeedbackCommand(program2) {
|
|
|
11208
11224
|
}
|
|
11209
11225
|
|
|
11210
11226
|
// src/lib/git.ts
|
|
11211
|
-
var
|
|
11227
|
+
var import_node_child_process4 = require("node:child_process");
|
|
11212
11228
|
var import_node_fs2 = require("node:fs");
|
|
11213
|
-
var
|
|
11229
|
+
var import_node_path4 = require("node:path");
|
|
11214
11230
|
function resolveFile(relpath) {
|
|
11215
11231
|
if ((0, import_node_fs2.existsSync)(relpath)) return relpath;
|
|
11216
11232
|
if ((0, import_node_fs2.existsSync)(".claude/worktrees")) {
|
|
@@ -11218,7 +11234,7 @@ function resolveFile(relpath) {
|
|
|
11218
11234
|
const entries = (0, import_node_fs2.readdirSync)(".claude/worktrees", { withFileTypes: true });
|
|
11219
11235
|
for (const entry of entries) {
|
|
11220
11236
|
if (!entry.isDirectory()) continue;
|
|
11221
|
-
const candidate = (0,
|
|
11237
|
+
const candidate = (0, import_node_path4.join)(".claude/worktrees", entry.name, relpath);
|
|
11222
11238
|
if ((0, import_node_fs2.existsSync)(candidate)) return candidate;
|
|
11223
11239
|
}
|
|
11224
11240
|
} catch {
|
|
@@ -11228,7 +11244,7 @@ function resolveFile(relpath) {
|
|
|
11228
11244
|
}
|
|
11229
11245
|
function execGit(cmd) {
|
|
11230
11246
|
try {
|
|
11231
|
-
return (0,
|
|
11247
|
+
return (0, import_node_child_process4.execSync)(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
11232
11248
|
} catch {
|
|
11233
11249
|
return "";
|
|
11234
11250
|
}
|
|
@@ -11271,7 +11287,7 @@ function getWorktreeFiles() {
|
|
|
11271
11287
|
const entries = (0, import_node_fs2.readdirSync)(worktreeDir, { withFileTypes: true });
|
|
11272
11288
|
for (const entry of entries) {
|
|
11273
11289
|
if (!entry.isDirectory()) continue;
|
|
11274
|
-
const wtDir = (0,
|
|
11290
|
+
const wtDir = (0, import_node_path4.join)(worktreeDir, entry.name);
|
|
11275
11291
|
scanDir(wtDir, wtDir, fiveMinAgo, result);
|
|
11276
11292
|
}
|
|
11277
11293
|
} catch {
|
|
@@ -11282,12 +11298,12 @@ function scanDir(baseDir, dir, minMtime, result) {
|
|
|
11282
11298
|
try {
|
|
11283
11299
|
const entries = (0, import_node_fs2.readdirSync)(dir, { withFileTypes: true });
|
|
11284
11300
|
for (const entry of entries) {
|
|
11285
|
-
const fullPath = (0,
|
|
11301
|
+
const fullPath = (0, import_node_path4.join)(dir, entry.name);
|
|
11286
11302
|
if (entry.isDirectory()) {
|
|
11287
11303
|
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
11288
11304
|
scanDir(baseDir, fullPath, minMtime, result);
|
|
11289
11305
|
} else if (entry.isFile()) {
|
|
11290
|
-
const ext = (0,
|
|
11306
|
+
const ext = (0, import_node_path4.extname)(entry.name).slice(1);
|
|
11291
11307
|
if (!ANALYZABLE_EXTENSIONS.has(ext)) continue;
|
|
11292
11308
|
try {
|
|
11293
11309
|
const stat = (0, import_node_fs2.statSync)(fullPath);
|
|
@@ -11304,13 +11320,13 @@ function scanDir(baseDir, dir, minMtime, result) {
|
|
|
11304
11320
|
}
|
|
11305
11321
|
function filterAnalyzable(files) {
|
|
11306
11322
|
return files.filter((f) => {
|
|
11307
|
-
const ext = (0,
|
|
11323
|
+
const ext = (0, import_node_path4.extname)(f).slice(1);
|
|
11308
11324
|
return ANALYZABLE_EXTENSIONS.has(ext);
|
|
11309
11325
|
});
|
|
11310
11326
|
}
|
|
11311
11327
|
function filterReviewable(files) {
|
|
11312
11328
|
return files.filter((f) => {
|
|
11313
|
-
const ext = (0,
|
|
11329
|
+
const ext = (0, import_node_path4.extname)(f).slice(1);
|
|
11314
11330
|
if (ANALYZABLE_EXTENSIONS.has(ext)) return false;
|
|
11315
11331
|
if (REVIEWABLE_EXTENSIONS.has(ext)) return true;
|
|
11316
11332
|
const basename2 = f.split("/").pop() ?? "";
|
|
@@ -11330,7 +11346,7 @@ function getCurrentCommit() {
|
|
|
11330
11346
|
|
|
11331
11347
|
// src/lib/files.ts
|
|
11332
11348
|
var import_node_fs3 = require("node:fs");
|
|
11333
|
-
var
|
|
11349
|
+
var import_node_path5 = require("node:path");
|
|
11334
11350
|
var LANG_MAP = {
|
|
11335
11351
|
// Analyzable (static analysis + Gemini)
|
|
11336
11352
|
ts: "typescript",
|
|
@@ -11398,7 +11414,7 @@ var LANG_MAP = {
|
|
|
11398
11414
|
mk: "make"
|
|
11399
11415
|
};
|
|
11400
11416
|
function detectLanguage(filepath) {
|
|
11401
|
-
const ext = (0,
|
|
11417
|
+
const ext = (0, import_node_path5.extname)(filepath).slice(1);
|
|
11402
11418
|
return LANG_MAP[ext] ?? ext;
|
|
11403
11419
|
}
|
|
11404
11420
|
function sortByMtime(files) {
|
|
@@ -11577,7 +11593,7 @@ function writeIteration(iteration, commit) {
|
|
|
11577
11593
|
}
|
|
11578
11594
|
|
|
11579
11595
|
// src/lib/static-analysis.ts
|
|
11580
|
-
var
|
|
11596
|
+
var import_node_child_process5 = require("node:child_process");
|
|
11581
11597
|
var import_node_fs5 = require("node:fs");
|
|
11582
11598
|
var SEVERITY_ORDER = {
|
|
11583
11599
|
Error: 0,
|
|
@@ -11590,7 +11606,7 @@ var SEVERITY_ORDER = {
|
|
|
11590
11606
|
};
|
|
11591
11607
|
function isCodacyAvailable() {
|
|
11592
11608
|
try {
|
|
11593
|
-
(0,
|
|
11609
|
+
(0, import_node_child_process5.execSync)("which codacy-analysis", { stdio: "pipe" });
|
|
11594
11610
|
return true;
|
|
11595
11611
|
} catch {
|
|
11596
11612
|
return false;
|
|
@@ -11614,7 +11630,7 @@ function runCodacyAnalysis(files) {
|
|
|
11614
11630
|
const fileArgs = existingFiles.join(" ");
|
|
11615
11631
|
let output;
|
|
11616
11632
|
try {
|
|
11617
|
-
output = (0,
|
|
11633
|
+
output = (0, import_node_child_process5.execSync)(
|
|
11618
11634
|
`codacy-analysis analyze --install-dependencies --files ${fileArgs} --output-format json --log-level error --parallel-tools 3`,
|
|
11619
11635
|
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], maxBuffer: 10 * 1024 * 1024 }
|
|
11620
11636
|
);
|
|
@@ -11668,7 +11684,7 @@ function runCodacyAnalysis(files) {
|
|
|
11668
11684
|
|
|
11669
11685
|
// src/lib/specs.ts
|
|
11670
11686
|
var import_node_fs6 = require("node:fs");
|
|
11671
|
-
var
|
|
11687
|
+
var import_node_path6 = require("node:path");
|
|
11672
11688
|
var SPEC_CANDIDATES = [
|
|
11673
11689
|
"CLAUDE.md",
|
|
11674
11690
|
"AGENTS.md",
|
|
@@ -11730,7 +11746,7 @@ function findMdFiles(dir, maxDepth, depth = 0) {
|
|
|
11730
11746
|
try {
|
|
11731
11747
|
const entries = (0, import_node_fs6.readdirSync)(dir, { withFileTypes: true });
|
|
11732
11748
|
for (const entry of entries) {
|
|
11733
|
-
const fullPath = (0,
|
|
11749
|
+
const fullPath = (0, import_node_path6.join)(dir, entry.name);
|
|
11734
11750
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
11735
11751
|
result.push(fullPath);
|
|
11736
11752
|
} else if (entry.isDirectory() && depth < maxDepth - 1) {
|
|
@@ -11747,7 +11763,7 @@ function discoverPlans() {
|
|
|
11747
11763
|
const result = [];
|
|
11748
11764
|
try {
|
|
11749
11765
|
const entries = (0, import_node_fs6.readdirSync)(plansDir).filter((f) => f.endsWith(".md")).map((f) => {
|
|
11750
|
-
const fullPath = (0,
|
|
11766
|
+
const fullPath = (0, import_node_path6.join)(plansDir, f);
|
|
11751
11767
|
try {
|
|
11752
11768
|
const stat = (0, import_node_fs6.statSync)(fullPath);
|
|
11753
11769
|
return { name: f, path: fullPath, mtime: stat.mtimeMs, size: stat.size };
|
|
@@ -11847,11 +11863,11 @@ async function runAnalyze(opts, globals) {
|
|
|
11847
11863
|
}
|
|
11848
11864
|
const tokenResult = await resolveToken(globals.token);
|
|
11849
11865
|
if (!tokenResult.ok) {
|
|
11850
|
-
passAndExit("Not configured yet
|
|
11866
|
+
passAndExit("Not configured yet \u2014 run /gate-setup in Claude Code to enable quality gates.");
|
|
11851
11867
|
}
|
|
11852
11868
|
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
11853
11869
|
if (!urlResult.ok) {
|
|
11854
|
-
passAndExit("
|
|
11870
|
+
passAndExit("Not configured yet \u2014 run /gate-setup in Claude Code to enable quality gates.");
|
|
11855
11871
|
}
|
|
11856
11872
|
const currentCommit = getCurrentCommit();
|
|
11857
11873
|
const maxIterations = parseInt(opts.maxIterations, 10);
|
|
@@ -12020,12 +12036,12 @@ async function runReview(opts, globals) {
|
|
|
12020
12036
|
const codeDelta = collectCodeDelta(allFiles);
|
|
12021
12037
|
const tokenResult = await resolveToken(globals.token);
|
|
12022
12038
|
if (!tokenResult.ok) {
|
|
12023
|
-
printError(
|
|
12039
|
+
printError("Not configured yet \u2014 run /gate-setup in Claude Code to enable quality gates.");
|
|
12024
12040
|
process.exit(0);
|
|
12025
12041
|
}
|
|
12026
12042
|
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
12027
12043
|
if (!urlResult.ok) {
|
|
12028
|
-
printError(
|
|
12044
|
+
printError("Not configured yet \u2014 run /gate-setup in Claude Code to enable quality gates.");
|
|
12029
12045
|
process.exit(0);
|
|
12030
12046
|
}
|
|
12031
12047
|
let specs;
|
|
@@ -12093,8 +12109,138 @@ async function runReview(opts, globals) {
|
|
|
12093
12109
|
process.exit(0);
|
|
12094
12110
|
}
|
|
12095
12111
|
|
|
12112
|
+
// src/commands/init.ts
|
|
12113
|
+
var import_node_fs9 = require("node:fs");
|
|
12114
|
+
var import_promises8 = require("node:fs/promises");
|
|
12115
|
+
var import_node_path7 = require("node:path");
|
|
12116
|
+
var import_node_child_process6 = require("node:child_process");
|
|
12117
|
+
function resolveDataDir() {
|
|
12118
|
+
const candidates = [
|
|
12119
|
+
(0, import_node_path7.join)(__dirname, "..", "data"),
|
|
12120
|
+
// installed: node_modules/@codacy/gate-cli/data
|
|
12121
|
+
(0, import_node_path7.join)(__dirname, "..", "..", "data"),
|
|
12122
|
+
// edge case: nested resolution
|
|
12123
|
+
(0, import_node_path7.join)(process.cwd(), "cli", "data")
|
|
12124
|
+
// local dev: running from repo root
|
|
12125
|
+
];
|
|
12126
|
+
for (const candidate of candidates) {
|
|
12127
|
+
if ((0, import_node_fs9.existsSync)((0, import_node_path7.join)(candidate, "skills"))) {
|
|
12128
|
+
return candidate;
|
|
12129
|
+
}
|
|
12130
|
+
}
|
|
12131
|
+
throw new Error(
|
|
12132
|
+
"Could not find GATE.md skill data. Reinstall: npm install -g @codacy/gate-cli"
|
|
12133
|
+
);
|
|
12134
|
+
}
|
|
12135
|
+
async function copyDir(src, dest) {
|
|
12136
|
+
await (0, import_promises8.mkdir)(dest, { recursive: true });
|
|
12137
|
+
await (0, import_promises8.cp)(src, dest, { recursive: true, force: true });
|
|
12138
|
+
}
|
|
12139
|
+
function registerInitCommand(program2) {
|
|
12140
|
+
program2.command("init").description("Initialize GATE.md in the current project").option("--force", "Overwrite existing skills and hooks").action(async (opts) => {
|
|
12141
|
+
const force = opts.force ?? false;
|
|
12142
|
+
const projectMarkers = [".git", "package.json", "pyproject.toml", "go.mod", "Cargo.toml", "Gemfile", "pom.xml", "build.gradle"];
|
|
12143
|
+
const isProject = projectMarkers.some((m) => (0, import_node_fs9.existsSync)(m));
|
|
12144
|
+
if (!isProject) {
|
|
12145
|
+
printError("No project detected in the current directory.");
|
|
12146
|
+
printInfo('Run "gate init" from your project root.');
|
|
12147
|
+
process.exit(1);
|
|
12148
|
+
}
|
|
12149
|
+
console.log("");
|
|
12150
|
+
printInfo("Initializing GATE.md in this project...");
|
|
12151
|
+
console.log("");
|
|
12152
|
+
printInfo("Checking prerequisites...");
|
|
12153
|
+
const nodeVersion = process.version;
|
|
12154
|
+
const nodeMajor = parseInt(nodeVersion.slice(1), 10);
|
|
12155
|
+
if (nodeMajor < 20) {
|
|
12156
|
+
printError(`Node.js 20+ required (found ${nodeVersion}). Update from https://nodejs.org`);
|
|
12157
|
+
process.exit(1);
|
|
12158
|
+
}
|
|
12159
|
+
printInfo(` Node.js ${nodeVersion} \u2713`);
|
|
12160
|
+
try {
|
|
12161
|
+
const gitVersion = (0, import_node_child_process6.execSync)("git --version", { encoding: "utf-8" }).trim();
|
|
12162
|
+
printInfo(` ${gitVersion} \u2713`);
|
|
12163
|
+
} catch {
|
|
12164
|
+
printError("git is required but not installed. Install from https://git-scm.com");
|
|
12165
|
+
process.exit(1);
|
|
12166
|
+
}
|
|
12167
|
+
try {
|
|
12168
|
+
(0, import_node_child_process6.execSync)("which claude", { encoding: "utf-8" });
|
|
12169
|
+
printInfo(" Claude Code \u2713");
|
|
12170
|
+
} catch {
|
|
12171
|
+
printWarn(" Claude Code not found \u2014 hooks will be configured but need Claude Code to run.");
|
|
12172
|
+
}
|
|
12173
|
+
try {
|
|
12174
|
+
(0, import_node_child_process6.execSync)("which codacy-analysis", { encoding: "utf-8" });
|
|
12175
|
+
printInfo(" @codacy/analysis-cli \u2713");
|
|
12176
|
+
} catch {
|
|
12177
|
+
printWarn(" @codacy/analysis-cli not found \u2014 static analysis will be unavailable.");
|
|
12178
|
+
printWarn(" Install: npm install -g @codacy/analysis-cli");
|
|
12179
|
+
}
|
|
12180
|
+
console.log("");
|
|
12181
|
+
printInfo("Installing skills...");
|
|
12182
|
+
const dataDir = resolveDataDir();
|
|
12183
|
+
const skillsSource = (0, import_node_path7.join)(dataDir, "skills");
|
|
12184
|
+
const skillsDest = ".claude/skills";
|
|
12185
|
+
const skills = ["gate-setup", "gate-analyze", "gate-status", "gate-feedback"];
|
|
12186
|
+
let skillsInstalled = 0;
|
|
12187
|
+
for (const skill of skills) {
|
|
12188
|
+
const src = (0, import_node_path7.join)(skillsSource, skill);
|
|
12189
|
+
const dest = (0, import_node_path7.join)(skillsDest, skill);
|
|
12190
|
+
if (!(0, import_node_fs9.existsSync)(src)) {
|
|
12191
|
+
printWarn(` Skill data not found: ${skill}`);
|
|
12192
|
+
continue;
|
|
12193
|
+
}
|
|
12194
|
+
if ((0, import_node_fs9.existsSync)(dest) && !force) {
|
|
12195
|
+
const srcSkill = (0, import_node_path7.join)(src, "SKILL.md");
|
|
12196
|
+
const destSkill = (0, import_node_path7.join)(dest, "SKILL.md");
|
|
12197
|
+
if ((0, import_node_fs9.existsSync)(destSkill)) {
|
|
12198
|
+
try {
|
|
12199
|
+
const srcContent = await (0, import_promises8.readFile)(srcSkill, "utf-8");
|
|
12200
|
+
const destContent = await (0, import_promises8.readFile)(destSkill, "utf-8");
|
|
12201
|
+
if (srcContent === destContent) {
|
|
12202
|
+
skillsInstalled++;
|
|
12203
|
+
continue;
|
|
12204
|
+
}
|
|
12205
|
+
} catch {
|
|
12206
|
+
}
|
|
12207
|
+
}
|
|
12208
|
+
}
|
|
12209
|
+
await copyDir(src, dest);
|
|
12210
|
+
skillsInstalled++;
|
|
12211
|
+
}
|
|
12212
|
+
printInfo(` ${skillsInstalled}/${skills.length} skills installed to .claude/skills/ \u2713`);
|
|
12213
|
+
printInfo("Wiring Claude Code hooks...");
|
|
12214
|
+
const settings = await readSettings();
|
|
12215
|
+
const hookResult = installGateHooks(settings, force);
|
|
12216
|
+
if (hookResult.ok) {
|
|
12217
|
+
await writeSettings(hookResult.data);
|
|
12218
|
+
printInfo(" Stop hook: gate analyze \u2713");
|
|
12219
|
+
printInfo(" Intent hook: gate intent capture \u2713");
|
|
12220
|
+
} else {
|
|
12221
|
+
printWarn(` ${hookResult.error}`);
|
|
12222
|
+
printInfo(' Run "gate hooks install --force" to overwrite.');
|
|
12223
|
+
}
|
|
12224
|
+
await (0, import_promises8.mkdir)(GATE_DIR, { recursive: true });
|
|
12225
|
+
const globalGateDir = (0, import_node_path7.join)(process.env.HOME ?? "", ".gate");
|
|
12226
|
+
await (0, import_promises8.mkdir)(globalGateDir, { recursive: true });
|
|
12227
|
+
console.log("");
|
|
12228
|
+
printInfo("GATE.md initialized!");
|
|
12229
|
+
console.log("");
|
|
12230
|
+
console.log(" Installed:");
|
|
12231
|
+
console.log(" .claude/skills/gate-setup/ \u2014 project configuration");
|
|
12232
|
+
console.log(" .claude/skills/gate-analyze/ \u2014 on-demand analysis");
|
|
12233
|
+
console.log(" .claude/skills/gate-status/ \u2014 project health");
|
|
12234
|
+
console.log(" .claude/skills/gate-feedback/ \u2014 beta feedback");
|
|
12235
|
+
console.log(" .claude/settings.json \u2014 hooks (gate analyze + intent capture)");
|
|
12236
|
+
console.log("");
|
|
12237
|
+
console.log(" Next step: open this project in Claude Code and run /gate-setup");
|
|
12238
|
+
console.log("");
|
|
12239
|
+
});
|
|
12240
|
+
}
|
|
12241
|
+
|
|
12096
12242
|
// src/cli.ts
|
|
12097
|
-
program.name("gate").description("CLI for GATE.md quality gate service").version("0.
|
|
12243
|
+
program.name("gate").description("CLI for GATE.md quality gate service").version("0.4.0").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr");
|
|
12098
12244
|
registerAuthCommands(program);
|
|
12099
12245
|
registerHooksCommands(program);
|
|
12100
12246
|
registerIntentCommands(program);
|
|
@@ -12104,4 +12250,5 @@ registerStatusCommand(program);
|
|
|
12104
12250
|
registerFeedbackCommand(program);
|
|
12105
12251
|
registerAnalyzeCommand(program);
|
|
12106
12252
|
registerReviewCommand(program);
|
|
12253
|
+
registerInitCommand(program);
|
|
12107
12254
|
program.parse();
|