@hiro-c/agent-gate 1.5.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.
- package/README.md +1 -0
- package/dist/cli/agent-gate.d.ts.map +1 -1
- package/dist/cli/agent-gate.js +17 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/observability/suggest.d.ts +20 -0
- package/dist/observability/suggest.d.ts.map +1 -0
- package/dist/observability/suggest.js +125 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -76,6 +76,7 @@ Full options: see [docs/config.md](docs/config.md) (TODO) or `AgentGatePluginCon
|
|
|
76
76
|
| `agent-gate install` / `uninstall` | Register or remove the Claude Code hook |
|
|
77
77
|
| `agent-gate lint [--ai]` | Audit instruction files for ambiguity, emptiness, missing rules. `--ai` adds AI-driven contradiction / ambiguity / missing-imperative checks |
|
|
78
78
|
| `agent-gate stats` | Summarize the decision log (after `AGENT_GATE_LOG=1`) |
|
|
79
|
+
| `agent-gate suggest` | Surface rule candidates from repeated AI blocks and stale built-in rules |
|
|
79
80
|
| `agent-gate daemon` | Long-lived server on a Unix socket (opt-in speedup, set `AGENT_GATE_DAEMON=1`) |
|
|
80
81
|
|
|
81
82
|
## Environment
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-gate.d.ts","sourceRoot":"","sources":["../../src/cli/agent-gate.ts"],"names":[],"mappings":";AAIA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAA;AAYtE,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"agent-gate.d.ts","sourceRoot":"","sources":["../../src/cli/agent-gate.ts"],"names":[],"mappings":";AAIA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAA;AAYtE,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AA+C7C,wBAAsB,GAAG,CACvB,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,OAAO,GAChB,OAAO,CAAC,gBAAgB,CAAC,CAE3B;AAuHD,UAAU,UAAU;IAClB,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,OAAO,CAAA;IACjB,WAAW,EAAE,OAAO,CAAA;IACpB,EAAE,EAAE,OAAO,CAAA;CACZ;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CAqCpD"}
|
package/dist/cli/agent-gate.js
CHANGED
|
@@ -8,7 +8,9 @@ const processHookData_1 = require("../hooks/processHookData");
|
|
|
8
8
|
const installer_1 = require("./installer");
|
|
9
9
|
const adapters_1 = require("../adapters");
|
|
10
10
|
const stats_1 = require("../observability/stats");
|
|
11
|
+
const suggest_1 = require("../observability/suggest");
|
|
11
12
|
const decisionLogger_1 = require("../observability/decisionLogger");
|
|
13
|
+
const defaultRules_1 = require("../deterministic/defaultRules");
|
|
12
14
|
const collectRuleSources_1 = require("../collector/collectRuleSources");
|
|
13
15
|
const lintRuleSources_1 = require("../doctor/lintRuleSources");
|
|
14
16
|
const lintRuleSourcesWithAi_1 = require("../doctor/lintRuleSourcesWithAi");
|
|
@@ -28,6 +30,7 @@ Usage:
|
|
|
28
30
|
agent-gate install Register the hook in ~/.claude/settings.json
|
|
29
31
|
agent-gate uninstall Remove the hook from ~/.claude/settings.json
|
|
30
32
|
agent-gate stats Summarize decisions from the log file
|
|
33
|
+
agent-gate suggest Surface rule candidates and stale rules from the decision log
|
|
31
34
|
agent-gate lint [--ai] Audit CLAUDE.md / AGENTS.md / etc. for AI-friendliness
|
|
32
35
|
(--ai adds AI-driven contradiction / ambiguity / missing-imperative checks)
|
|
33
36
|
agent-gate daemon Start the long-lived daemon (Unix socket)
|
|
@@ -212,6 +215,9 @@ function main() {
|
|
|
212
215
|
case 'stats':
|
|
213
216
|
runStats();
|
|
214
217
|
return;
|
|
218
|
+
case 'suggest':
|
|
219
|
+
runSuggest();
|
|
220
|
+
return;
|
|
215
221
|
case 'lint':
|
|
216
222
|
void runLint(parsedArgs.ai);
|
|
217
223
|
return;
|
|
@@ -228,6 +234,17 @@ function runStats() {
|
|
|
228
234
|
const stats = (0, stats_1.readStats)((0, decisionLogger_1.defaultLogPath)());
|
|
229
235
|
console.log((0, stats_1.formatStats)(stats));
|
|
230
236
|
}
|
|
237
|
+
function runSuggest() {
|
|
238
|
+
const windowDays = parseInt(process.env.AGENT_GATE_SUGGEST_WINDOW_DAYS ?? '7', 10);
|
|
239
|
+
const minPatternCount = parseInt(process.env.AGENT_GATE_SUGGEST_MIN_COUNT ?? '3', 10);
|
|
240
|
+
const knownRuleIds = defaultRules_1.defaultDeterministicRules.map((r) => r.id);
|
|
241
|
+
const suggestions = (0, suggest_1.suggestRules)((0, decisionLogger_1.defaultLogPath)(), {
|
|
242
|
+
windowDays: Number.isNaN(windowDays) ? 7 : windowDays,
|
|
243
|
+
minPatternCount: Number.isNaN(minPatternCount) ? 3 : minPatternCount,
|
|
244
|
+
knownRuleIds,
|
|
245
|
+
});
|
|
246
|
+
console.log((0, suggest_1.formatSuggestions)(suggestions));
|
|
247
|
+
}
|
|
231
248
|
async function runLint(useAi) {
|
|
232
249
|
const cwd = process.cwd();
|
|
233
250
|
const sources = (0, collectRuleSources_1.collectRuleSources)(cwd);
|
package/dist/index.d.ts
CHANGED
|
@@ -34,6 +34,8 @@ export { sendToDaemon } from './daemon/client';
|
|
|
34
34
|
export type { SendToDaemonOptions } from './daemon/client';
|
|
35
35
|
export { defaultSocketPath as defaultDaemonSocketPath, } from './daemon/protocol';
|
|
36
36
|
export type { DaemonRequest, DaemonResponse, DaemonErrorResponse, } from './daemon/protocol';
|
|
37
|
+
export { suggestRules, formatSuggestions } from './observability/suggest';
|
|
38
|
+
export type { Suggestion, SuggestionKind, SuggestOptions, } from './observability/suggest';
|
|
37
39
|
export { EventBus } from './observability/eventBus';
|
|
38
40
|
export { JsonlFileSink } from './observability/sinks/JsonlFileSink';
|
|
39
41
|
export type { PipelineEvent, RuleFiredEvent, AiRequestedEvent, AiCompletedEvent, VerdictDecidedEvent, PipelineErrorEvent, Sink, } from './observability/sinks/Sink';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.processHookData = exports.validator = exports.collectRuleSources = exports.JsonlFileSink = exports.EventBus = exports.defaultDaemonSocketPath = exports.sendToDaemon = exports.DaemonServer = exports.DecisionCache = exports.formatFindings = exports.lintRuleSourcesWithAi = exports.lintRuleSources = exports.AgentSdkClient = exports.CompositeModelClient = exports.cursorAdapter = exports.claudeCodeAdapter = exports.buildDefaultDeterministicRules = exports.defaultDeterministicRules = exports.forbidFilePathPattern = exports.forbidContentPattern = exports.forbidCommandPattern = exports.HookDataSchema = exports.loadPluginConfig = exports.loadAgentGateConfig = exports.defineConfig = exports.Config = void 0;
|
|
3
|
+
exports.processHookData = exports.validator = exports.collectRuleSources = exports.JsonlFileSink = exports.EventBus = exports.formatSuggestions = exports.suggestRules = exports.defaultDaemonSocketPath = exports.sendToDaemon = exports.DaemonServer = exports.DecisionCache = exports.formatFindings = exports.lintRuleSourcesWithAi = exports.lintRuleSources = exports.AgentSdkClient = exports.CompositeModelClient = exports.cursorAdapter = exports.claudeCodeAdapter = exports.buildDefaultDeterministicRules = exports.defaultDeterministicRules = exports.forbidFilePathPattern = exports.forbidContentPattern = exports.forbidCommandPattern = exports.HookDataSchema = exports.loadPluginConfig = exports.loadAgentGateConfig = exports.defineConfig = exports.Config = void 0;
|
|
4
4
|
// Config
|
|
5
5
|
var Config_1 = require("./config/Config");
|
|
6
6
|
Object.defineProperty(exports, "Config", { enumerable: true, get: function () { return Config_1.Config; } });
|
|
@@ -48,6 +48,9 @@ Object.defineProperty(exports, "sendToDaemon", { enumerable: true, get: function
|
|
|
48
48
|
var protocol_1 = require("./daemon/protocol");
|
|
49
49
|
Object.defineProperty(exports, "defaultDaemonSocketPath", { enumerable: true, get: function () { return protocol_1.defaultSocketPath; } });
|
|
50
50
|
// Observability
|
|
51
|
+
var suggest_1 = require("./observability/suggest");
|
|
52
|
+
Object.defineProperty(exports, "suggestRules", { enumerable: true, get: function () { return suggest_1.suggestRules; } });
|
|
53
|
+
Object.defineProperty(exports, "formatSuggestions", { enumerable: true, get: function () { return suggest_1.formatSuggestions; } });
|
|
51
54
|
var eventBus_1 = require("./observability/eventBus");
|
|
52
55
|
Object.defineProperty(exports, "EventBus", { enumerable: true, get: function () { return eventBus_1.EventBus; } });
|
|
53
56
|
var JsonlFileSink_1 = require("./observability/sinks/JsonlFileSink");
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type SuggestionKind = 'add-rule' | 'disable-rule' | 'maintain';
|
|
2
|
+
export interface Suggestion {
|
|
3
|
+
kind: SuggestionKind;
|
|
4
|
+
toolName?: string;
|
|
5
|
+
reasonExcerpt?: string;
|
|
6
|
+
ruleId?: string;
|
|
7
|
+
count: number;
|
|
8
|
+
message: string;
|
|
9
|
+
}
|
|
10
|
+
export interface SuggestOptions {
|
|
11
|
+
/** Only consider log entries within this many days. */
|
|
12
|
+
windowDays: number;
|
|
13
|
+
/** Minimum repetitions before suggesting an add-rule. Default 3. */
|
|
14
|
+
minPatternCount?: number;
|
|
15
|
+
/** Known deterministic rule ids to evaluate for stale-rule suggestions. */
|
|
16
|
+
knownRuleIds?: string[];
|
|
17
|
+
}
|
|
18
|
+
export declare function suggestRules(logPath: string, options: SuggestOptions): Suggestion[];
|
|
19
|
+
export declare function formatSuggestions(suggestions: Suggestion[]): string;
|
|
20
|
+
//# sourceMappingURL=suggest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"suggest.d.ts","sourceRoot":"","sources":["../../src/observability/suggest.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,cAAc,GAAG,UAAU,CAAA;AAErE,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,cAAc,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,uDAAuD;IACvD,UAAU,EAAE,MAAM,CAAA;IAClB,oEAAoE;IACpE,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;CACxB;AAyCD,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,cAAc,GACtB,UAAU,EAAE,CA2Dd;AAED,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,UAAU,EAAE,GAAG,MAAM,CA0BnE"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.suggestRules = suggestRules;
|
|
4
|
+
exports.formatSuggestions = formatSuggestions;
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
function readEntries(logPath) {
|
|
7
|
+
if (!(0, fs_1.existsSync)(logPath))
|
|
8
|
+
return [];
|
|
9
|
+
const content = (0, fs_1.readFileSync)(logPath, 'utf-8');
|
|
10
|
+
const out = [];
|
|
11
|
+
for (const line of content.split('\n')) {
|
|
12
|
+
const t = line.trim();
|
|
13
|
+
if (!t)
|
|
14
|
+
continue;
|
|
15
|
+
try {
|
|
16
|
+
out.push(JSON.parse(t));
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// skip malformed
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
function withinWindow(timestamp, cutoff) {
|
|
25
|
+
const t = Date.parse(timestamp);
|
|
26
|
+
if (Number.isNaN(t))
|
|
27
|
+
return false;
|
|
28
|
+
return t >= cutoff;
|
|
29
|
+
}
|
|
30
|
+
function reasonKey(reason) {
|
|
31
|
+
// Group by the first ~80 chars of the reason; longer divergence
|
|
32
|
+
// probably reflects per-call detail, not a distinct pattern.
|
|
33
|
+
return reason.trim().slice(0, 80);
|
|
34
|
+
}
|
|
35
|
+
function trimExcerpt(reason) {
|
|
36
|
+
const trimmed = reason.trim();
|
|
37
|
+
return trimmed.length > 120 ? trimmed.slice(0, 117) + '...' : trimmed;
|
|
38
|
+
}
|
|
39
|
+
function suggestRules(logPath, options) {
|
|
40
|
+
const entries = readEntries(logPath);
|
|
41
|
+
if (entries.length === 0)
|
|
42
|
+
return [];
|
|
43
|
+
const cutoff = Date.now() - options.windowDays * 86400_000;
|
|
44
|
+
const recent = entries.filter((e) => typeof e.timestamp === 'string' && withinWindow(e.timestamp, cutoff));
|
|
45
|
+
const minCount = options.minPatternCount ?? 3;
|
|
46
|
+
const suggestions = [];
|
|
47
|
+
// add-rule: AI blocks that repeat
|
|
48
|
+
const buckets = new Map();
|
|
49
|
+
for (const e of recent) {
|
|
50
|
+
if (e.decision !== 'block')
|
|
51
|
+
continue;
|
|
52
|
+
if (e.source !== 'ai')
|
|
53
|
+
continue;
|
|
54
|
+
const key = `${e.toolName}::${reasonKey(e.reason)}`;
|
|
55
|
+
const existing = buckets.get(key);
|
|
56
|
+
if (existing) {
|
|
57
|
+
existing.count++;
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
buckets.set(key, {
|
|
61
|
+
toolName: e.toolName,
|
|
62
|
+
reasonExcerpt: trimExcerpt(e.reason),
|
|
63
|
+
count: 1,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
for (const b of buckets.values()) {
|
|
68
|
+
if (b.count < minCount)
|
|
69
|
+
continue;
|
|
70
|
+
suggestions.push({
|
|
71
|
+
kind: 'add-rule',
|
|
72
|
+
toolName: b.toolName,
|
|
73
|
+
reasonExcerpt: b.reasonExcerpt,
|
|
74
|
+
count: b.count,
|
|
75
|
+
message: `The AI has blocked ${b.count} ${b.toolName} calls with this reason. Consider promoting it to a deterministic rule to skip the AI roundtrip.`,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
// disable-rule: known rules that did not fire in the window
|
|
79
|
+
if (options.knownRuleIds && options.knownRuleIds.length > 0) {
|
|
80
|
+
const firedRecent = new Set();
|
|
81
|
+
for (const e of recent) {
|
|
82
|
+
if (e.source === 'deterministic' && e.ruleId) {
|
|
83
|
+
firedRecent.add(e.ruleId);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
for (const rid of options.knownRuleIds) {
|
|
87
|
+
if (firedRecent.has(rid))
|
|
88
|
+
continue;
|
|
89
|
+
suggestions.push({
|
|
90
|
+
kind: 'disable-rule',
|
|
91
|
+
ruleId: rid,
|
|
92
|
+
count: 0,
|
|
93
|
+
message: `Rule "${rid}" has not fired in the last ${options.windowDays} days. If your project never triggers it, you can disable it in .agent-gate.config.{ts,json} to reduce noise.`,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return suggestions;
|
|
98
|
+
}
|
|
99
|
+
function formatSuggestions(suggestions) {
|
|
100
|
+
if (suggestions.length === 0) {
|
|
101
|
+
return 'No suggestions. Nothing to do.';
|
|
102
|
+
}
|
|
103
|
+
const lines = [];
|
|
104
|
+
let i = 1;
|
|
105
|
+
for (const s of suggestions) {
|
|
106
|
+
if (s.kind === 'add-rule') {
|
|
107
|
+
lines.push(`${i}. [add-rule] ${s.toolName ?? '?'} blocked ${s.count} times`);
|
|
108
|
+
if (s.reasonExcerpt) {
|
|
109
|
+
lines.push(` reason: ${s.reasonExcerpt}`);
|
|
110
|
+
}
|
|
111
|
+
lines.push(` ${s.message}`);
|
|
112
|
+
}
|
|
113
|
+
else if (s.kind === 'disable-rule') {
|
|
114
|
+
lines.push(`${i}. [disable-rule] ${s.ruleId ?? '?'} (0 fires)`);
|
|
115
|
+
lines.push(` ${s.message}`);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
lines.push(`${i}. [${s.kind}] ${s.message}`);
|
|
119
|
+
}
|
|
120
|
+
lines.push('');
|
|
121
|
+
i++;
|
|
122
|
+
}
|
|
123
|
+
lines.push(`${suggestions.length} suggestion${suggestions.length === 1 ? '' : 's'}.`);
|
|
124
|
+
return lines.join('\n');
|
|
125
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hiro-c/agent-gate",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "Runtime rule enforcer for AI coding agents. Reads CLAUDE.md / AGENTS.md / .cursorrules and enforces them via Claude Code and Cursor hooks, with a deterministic safety baseline.",
|
|
5
5
|
"author": "Hiro-Chiba",
|
|
6
6
|
"license": "MIT",
|