@kavishdua/proctor 1.4.0 → 1.6.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.
@@ -1,215 +1,20 @@
1
1
  #!/usr/bin/env node
2
-
3
- // src/scripts/postinstall.ts
4
- import { spawnSync } from "child_process";
5
- import { existsSync } from "fs";
6
- import { dirname, join as join2, resolve } from "path";
7
- import { fileURLToPath } from "url";
8
-
9
- // src/adapters/detect.ts
10
- import { stat } from "fs/promises";
11
- import { join } from "path";
12
-
13
- // src/adapters/registry.ts
14
- function cursorMdcTransform(canonical) {
15
- return `---
2
+ import { createRequire as __proctorCreateRequire } from 'node:module'; const require = __proctorCreateRequire(import.meta.url);
3
+ import{spawnSync}from"child_process";import{existsSync}from"fs";import{dirname,join as join2,resolve}from"path";import{fileURLToPath}from"url";import{stat}from"fs/promises";import{join}from"path";function cursorMdcTransform(canonical){return`---
16
4
  description: proctor honest-completion ruleset, catches tests deleted, skipped, weakened, or gamed to fake a passing build
17
5
  globs: '**'
18
6
  alwaysApply: true
19
7
  ---
20
8
 
21
- ${canonical}`;
22
- }
23
- function skillFrontmatterTransform(canonical) {
24
- return `---
9
+ ${canonical}`}function skillFrontmatterTransform(canonical){return`---
25
10
  name: proctor
26
11
  description: Honest-completion ruleset for changes that touch tests or the code they cover. Use before deleting, skipping, renaming, or rewriting a test, before weakening an assertion, and before hardcoding or stubbing an implementation to make a test pass. Also covers what to do when a test looks genuinely wrong.
27
12
  ---
28
13
 
29
- ${canonical}`;
30
- }
31
- function copilotApplyToTransform(canonical) {
32
- return `---
14
+ ${canonical}`}function copilotApplyToTransform(canonical){return`---
33
15
  applyTo: '**'
34
16
  ---
35
17
 
36
- ${canonical}`;
37
- }
38
- var AGENT_ADAPTERS = [
39
- // Claude Code gets skill frontmatter via skillFrontmatterTransform so the ruleset carries an explicit
40
- // description and loads when it is actually relevant.
41
- { id: "claude-code", displayName: "Claude Code", relativePath: ".claude/skills/proctor/SKILL.md", scriptable: true, transform: skillFrontmatterTransform, detect: [".claude", "CLAUDE.md"] },
42
- // Codex reads skills from `.agents/skills/` and REQUIRES `name` and `description` frontmatter;
43
- // without it the skill is not recognized, so this path needs the same transform Claude Code uses.
44
- { id: "codex", displayName: "Codex CLI", relativePath: ".agents/skills/proctor/SKILL.md", scriptable: true, transform: skillFrontmatterTransform, detect: [".codex", ".agents/skills"] },
45
- // Cursor gets .mdc YAML frontmatter (description/globs/alwaysApply) via cursorMdcTransform
46
- // so the rule reliably auto-attaches.
47
- { id: "cursor", displayName: "Cursor", relativePath: ".cursor/rules/proctor.mdc", scriptable: true, transform: cursorMdcTransform, detect: [".cursor", ".cursorrules"] },
48
- // Windsurf's rules.md is the workspace rules file users write their own rules into, not a
49
- // proctor-owned path, so it merges rather than overwrites.
50
- { id: "windsurf", displayName: "Windsurf", relativePath: ".windsurf/rules/rules.md", scriptable: false, shared: true, detect: [".windsurf", ".windsurfrules"] },
51
- { id: "gemini-cli", displayName: "Gemini CLI", relativePath: "GEMINI.md", scriptable: true, shared: true, detect: [".gemini"] },
52
- { id: "aider", displayName: "Aider", relativePath: "CONVENTIONS.md", scriptable: true, shared: true, detect: [".aider.conf.yml", ".aider.conf.yaml"] },
53
- { id: "continue", displayName: "Continue.dev", relativePath: ".continue/rules/proctor.md", scriptable: true, detect: [".continue"] },
54
- { id: "cline", displayName: "Cline", relativePath: ".clinerules/proctor.md", scriptable: true, detect: [".clinerules"] },
55
- // Amazon Q Developer CLI is mid-transition to a closed-source "Kiro CLI", so treat this path
56
- // as unstable: adapter only, no bench runner wired up.
57
- { id: "amazon-q", displayName: "Amazon Q Developer", relativePath: ".amazonq/rules/proctor.md", scriptable: false, detect: [".amazonq"] },
58
- // GitHub Copilot gets `applyTo: '**'` frontmatter via copilotApplyToTransform so the scoped
59
- // instructions file actually activates.
60
- // Detection deliberately does not treat a bare `.github/` as Copilot: nearly every repo has one.
61
- { id: "github-copilot", displayName: "GitHub Copilot", relativePath: ".github/instructions/proctor.instructions.md", scriptable: false, transform: copilotApplyToTransform, detect: [".github/instructions", ".github/copilot-instructions.md"] },
62
- { id: "zed", displayName: "Zed", relativePath: ".rules", scriptable: false, shared: true, detect: [".zed"] },
63
- // Universal cross-vendor AGENTS.md standard (Linux Foundation-stewarded). Also covers Codex
64
- // CLI's actual documented convention (developers.openai.com/codex/guides/agents-md) for free,
65
- // the existing `codex` entry above deliberately keeps its original `.agents/skills/` path.
66
- { id: "agents-md", displayName: "AGENTS.md (universal)", relativePath: "AGENTS.md", scriptable: false, shared: true },
67
- // OpenHands' repo microagent is the one repo-wide instructions file, user-authored.
68
- { id: "openhands", displayName: "OpenHands", relativePath: ".openhands/microagents/repo.md", scriptable: true, shared: true, detect: [".openhands"] },
69
- // Kiro (AWS agentic IDE / Kiro CLI) intentionally coexists with the `amazon-q` entry above,
70
- // they are two separately-installed products today. Revisit consolidation if the
71
- // Amazon Q to Kiro merge ever completes.
72
- { id: "kiro", displayName: "Kiro", relativePath: ".kiro/steering/proctor.md", scriptable: true, detect: [".kiro"] },
73
- { id: "tabnine", displayName: "Tabnine", relativePath: ".tabnine/guidelines/proctor.md", scriptable: true, detect: [".tabnine"] },
74
- { id: "trae", displayName: "Trae", relativePath: ".trae/rules/proctor.md", scriptable: false, detect: [".trae"] },
75
- { id: "github-copilot-global", displayName: "GitHub Copilot (global)", relativePath: ".github/copilot-instructions.md", scriptable: false, shared: true, detect: [".github/instructions"] },
76
- // Qodo's canonical path is a generic, un-namespaced repo-root filename that will often already
77
- // hold unrelated user content, so it merges into a managed block instead of overwriting.
78
- { id: "qodo", displayName: "Qodo", relativePath: "best_practices.md", scriptable: true, shared: true, detect: [".qodo"] },
79
- // --- Agents added after the first roster, all with a stable file-based repo convention ---
80
- // Roo Code reads every file under .roo/rules/ recursively, so a proctor-owned file drops in
81
- // cleanly alongside whatever else the user keeps there.
82
- { id: "roo-code", displayName: "Roo Code", relativePath: ".roo/rules/proctor.md", scriptable: false, detect: [".roo", ".roomodes"] },
83
- // Kilo Code v7 moved config to kilo.jsonc but still auto-includes .kilocode/rules/ for
84
- // backward compatibility, which is the only path proctor can write without editing user config.
85
- { id: "kilo-code", displayName: "Kilo Code", relativePath: ".kilocode/rules/proctor.md", scriptable: true, detect: [".kilocode", "kilo.jsonc"] },
86
- { id: "augment", displayName: "Augment Code", relativePath: ".augment/rules/proctor.md", scriptable: true, detect: [".augment"] },
87
- // Google Antigravity defaults to .agents/rules/ (with .agent/rules/ still read as a fallback).
88
- // Distinct subdirectory from the `codex` entry's .agents/skills/, so the two never collide.
89
- { id: "antigravity", displayName: "Google Antigravity", relativePath: ".agents/rules/proctor.md", scriptable: false, detect: [".agents/rules", ".agent/rules"] },
90
- // goose reads a single .goosehints file per directory, user-authored.
91
- { id: "goose", displayName: "goose", relativePath: ".goosehints", scriptable: true, shared: true },
92
- // Junie now prefers AGENTS.md (covered above) but still reads .junie/guidelines.md, which
93
- // keeps older Junie installs covered.
94
- { id: "junie", displayName: "JetBrains Junie", relativePath: ".junie/guidelines.md", scriptable: true, shared: true, detect: [".junie"] },
95
- { id: "qwen-code", displayName: "Qwen Code", relativePath: "QWEN.md", scriptable: true, shared: true, detect: [".qwen"] },
96
- // Crush reads CRUSH.md for Crush-specific rules in addition to AGENTS.md.
97
- { id: "crush", displayName: "Crush", relativePath: "CRUSH.md", scriptable: true, shared: true, detect: [".crush"] },
98
- // Warp defaults to AGENTS.md now, but WARP.md still takes priority when both exist, so the
99
- // ruleset has to be in both for Warp users who already have a WARP.md.
100
- { id: "warp", displayName: "Warp", relativePath: "WARP.md", scriptable: false, shared: true },
101
- // Amp's own file is AGENT.md, singular, which is a different file from the universal AGENTS.md.
102
- { id: "amp", displayName: "Amp", relativePath: "AGENT.md", scriptable: true, shared: true },
103
- // Firebase Studio generates .idx/airules.md with its own default rules, so merge rather than
104
- // replace them.
105
- { id: "firebase-studio", displayName: "Firebase Studio", relativePath: ".idx/airules.md", scriptable: false, shared: true, detect: [".idx"] },
106
- // Replit Agent reads AGENTS.md as well, but replit.md is its native file and is auto-generated.
107
- { id: "replit", displayName: "Replit Agent", relativePath: "replit.md", scriptable: false, shared: true, detect: [".replit"] }
108
- ];
109
-
110
- // src/adapters/detect.ts
111
- var FALLBACK_ADAPTER_ID = "agents-md";
112
- async function exists(cwd, relativePath) {
113
- try {
114
- await stat(join(cwd, relativePath));
115
- return true;
116
- } catch {
117
- return false;
118
- }
119
- }
120
- async function detectAgents(cwd) {
121
- const detected = await Promise.all(
122
- AGENT_ADAPTERS.map(async (adapter) => {
123
- for (const marker of [adapter.relativePath, ...adapter.detect ?? []]) {
124
- if (await exists(cwd, marker)) return adapter;
125
- }
126
- return void 0;
127
- })
128
- );
129
- const found = detected.filter((a) => a !== void 0);
130
- if (found.length > 0) return found;
131
- const fallback = AGENT_ADAPTERS.find((a) => a.id === FALLBACK_ADAPTER_ID);
132
- return fallback ? [fallback] : [];
133
- }
134
-
135
- // src/scripts/postinstall.ts
136
- var projectDir = process.env.INIT_CWD;
137
- function packageRoot() {
138
- let dir = fileURLToPath(new URL(".", import.meta.url));
139
- for (let i = 0; i < 6; i++) {
140
- if (existsSync(join2(dir, "package.json"))) return dir;
141
- const parent = dirname(dir);
142
- if (parent === dir) break;
143
- dir = parent;
144
- }
145
- return dir;
146
- }
147
- function skipReason(root) {
148
- if (process.env.PROCTOR_NO_POSTINSTALL) return "PROCTOR_NO_POSTINSTALL is set";
149
- if (process.env.CI) return "running in CI, where the checkout is disposable";
150
- if (process.env.npm_config_global === "true") return "this is a global install, with no project to set up";
151
- if (!projectDir) return "INIT_CWD is not set, so the target project is unknown";
152
- const target = resolve(projectDir);
153
- if (target === resolve(root)) return "this is proctor\u2019s own repository";
154
- if (target.split(/[\\/]/).includes("node_modules")) return "installed as a transitive dependency";
155
- if (target.includes("_npx") || target.includes(".npm/_cacache")) return "running via npx, not installing into a project";
156
- if (!existsSync(join2(target, ".git"))) return "no git repository here, so there is no commit to guard";
157
- return void 0;
158
- }
159
- async function printNotice(target) {
160
- let agentPaths = [];
161
- try {
162
- agentPaths = (await detectAgents(target)).map((a) => a.relativePath);
163
- } catch {
164
- }
165
- const lines = [
166
- "",
167
- "proctor is installed. It has written nothing to your repository.",
168
- "",
169
- " npx proctor setup",
170
- "",
171
- "That one command would write, and nothing else:",
172
- agentPaths.length > 0 ? ` - the honest-completion ruleset to ${agentPaths.length} agent path${agentPaths.length === 1 ? "" : "s"}: ${agentPaths.join(", ")}` : " - the honest-completion ruleset to the agent paths this repo already uses (AGENTS.md if none are detected)",
173
- " - a git pre-commit hook at .git/hooks/pre-commit (or the configured hook path / .husky/pre-commit); a foreign hook is backed up, then replaced",
174
- " - a Claude Code Stop hook in .claude/settings.json, only if this repo uses Claude Code",
175
- " - .proctor-adapter-manifest.json, recording the deployed ruleset paths",
176
- "",
177
- "No network call is made. Git may configure a hook path outside the working tree.",
178
- "Run `npx proctor check` first if you want to see what it finds before wiring anything up.",
179
- "Set PROCTOR_AUTO_SETUP=1 to have future installs run setup for you.",
180
- ""
181
- ];
182
- process.stdout.write(lines.join("\n"));
183
- }
184
- async function main() {
185
- const root = packageRoot();
186
- const reason = skipReason(root);
187
- if (reason) {
188
- if (process.env.PROCTOR_DEBUG) {
189
- process.stdout.write(`proctor: postinstall did nothing (${reason}).
190
- `);
191
- }
192
- return;
193
- }
194
- const target = projectDir;
195
- if (!process.env.PROCTOR_AUTO_SETUP) {
196
- await printNotice(target);
197
- return;
198
- }
199
- const cli = join2(root, "dist", "cli.js");
200
- if (!existsSync(cli)) {
201
- process.stdout.write("proctor: build output not found, skipping automatic setup. Run `npx proctor setup`.\n");
202
- return;
203
- }
204
- const result = spawnSync(process.execPath, [cli, "setup"], {
205
- cwd: target,
206
- stdio: "inherit"
207
- });
208
- if (result.status !== 0) {
209
- process.stdout.write("proctor: automatic setup did not finish cleanly. Run `npx proctor setup` to see why.\n");
210
- }
211
- }
212
- main().catch((err) => {
213
- process.stdout.write(`proctor: postinstall notice could not be printed (${String(err)}). Run \`npx proctor setup\` to install the guards.
214
- `);
215
- });
18
+ ${canonical}`}var AGENT_ADAPTERS=[{id:"claude-code",displayName:"Claude Code",relativePath:".claude/skills/proctor/SKILL.md",scriptable:true,transform:skillFrontmatterTransform,detect:[".claude","CLAUDE.md"]},{id:"codex",displayName:"Codex CLI",relativePath:".agents/skills/proctor/SKILL.md",scriptable:true,transform:skillFrontmatterTransform,detect:[".codex",".agents/skills"]},{id:"cursor",displayName:"Cursor",relativePath:".cursor/rules/proctor.mdc",scriptable:true,transform:cursorMdcTransform,detect:[".cursor",".cursorrules"]},{id:"windsurf",displayName:"Windsurf",relativePath:".windsurf/rules/rules.md",scriptable:false,shared:true,detect:[".windsurf",".windsurfrules"]},{id:"gemini-cli",displayName:"Gemini CLI",relativePath:"GEMINI.md",scriptable:true,shared:true,detect:[".gemini"],experimental:"consumer Gemini CLI auth is retired by Google (Antigravity CLI is the supported successor) and its AfterAgent hook is contract-verified only, never live-verified"},{id:"aider",displayName:"Aider",relativePath:"CONVENTIONS.md",scriptable:true,shared:true,detect:[".aider.conf.yml",".aider.conf.yaml"]},{id:"continue",displayName:"Continue.dev",relativePath:".continue/rules/proctor.md",scriptable:true,detect:[".continue"]},{id:"cline",displayName:"Cline",relativePath:".clinerules/proctor.md",scriptable:true,detect:[".clinerules"]},{id:"amazon-q",displayName:"Amazon Q Developer",relativePath:".amazonq/rules/proctor.md",scriptable:false,detect:[".amazonq"]},{id:"github-copilot",displayName:"GitHub Copilot",relativePath:".github/instructions/proctor.instructions.md",scriptable:false,transform:copilotApplyToTransform,detect:[".github/instructions",".github/copilot-instructions.md"]},{id:"zed",displayName:"Zed",relativePath:".rules",scriptable:false,shared:true,detect:[".zed"]},{id:"agents-md",displayName:"AGENTS.md (universal)",relativePath:"AGENTS.md",scriptable:false,shared:true},{id:"openhands",displayName:"OpenHands",relativePath:".openhands/microagents/repo.md",scriptable:true,shared:true,detect:[".openhands"]},{id:"kiro",displayName:"Kiro",relativePath:".kiro/steering/proctor.md",scriptable:true,detect:[".kiro"]},{id:"tabnine",displayName:"Tabnine",relativePath:".tabnine/guidelines/proctor.md",scriptable:true,detect:[".tabnine"]},{id:"trae",displayName:"Trae",relativePath:".trae/rules/proctor.md",scriptable:false,detect:[".trae"]},{id:"github-copilot-global",displayName:"GitHub Copilot (global)",relativePath:".github/copilot-instructions.md",scriptable:false,shared:true,detect:[".github/instructions"]},{id:"qodo",displayName:"Qodo",relativePath:"best_practices.md",scriptable:true,shared:true,detect:[".qodo"]},{id:"roo-code",displayName:"Roo Code",relativePath:".roo/rules/proctor.md",scriptable:false,detect:[".roo",".roomodes"]},{id:"kilo-code",displayName:"Kilo Code",relativePath:".kilocode/rules/proctor.md",scriptable:true,detect:[".kilocode","kilo.jsonc"]},{id:"augment",displayName:"Augment Code",relativePath:".augment/rules/proctor.md",scriptable:true,detect:[".augment"]},{id:"antigravity",displayName:"Google Antigravity",relativePath:".agents/rules/proctor.md",scriptable:false,detect:[".agents/rules",".agent/rules"]},{id:"goose",displayName:"goose",relativePath:".goosehints",scriptable:true,shared:true},{id:"junie",displayName:"JetBrains Junie",relativePath:".junie/guidelines.md",scriptable:true,shared:true,detect:[".junie"]},{id:"qwen-code",displayName:"Qwen Code",relativePath:"QWEN.md",scriptable:true,shared:true,detect:[".qwen"]},{id:"crush",displayName:"Crush",relativePath:"CRUSH.md",scriptable:true,shared:true,detect:[".crush"]},{id:"warp",displayName:"Warp",relativePath:"WARP.md",scriptable:false,shared:true},{id:"amp",displayName:"Amp",relativePath:"AGENT.md",scriptable:true,shared:true},{id:"firebase-studio",displayName:"Firebase Studio",relativePath:".idx/airules.md",scriptable:false,shared:true,detect:[".idx"]},{id:"replit",displayName:"Replit Agent",relativePath:"replit.md",scriptable:false,shared:true,detect:[".replit"]}];var FALLBACK_ADAPTER_ID="agents-md";async function exists(cwd,relativePath){try{await stat(join(cwd,relativePath));return true}catch{return false}}async function detectAgents(cwd){const detected=await Promise.all(AGENT_ADAPTERS.map(async adapter=>{for(const marker of[adapter.relativePath,...adapter.detect??[]]){if(await exists(cwd,marker))return adapter}return void 0}));const found=detected.filter(a=>a!==void 0);if(found.length>0)return found;const fallback=AGENT_ADAPTERS.find(a=>a.id===FALLBACK_ADAPTER_ID);return fallback?[fallback]:[]}var projectDir=process.env.INIT_CWD;function packageRoot(){let dir=fileURLToPath(new URL(".",import.meta.url));for(let i=0;i<6;i++){if(existsSync(join2(dir,"package.json")))return dir;const parent=dirname(dir);if(parent===dir)break;dir=parent}return dir}function skipReason(root){if(process.env.PROCTOR_NO_POSTINSTALL)return"PROCTOR_NO_POSTINSTALL is set";if(process.env.CI)return"running in CI, where the checkout is disposable";if(process.env.npm_config_global==="true")return"this is a global install, with no project to set up";if(!projectDir)return"INIT_CWD is not set, so the target project is unknown";const target=resolve(projectDir);if(target===resolve(root))return"this is proctor\u2019s own repository";if(target.split(/[\\/]/).includes("node_modules"))return"installed as a transitive dependency";if(target.includes("_npx")||target.includes(".npm/_cacache"))return"running via npx, not installing into a project";if(!existsSync(join2(target,".git")))return"no git repository here, so there is no commit to guard";return void 0}async function printNotice(target){let agentPaths=[];try{agentPaths=(await detectAgents(target)).map(a=>a.relativePath)}catch{}const lines=["","proctor is installed. It has written nothing to your repository.",""," npx proctor setup","","That one command would write, and nothing else:",agentPaths.length>0?` - the honest-completion ruleset to ${agentPaths.length} agent path${agentPaths.length===1?"":"s"}: ${agentPaths.join(", ")}`:" - the honest-completion ruleset to the agent paths this repo already uses (AGENTS.md if none are detected)"," - a git pre-commit hook at .git/hooks/pre-commit (or the configured hook path / .husky/pre-commit); a foreign hook is backed up, then replaced"," - a Claude Code Stop hook in .claude/settings.json, only if this repo uses Claude Code"," - .proctor-adapter-manifest.json, recording the deployed ruleset paths","","No network call is made. Git may configure a hook path outside the working tree.","Run `npx proctor check` first if you want to see what it finds before wiring anything up.","Set PROCTOR_AUTO_SETUP=1 to have future installs run setup for you.",""];process.stdout.write(lines.join("\n"))}async function main(){const root=packageRoot();const reason=skipReason(root);if(reason){if(process.env.PROCTOR_DEBUG){process.stdout.write(`proctor: postinstall did nothing (${reason}).
19
+ `)}return}const target=projectDir;if(!process.env.PROCTOR_AUTO_SETUP){await printNotice(target);return}const cli=join2(root,"dist","cli.js");if(!existsSync(cli)){process.stdout.write("proctor: build output not found, skipping automatic setup. Run `npx proctor setup`.\n");return}const result=spawnSync(process.execPath,[cli,"setup"],{cwd:target,stdio:"inherit"});if(result.status!==0){process.stdout.write("proctor: automatic setup did not finish cleanly. Run `npx proctor setup` to see why.\n")}}main().catch(err=>{process.stdout.write(`proctor: postinstall notice could not be printed (${String(err)}). Run \`npx proctor setup\` to install the guards.
20
+ `)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kavishdua/proctor",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "description": "Catch AI agents gaming their test suites: deleted tests, skipped tests, weakened assertions, hardcoded outputs",
5
5
  "homepage": "https://github.com/catfish-1234/proctor#readme",
6
6
  "bugs": {
@@ -75,7 +75,7 @@
75
75
  "oxlint": "^1.88.0",
76
76
  "tsup": "8.5.1",
77
77
  "typescript": "^7.0.2",
78
- "vitest": "^4.1.11"
78
+ "vitest": "^4.1.12"
79
79
  },
80
80
  "overrides": {
81
81
  "esbuild": "^0.28.1"