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.
Files changed (66) hide show
  1. package/README.md +64 -0
  2. package/bin/fchek.js +107 -0
  3. package/lib/api.js +110 -0
  4. package/lib/audit.js +211 -0
  5. package/lib/bench.js +248 -0
  6. package/lib/config.js +191 -0
  7. package/lib/context.js +356 -0
  8. package/lib/convention.js +526 -0
  9. package/lib/coverage.js +604 -0
  10. package/lib/db.js +135 -0
  11. package/lib/deps-check.js +264 -0
  12. package/lib/deps.js +374 -0
  13. package/lib/docker.js +84 -0
  14. package/lib/doctor.js +149 -0
  15. package/lib/dom.js +226 -0
  16. package/lib/fuzz.js +470 -0
  17. package/lib/git.js +290 -0
  18. package/lib/goto.js +544 -0
  19. package/lib/launch.js +182 -0
  20. package/lib/lint.js +624 -0
  21. package/lib/new_features.test.js +181 -0
  22. package/lib/output.js +46 -0
  23. package/lib/port.js +173 -0
  24. package/lib/process.js +228 -0
  25. package/lib/profile.js +453 -0
  26. package/lib/python.js +41 -0
  27. package/lib/race.js +186 -0
  28. package/lib/registry.js +179 -0
  29. package/lib/repl.js +135 -0
  30. package/lib/run.js +403 -0
  31. package/lib/screenshot.js +152 -0
  32. package/lib/secrets.js +257 -0
  33. package/lib/state.js +219 -0
  34. package/lib/test.js +471 -0
  35. package/lib/vuln.js +253 -0
  36. package/lib/watch.js +240 -0
  37. package/lib/winlog.js +123 -0
  38. package/package.json +27 -0
  39. package/skills/ACTIVATE.md +274 -0
  40. package/skills/README.md +163 -0
  41. package/skills/agent.md +444 -0
  42. package/skills/api.md +47 -0
  43. package/skills/bench.md +117 -0
  44. package/skills/context.md +116 -0
  45. package/skills/convention.md +143 -0
  46. package/skills/coverage.md +99 -0
  47. package/skills/csharp.md +97 -0
  48. package/skills/db.md +66 -0
  49. package/skills/deps-check.md +135 -0
  50. package/skills/deps.md +143 -0
  51. package/skills/docker.md +61 -0
  52. package/skills/dom.md +56 -0
  53. package/skills/fuzz.md +167 -0
  54. package/skills/goto.md +111 -0
  55. package/skills/lint.md +123 -0
  56. package/skills/port.md +57 -0
  57. package/skills/profile.md +91 -0
  58. package/skills/race.md +117 -0
  59. package/skills/repl.md +81 -0
  60. package/skills/rules.md +318 -0
  61. package/skills/run.md +135 -0
  62. package/skills/secrets.md +170 -0
  63. package/skills/security.md +360 -0
  64. package/skills/state.md +261 -0
  65. package/skills/vuln.md +57 -0
  66. 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 };