fchek 1.0.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 +64 -0
- package/bin/fchek.js +107 -0
- package/lib/api.js +110 -0
- package/lib/audit.js +211 -0
- package/lib/bench.js +248 -0
- package/lib/config.js +191 -0
- package/lib/context.js +356 -0
- package/lib/convention.js +526 -0
- package/lib/coverage.js +604 -0
- package/lib/db.js +135 -0
- package/lib/deps-check.js +264 -0
- package/lib/deps.js +374 -0
- package/lib/docker.js +84 -0
- package/lib/doctor.js +149 -0
- package/lib/dom.js +226 -0
- package/lib/fuzz.js +470 -0
- package/lib/git.js +290 -0
- package/lib/goto.js +544 -0
- package/lib/launch.js +182 -0
- package/lib/lint.js +624 -0
- package/lib/new_features.test.js +181 -0
- package/lib/output.js +46 -0
- package/lib/port.js +173 -0
- package/lib/process.js +228 -0
- package/lib/profile.js +453 -0
- package/lib/python.js +41 -0
- package/lib/race.js +186 -0
- package/lib/registry.js +179 -0
- package/lib/repl.js +135 -0
- package/lib/run.js +403 -0
- package/lib/screenshot.js +152 -0
- package/lib/secrets.js +257 -0
- package/lib/state.js +219 -0
- package/lib/test.js +471 -0
- package/lib/vuln.js +253 -0
- package/lib/watch.js +240 -0
- package/lib/winlog.js +123 -0
- package/package.json +27 -0
- package/skills/ACTIVATE.md +274 -0
- package/skills/README.md +163 -0
- package/skills/agent.md +444 -0
- package/skills/api.md +47 -0
- package/skills/bench.md +117 -0
- package/skills/context.md +116 -0
- package/skills/convention.md +143 -0
- package/skills/coverage.md +99 -0
- package/skills/csharp.md +97 -0
- package/skills/db.md +66 -0
- package/skills/deps-check.md +135 -0
- package/skills/deps.md +143 -0
- package/skills/docker.md +61 -0
- package/skills/dom.md +56 -0
- package/skills/fuzz.md +167 -0
- package/skills/goto.md +111 -0
- package/skills/lint.md +123 -0
- package/skills/port.md +57 -0
- package/skills/profile.md +91 -0
- package/skills/race.md +117 -0
- package/skills/repl.md +81 -0
- package/skills/rules.md +318 -0
- package/skills/run.md +135 -0
- package/skills/secrets.md +170 -0
- package/skills/security.md +360 -0
- package/skills/state.md +261 -0
- package/skills/vuln.md +57 -0
- package/skills/windows.md +320 -0
package/lib/git.js
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* git.js — safe git operations in JSON
|
|
5
|
+
*
|
|
6
|
+
* Read-only by default. Write operations (stage, commit) require explicit flags.
|
|
7
|
+
* Never pushes, never force-pushes, never deletes branches.
|
|
8
|
+
* Agent uses this to understand repo state and commit completed work.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { spawnSync, execSync } = require('child_process');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const { output, ok, fail } = require('./output');
|
|
15
|
+
|
|
16
|
+
const HELP = `
|
|
17
|
+
fchek git <action> [args...]
|
|
18
|
+
|
|
19
|
+
Safe git operations — JSON output. No push, no force, no branch deletion.
|
|
20
|
+
|
|
21
|
+
Actions:
|
|
22
|
+
status Current branch, staged/unstaged/untracked files
|
|
23
|
+
diff [file] Show unstaged changes (or specific file)
|
|
24
|
+
diff --staged Show staged changes
|
|
25
|
+
log [--n=10] Recent commits
|
|
26
|
+
stage <file...> Stage files for commit (git add)
|
|
27
|
+
unstage <file...> Unstage files (git reset HEAD)
|
|
28
|
+
commit <message> Commit staged changes
|
|
29
|
+
branch List branches
|
|
30
|
+
stash Stash uncommitted changes
|
|
31
|
+
stash pop Restore stashed changes
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
fchek git status
|
|
35
|
+
fchek git diff
|
|
36
|
+
fchek git diff --staged
|
|
37
|
+
fchek git log --n=5
|
|
38
|
+
fchek git stage src/auth.py src/utils.py
|
|
39
|
+
fchek git commit "fix: validate user input in auth"
|
|
40
|
+
`.trim();
|
|
41
|
+
|
|
42
|
+
function gitExists() {
|
|
43
|
+
return commandExists('git');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function commandExists(cmd) {
|
|
47
|
+
try {
|
|
48
|
+
execSync(
|
|
49
|
+
process.platform === 'win32' ? `where ${cmd}` : `which ${cmd}`,
|
|
50
|
+
{ stdio: 'ignore', timeout: 3000 }
|
|
51
|
+
);
|
|
52
|
+
return true;
|
|
53
|
+
} catch { return false; }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isGitRepo(dir) {
|
|
57
|
+
const res = spawnSync('git', ['rev-parse', '--is-inside-work-tree'], {
|
|
58
|
+
encoding: 'utf8', cwd: dir, timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'],
|
|
59
|
+
});
|
|
60
|
+
return res.status === 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function gitCmd(args, cwd, timeoutMs = 15000) {
|
|
64
|
+
return spawnSync('git', args, {
|
|
65
|
+
encoding: 'utf8',
|
|
66
|
+
cwd,
|
|
67
|
+
timeout: timeoutMs,
|
|
68
|
+
windowsHide: true,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ─── Actions ──────────────────────────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
function gitStatus(cwd) {
|
|
75
|
+
const branchRes = gitCmd(['rev-parse', '--abbrev-ref', 'HEAD'], cwd);
|
|
76
|
+
const branch = (branchRes.stdout || '').trim();
|
|
77
|
+
|
|
78
|
+
const statusRes = gitCmd(['status', '--porcelain=v1'], cwd);
|
|
79
|
+
const lines = (statusRes.stdout || '').split('\n').filter(Boolean);
|
|
80
|
+
|
|
81
|
+
const staged = [];
|
|
82
|
+
const unstaged = [];
|
|
83
|
+
const untracked = [];
|
|
84
|
+
const conflicts = [];
|
|
85
|
+
|
|
86
|
+
for (const line of lines) {
|
|
87
|
+
const x = line[0]; // index status
|
|
88
|
+
const y = line[1]; // working tree status
|
|
89
|
+
const file = line.slice(3);
|
|
90
|
+
|
|
91
|
+
if (x === '?' && y === '?') { untracked.push(file); continue; }
|
|
92
|
+
if (x === 'U' || y === 'U') { conflicts.push(file); continue; }
|
|
93
|
+
if (x !== ' ' && x !== '?') staged.push({ status: x, file });
|
|
94
|
+
if (y !== ' ' && y !== '?') unstaged.push({ status: y, file });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Last commit info
|
|
98
|
+
const logRes = gitCmd(['log', '-1', '--format=%H %s %ai'], cwd);
|
|
99
|
+
const logLine = (logRes.stdout || '').trim();
|
|
100
|
+
const lastCommit = logLine ? {
|
|
101
|
+
hash: logLine.slice(0, 8),
|
|
102
|
+
message: logLine.slice(41, logLine.lastIndexOf(' ')).trim(),
|
|
103
|
+
date: logLine.slice(logLine.lastIndexOf(' ') + 1),
|
|
104
|
+
} : null;
|
|
105
|
+
|
|
106
|
+
output(ok({
|
|
107
|
+
branch,
|
|
108
|
+
staged,
|
|
109
|
+
unstaged,
|
|
110
|
+
untracked,
|
|
111
|
+
conflicts,
|
|
112
|
+
is_clean: staged.length === 0 && unstaged.length === 0 && untracked.length === 0,
|
|
113
|
+
last_commit: lastCommit,
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function gitDiff(cwd, file, staged) {
|
|
118
|
+
const args = ['diff'];
|
|
119
|
+
if (staged) args.push('--staged');
|
|
120
|
+
if (file) args.push('--', file);
|
|
121
|
+
args.push('--stat', '--patch');
|
|
122
|
+
|
|
123
|
+
const res = gitCmd(args, cwd, 30000);
|
|
124
|
+
const raw = (res.stdout || '').slice(0, 20000);
|
|
125
|
+
|
|
126
|
+
// Parse stat summary
|
|
127
|
+
const changed = parseInt((raw.match(/(\d+) file(?:s)? changed/) || [0, 0])[1]);
|
|
128
|
+
const inserted = parseInt((raw.match(/(\d+) insertion/) || [0, 0])[1]);
|
|
129
|
+
const deleted = parseInt((raw.match(/(\d+) deletion/) || [0, 0])[1]);
|
|
130
|
+
|
|
131
|
+
// Extract changed files list
|
|
132
|
+
const changedFiles = [];
|
|
133
|
+
for (const m of raw.matchAll(/^diff --git a\/(.+?) b\//gm)) {
|
|
134
|
+
changedFiles.push(m[1]);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
output(ok({
|
|
138
|
+
staged,
|
|
139
|
+
file: file || null,
|
|
140
|
+
files_changed: changed,
|
|
141
|
+
insertions: inserted,
|
|
142
|
+
deletions: deleted,
|
|
143
|
+
changed_files: changedFiles,
|
|
144
|
+
patch: raw,
|
|
145
|
+
}));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function gitLog(cwd, n) {
|
|
149
|
+
const res = gitCmd(['log', `--max-count=${n}`, '--format=%H|%s|%ai|%an'], cwd);
|
|
150
|
+
const commits = (res.stdout || '').trim().split('\n').filter(Boolean).map(line => {
|
|
151
|
+
const parts = line.split('|');
|
|
152
|
+
return {
|
|
153
|
+
hash: parts[0]?.slice(0, 8),
|
|
154
|
+
message: parts[1],
|
|
155
|
+
date: parts[2],
|
|
156
|
+
author: parts[3],
|
|
157
|
+
};
|
|
158
|
+
});
|
|
159
|
+
output(ok({ commits, count: commits.length }));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function gitStage(cwd, files) {
|
|
163
|
+
if (files.length === 0) {
|
|
164
|
+
return output(fail('No files specified. Usage: fchek git stage <file...>'));
|
|
165
|
+
}
|
|
166
|
+
const res = gitCmd(['add', '--', ...files], cwd);
|
|
167
|
+
if (res.status !== 0) {
|
|
168
|
+
return output(fail(`git add failed:\n${res.stderr}`));
|
|
169
|
+
}
|
|
170
|
+
output(ok({ action: 'stage', files, staged: true }));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function gitUnstage(cwd, files) {
|
|
174
|
+
if (files.length === 0) {
|
|
175
|
+
return output(fail('No files specified. Usage: fchek git unstage <file...>'));
|
|
176
|
+
}
|
|
177
|
+
const res = gitCmd(['reset', 'HEAD', '--', ...files], cwd);
|
|
178
|
+
if (res.status !== 0) {
|
|
179
|
+
return output(fail(`git reset failed:\n${res.stderr}`));
|
|
180
|
+
}
|
|
181
|
+
output(ok({ action: 'unstage', files }));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function gitCommit(cwd, message) {
|
|
185
|
+
if (!message) {
|
|
186
|
+
return output(fail('Commit message required. Usage: fchek git commit "<message>"'));
|
|
187
|
+
}
|
|
188
|
+
// Check there is something staged
|
|
189
|
+
const statusRes = gitCmd(['diff', '--staged', '--stat'], cwd);
|
|
190
|
+
if (!(statusRes.stdout || '').trim()) {
|
|
191
|
+
return output(fail('Nothing staged. Use: fchek git stage <files> first'));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const res = gitCmd(['commit', '-m', message], cwd);
|
|
195
|
+
if (res.status !== 0) {
|
|
196
|
+
return output(fail(`git commit failed:\n${res.stderr}`));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const logRes = gitCmd(['log', '-1', '--format=%H %s'], cwd);
|
|
200
|
+
const line = (logRes.stdout || '').trim();
|
|
201
|
+
output(ok({
|
|
202
|
+
action: 'commit',
|
|
203
|
+
hash: line.slice(0, 8),
|
|
204
|
+
message,
|
|
205
|
+
committed: true,
|
|
206
|
+
output: (res.stdout || '').trim(),
|
|
207
|
+
}));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function gitBranch(cwd) {
|
|
211
|
+
const res = gitCmd(['branch', '-a', '--format=%(refname:short)|%(HEAD)'], cwd);
|
|
212
|
+
const branches = (res.stdout || '').trim().split('\n').filter(Boolean).map(line => {
|
|
213
|
+
const [name, current] = line.split('|');
|
|
214
|
+
return { name: name.trim(), current: current === '*' };
|
|
215
|
+
});
|
|
216
|
+
output(ok({ branches }));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function gitStash(cwd, action) {
|
|
220
|
+
if (action === 'pop') {
|
|
221
|
+
const res = gitCmd(['stash', 'pop'], cwd);
|
|
222
|
+
if (res.status !== 0) return output(fail(`git stash pop failed:\n${res.stderr}`));
|
|
223
|
+
output(ok({ action: 'stash_pop', output: (res.stdout || '').trim() }));
|
|
224
|
+
} else {
|
|
225
|
+
const res = gitCmd(['stash'], cwd);
|
|
226
|
+
if (res.status !== 0) return output(fail(`git stash failed:\n${res.stderr}`));
|
|
227
|
+
output(ok({ action: 'stash', output: (res.stdout || '').trim() }));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ─── Entry point ─────────────────────────────────────────────────────────────
|
|
232
|
+
|
|
233
|
+
async function run(args) {
|
|
234
|
+
if (args.length === 0 || args[0] === '--help') { console.log(HELP); return; }
|
|
235
|
+
|
|
236
|
+
if (!gitExists()) {
|
|
237
|
+
return output(fail('git not found. Install: https://git-scm.com'));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const cwd = process.cwd();
|
|
241
|
+
if (!isGitRepo(cwd)) {
|
|
242
|
+
return output(fail(
|
|
243
|
+
'Not a git repository.\n' +
|
|
244
|
+
'Initialize with: git init && git add . && git commit -m "initial"'
|
|
245
|
+
));
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const action = args[0];
|
|
249
|
+
|
|
250
|
+
switch (action) {
|
|
251
|
+
case 'status':
|
|
252
|
+
return gitStatus(cwd);
|
|
253
|
+
|
|
254
|
+
case 'diff': {
|
|
255
|
+
const staged = args.includes('--staged');
|
|
256
|
+
const file = args.find(a => !a.startsWith('--') && a !== 'diff') || null;
|
|
257
|
+
return gitDiff(cwd, file, staged);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
case 'log': {
|
|
261
|
+
const n = parseInt(
|
|
262
|
+
(args.find(a => a.startsWith('--n=')) || '--n=10').replace('--n=', ''), 10
|
|
263
|
+
);
|
|
264
|
+
return gitLog(cwd, n);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
case 'stage':
|
|
268
|
+
return gitStage(cwd, args.slice(1));
|
|
269
|
+
|
|
270
|
+
case 'unstage':
|
|
271
|
+
return gitUnstage(cwd, args.slice(1));
|
|
272
|
+
|
|
273
|
+
case 'commit':
|
|
274
|
+
return gitCommit(cwd, args.slice(1).join(' '));
|
|
275
|
+
|
|
276
|
+
case 'branch':
|
|
277
|
+
return gitBranch(cwd);
|
|
278
|
+
|
|
279
|
+
case 'stash':
|
|
280
|
+
return gitStash(cwd, args[1]);
|
|
281
|
+
|
|
282
|
+
default:
|
|
283
|
+
return output(fail(
|
|
284
|
+
`Unknown git action: "${action}"\n` +
|
|
285
|
+
`Valid: status, diff, log, stage, unstage, commit, branch, stash`
|
|
286
|
+
));
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
module.exports = { run };
|