@oussamadouhou/agent-enforcement 0.1.2

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 (40) hide show
  1. package/.claude-plugin/plugin.json +5 -0
  2. package/README.md +67 -0
  3. package/dist/adapters/logger.d.ts +23 -0
  4. package/dist/adapters/logger.d.ts.map +1 -0
  5. package/dist/adapters/logger.js +58 -0
  6. package/dist/adapters/logger.js.map +1 -0
  7. package/dist/adapters/message-reader.d.ts +20 -0
  8. package/dist/adapters/message-reader.d.ts.map +1 -0
  9. package/dist/adapters/message-reader.js +123 -0
  10. package/dist/adapters/message-reader.js.map +1 -0
  11. package/dist/constants.d.ts +22 -0
  12. package/dist/constants.d.ts.map +1 -0
  13. package/dist/constants.js +50 -0
  14. package/dist/constants.js.map +1 -0
  15. package/dist/detection/environment.d.ts +7 -0
  16. package/dist/detection/environment.d.ts.map +1 -0
  17. package/dist/detection/environment.js +30 -0
  18. package/dist/detection/environment.js.map +1 -0
  19. package/dist/hook.d.ts +35 -0
  20. package/dist/hook.d.ts.map +1 -0
  21. package/dist/hook.js +223 -0
  22. package/dist/hook.js.map +1 -0
  23. package/dist/index.d.ts +14 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +8 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/plugin.d.ts +4 -0
  28. package/dist/plugin.d.ts.map +1 -0
  29. package/dist/plugin.js +6 -0
  30. package/dist/plugin.js.map +1 -0
  31. package/dist/schema/verification-schema.d.ts +127 -0
  32. package/dist/schema/verification-schema.d.ts.map +1 -0
  33. package/dist/schema/verification-schema.js +132 -0
  34. package/dist/schema/verification-schema.js.map +1 -0
  35. package/docs/EVIDENCE_SYSTEM.md +214 -0
  36. package/fixtures/claude/project-abc/session-123.jsonl +2 -0
  37. package/fixtures/opencode/messages/session-123/msg_001.json +4 -0
  38. package/hooks/enforcement.py +255 -0
  39. package/hooks/hooks.json +15 -0
  40. package/package.json +70 -0
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "agent-enforcement",
3
+ "version": "1.0.0",
4
+ "description": "Evidence enforcement for todo completion"
5
+ }
package/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # @oussamadouhou/agent-enforcement
2
+
3
+ Evidence enforcement hooks for OpenCode and Claude Code. This package blocks or warns when `todowrite` tasks are marked complete without the required evidence block, checklist, and trust markers.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ bun add @oussamadouhou/agent-enforcement
9
+ ```
10
+
11
+ ## OpenCode usage
12
+
13
+ ```ts
14
+ import type { Plugin } from "@opencode-ai/plugin"
15
+ import { createEvidenceEnforcementHook } from "@oussamadouhou/agent-enforcement"
16
+
17
+ const AgentEnforcementPlugin: Plugin = async ctx => {
18
+ return createEvidenceEnforcementHook(ctx)
19
+ }
20
+
21
+ export default AgentEnforcementPlugin
22
+ ```
23
+
24
+ ## Claude Code usage
25
+
26
+ Copy the plugin to your Claude Code plugins directory and enable `hooks/enforcement.py`:
27
+
28
+ ```bash
29
+ cp -r . ~/.claude/plugins/agent-enforcement
30
+ ```
31
+
32
+ Then ensure `hooks/hooks.json` is configured in your Claude settings.
33
+
34
+ ## Configuration
35
+
36
+ - `OPENCODE_ENFORCEMENT_LEVEL` or `ENFORCEMENT_LEVEL`: `0` (creative), `1` (standard), `2` (strict)
37
+ - `ENFORCEMENT_LOG_MODE`: `file` | `console` | `silent`
38
+ - `ENFORCEMENT_LOG_PATH`: file path for logs
39
+ - `ENFORCEMENT_LOG_LEVEL`: `debug` | `info` | `warn` | `error`
40
+
41
+ ## Evidence format
42
+
43
+ ```text
44
+ **Evidence for task-1**:
45
+
46
+ **Execution**:
47
+ - Tool: read
48
+
49
+ **Verification**:
50
+ - Checked output
51
+
52
+ **Checklist**:
53
+ - [x] Tool executed successfully
54
+ - [x] Output captured
55
+ - [x] Result matches expected
56
+ - [x] No workarounds used
57
+ - [x] Independently verifiable
58
+
59
+ **Trust**: 🟢 HIGH | ✅ Ground Truth
60
+ ```
61
+
62
+ ## Development
63
+
64
+ ```bash
65
+ bun test
66
+ bun run build
67
+ ```
@@ -0,0 +1,23 @@
1
+ export interface Logger {
2
+ info(msg: string): void;
3
+ warn(msg: string): void;
4
+ error(msg: string): void;
5
+ debug(msg: string): void;
6
+ }
7
+ export interface LogConfig {
8
+ mode: "file" | "console" | "silent";
9
+ path?: string;
10
+ level?: "debug" | "info" | "warn" | "error";
11
+ }
12
+ export declare function getLogConfig(): LogConfig;
13
+ export declare class DefaultLogger implements Logger {
14
+ private config;
15
+ constructor(config?: LogConfig);
16
+ private shouldLog;
17
+ private log;
18
+ info(msg: string): void;
19
+ warn(msg: string): void;
20
+ error(msg: string): void;
21
+ debug(msg: string): void;
22
+ }
23
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/adapters/logger.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,QAAQ,CAAA;IACnC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;CAC5C;AASD,wBAAgB,YAAY,IAAI,SAAS,CAMxC;AAED,qBAAa,aAAc,YAAW,MAAM;IAC1C,OAAO,CAAC,MAAM,CAAW;gBAEb,MAAM,CAAC,EAAE,SAAS;IAI9B,OAAO,CAAC,SAAS;IAQjB,OAAO,CAAC,GAAG;IAiBX,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAIvB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAIvB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAIxB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;CAGzB"}
@@ -0,0 +1,58 @@
1
+ import { appendFileSync } from "node:fs";
2
+ const LOG_LEVELS = {
3
+ debug: 10,
4
+ info: 20,
5
+ warn: 30,
6
+ error: 40,
7
+ };
8
+ export function getLogConfig() {
9
+ return {
10
+ mode: process.env.ENFORCEMENT_LOG_MODE || "file",
11
+ path: process.env.ENFORCEMENT_LOG_PATH || "/tmp/agent-enforcement.log",
12
+ level: process.env.ENFORCEMENT_LOG_LEVEL || "info",
13
+ };
14
+ }
15
+ export class DefaultLogger {
16
+ config;
17
+ constructor(config) {
18
+ this.config = config ?? getLogConfig();
19
+ }
20
+ shouldLog(level) {
21
+ if (this.config.mode === "silent") {
22
+ return false;
23
+ }
24
+ const minLevel = this.config.level ?? "info";
25
+ return LOG_LEVELS[level] >= LOG_LEVELS[minLevel];
26
+ }
27
+ log(level, msg) {
28
+ if (!this.shouldLog(level)) {
29
+ return;
30
+ }
31
+ const timestamp = new Date().toISOString();
32
+ const formatted = `[${timestamp}] [${level.toUpperCase()}] ${msg}`;
33
+ if (this.config.mode === "file") {
34
+ try {
35
+ appendFileSync(this.config.path ?? "/tmp/agent-enforcement.log", `${formatted}\n`);
36
+ }
37
+ catch {
38
+ return;
39
+ }
40
+ }
41
+ else if (this.config.mode === "console") {
42
+ console.log(formatted);
43
+ }
44
+ }
45
+ info(msg) {
46
+ this.log("info", msg);
47
+ }
48
+ warn(msg) {
49
+ this.log("warn", msg);
50
+ }
51
+ error(msg) {
52
+ this.log("error", msg);
53
+ }
54
+ debug(msg) {
55
+ this.log("debug", msg);
56
+ }
57
+ }
58
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/adapters/logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAexC,MAAM,UAAU,GAAoD;IAClE,KAAK,EAAE,EAAE;IACT,IAAI,EAAE,EAAE;IACR,IAAI,EAAE,EAAE;IACR,KAAK,EAAE,EAAE;CACV,CAAA;AAED,MAAM,UAAU,YAAY;IAC1B,OAAO;QACL,IAAI,EAAG,OAAO,CAAC,GAAG,CAAC,oBAA0C,IAAI,MAAM;QACvE,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,4BAA4B;QACtE,KAAK,EAAG,OAAO,CAAC,GAAG,CAAC,qBAA4C,IAAI,MAAM;KAC3E,CAAA;AACH,CAAC;AAED,MAAM,OAAO,aAAa;IAChB,MAAM,CAAW;IAEzB,YAAY,MAAkB;QAC5B,IAAI,CAAC,MAAM,GAAG,MAAM,IAAI,YAAY,EAAE,CAAA;IACxC,CAAC;IAEO,SAAS,CAAC,KAAsC;QACtD,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAClC,OAAO,KAAK,CAAA;QACd,CAAC;QACD,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI,MAAM,CAAA;QAC5C,OAAO,UAAU,CAAC,KAAK,CAAC,IAAI,UAAU,CAAC,QAAQ,CAAC,CAAA;IAClD,CAAC;IAEO,GAAG,CAAC,KAAsC,EAAE,GAAW;QAC7D,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3B,OAAM;QACR,CAAC;QACD,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;QAC1C,MAAM,SAAS,GAAG,IAAI,SAAS,MAAM,KAAK,CAAC,WAAW,EAAE,KAAK,GAAG,EAAE,CAAA;QAClE,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAChC,IAAI,CAAC;gBACH,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,4BAA4B,EAAE,GAAG,SAAS,IAAI,CAAC,CAAA;YACpF,CAAC;YAAC,MAAM,CAAC;gBACP,OAAM;YACR,CAAC;QACH,CAAC;aAAM,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC1C,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;QACxB,CAAC;IACH,CAAC;IAED,IAAI,CAAC,GAAW;QACd,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACvB,CAAC;IAED,IAAI,CAAC,GAAW;QACd,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACvB,CAAC;IAED,KAAK,CAAC,GAAW;QACf,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,CAAA;IACxB,CAAC;IAED,KAAK,CAAC,GAAW;QACf,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,CAAA;IACxB,CAAC;CACF"}
@@ -0,0 +1,20 @@
1
+ import type { Logger } from "./logger";
2
+ export interface MessageReader {
3
+ getLastAssistantMessage(sessionID: string): string | null;
4
+ }
5
+ export declare class OpenCodeMessageReader implements MessageReader {
6
+ private storagePath;
7
+ private logger?;
8
+ constructor(storagePath?: string, logger?: Logger);
9
+ private getMessageDir;
10
+ getLastAssistantMessage(sessionID: string): string | null;
11
+ }
12
+ export declare class ClaudeCodeMessageReader implements MessageReader {
13
+ private projectPath;
14
+ private logger?;
15
+ constructor(projectPath?: string, logger?: Logger);
16
+ private detectProjectPath;
17
+ getLastAssistantMessage(sessionID: string): string | null;
18
+ private extractText;
19
+ }
20
+ //# sourceMappingURL=message-reader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-reader.d.ts","sourceRoot":"","sources":["../../src/adapters/message-reader.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AAEtC,MAAM,WAAW,aAAa;IAC5B,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;CAC1D;AAED,qBAAa,qBAAsB,YAAW,aAAa;IACzD,OAAO,CAAC,WAAW,CAAQ;IAC3B,OAAO,CAAC,MAAM,CAAC,CAAQ;gBAEX,WAAW,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;IAQjD,OAAO,CAAC,aAAa;IAoBrB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;CAwB1D;AAED,qBAAa,uBAAwB,YAAW,aAAa;IAC3D,OAAO,CAAC,WAAW,CAAQ;IAC3B,OAAO,CAAC,MAAM,CAAC,CAAQ;gBAEX,WAAW,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;IAKjD,OAAO,CAAC,iBAAiB;IAuBzB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IA2BzD,OAAO,CAAC,WAAW;CAsBpB"}
@@ -0,0 +1,123 @@
1
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import os from "node:os";
4
+ export class OpenCodeMessageReader {
5
+ storagePath;
6
+ logger;
7
+ constructor(storagePath, logger) {
8
+ this.storagePath =
9
+ storagePath ??
10
+ process.env.OPENCODE_MESSAGE_STORAGE ??
11
+ join(os.homedir(), ".local/share/opencode/storage/message");
12
+ this.logger = logger;
13
+ }
14
+ getMessageDir(sessionID) {
15
+ if (!existsSync(this.storagePath)) {
16
+ return null;
17
+ }
18
+ const directPath = join(this.storagePath, sessionID);
19
+ if (existsSync(directPath)) {
20
+ return directPath;
21
+ }
22
+ for (const dir of readdirSync(this.storagePath)) {
23
+ const sessionPath = join(this.storagePath, dir, sessionID);
24
+ if (existsSync(sessionPath)) {
25
+ return sessionPath;
26
+ }
27
+ }
28
+ return null;
29
+ }
30
+ getLastAssistantMessage(sessionID) {
31
+ const messageDir = this.getMessageDir(sessionID);
32
+ if (!messageDir) {
33
+ return null;
34
+ }
35
+ try {
36
+ const files = readdirSync(messageDir)
37
+ .filter(file => file.endsWith(".json"))
38
+ .sort()
39
+ .reverse();
40
+ for (const file of files) {
41
+ const content = JSON.parse(readFileSync(join(messageDir, file), "utf-8"));
42
+ if (content.role === "assistant" && typeof content.text === "string") {
43
+ return content.text;
44
+ }
45
+ }
46
+ }
47
+ catch (error) {
48
+ this.logger?.warn(`Failed to read message files for ${sessionID}: ${String(error)}`);
49
+ }
50
+ return null;
51
+ }
52
+ }
53
+ export class ClaudeCodeMessageReader {
54
+ projectPath;
55
+ logger;
56
+ constructor(projectPath, logger) {
57
+ this.projectPath = projectPath ?? this.detectProjectPath();
58
+ this.logger = logger;
59
+ }
60
+ detectProjectPath() {
61
+ const projectsPath = join(os.homedir(), ".claude/projects");
62
+ if (!existsSync(projectsPath)) {
63
+ return process.cwd();
64
+ }
65
+ const projects = readdirSync(projectsPath).filter(project => existsSync(join(projectsPath, project, "settings.json")));
66
+ if (projects.length === 0) {
67
+ return process.cwd();
68
+ }
69
+ const sorted = projects.sort((a, b) => {
70
+ const statA = statSync(join(projectsPath, a)).mtimeMs;
71
+ const statB = statSync(join(projectsPath, b)).mtimeMs;
72
+ return statB - statA;
73
+ });
74
+ return join(projectsPath, sorted[0]);
75
+ }
76
+ getLastAssistantMessage(sessionID) {
77
+ const sessionFile = join(this.projectPath, `${sessionID}.jsonl`);
78
+ if (!existsSync(sessionFile)) {
79
+ return null;
80
+ }
81
+ try {
82
+ const content = readFileSync(sessionFile, "utf-8");
83
+ const lines = content.trim().split("\n");
84
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
85
+ try {
86
+ const message = JSON.parse(lines[i]);
87
+ if (message.role === "assistant") {
88
+ const text = this.extractText(message);
89
+ return text ?? lines[i];
90
+ }
91
+ }
92
+ catch {
93
+ continue;
94
+ }
95
+ }
96
+ }
97
+ catch (error) {
98
+ this.logger?.warn(`Failed to read Claude Code session ${sessionID}: ${String(error)}`);
99
+ }
100
+ return null;
101
+ }
102
+ extractText(message) {
103
+ const text = message.text;
104
+ if (typeof text === "string") {
105
+ return text;
106
+ }
107
+ const content = message.content;
108
+ if (Array.isArray(content)) {
109
+ for (const part of content) {
110
+ if (part &&
111
+ typeof part === "object" &&
112
+ part.type === "text") {
113
+ const textPart = part.text;
114
+ if (typeof textPart === "string") {
115
+ return textPart;
116
+ }
117
+ }
118
+ }
119
+ }
120
+ return null;
121
+ }
122
+ }
123
+ //# sourceMappingURL=message-reader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-reader.js","sourceRoot":"","sources":["../../src/adapters/message-reader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ export declare const HOOK_NAME = "evidence-enforcement";
2
+ export declare const ENFORCEMENT_LEVELS: {
3
+ readonly CREATIVE: 0;
4
+ readonly STANDARD: 1;
5
+ readonly STRICT: 2;
6
+ };
7
+ export type EnforcementLevel = typeof ENFORCEMENT_LEVELS[keyof typeof ENFORCEMENT_LEVELS];
8
+ export declare const EVIDENCE_PATTERNS: {
9
+ EVIDENCE_BLOCK: RegExp;
10
+ EXECUTION_SECTION: RegExp;
11
+ VERIFICATION_SECTION: RegExp;
12
+ CHECKLIST_SECTION: RegExp;
13
+ TRUST_MARKERS: RegExp;
14
+ CHECKLIST_ITEM: RegExp;
15
+ };
16
+ export declare const REQUIRED_CHECKLIST_ITEMS: string[];
17
+ export declare const ERROR_MESSAGES: {
18
+ NO_EVIDENCE_BLOCK: (level: EnforcementLevel) => string;
19
+ INCOMPLETE_CHECKLIST: (missingItems: string[], level: EnforcementLevel) => string;
20
+ NO_TRUST_MARKERS: (level: EnforcementLevel) => string;
21
+ };
22
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,SAAS,yBAAyB,CAAA;AAE/C,eAAO,MAAM,kBAAkB;;;;CAIrB,CAAA;AAEV,MAAM,MAAM,gBAAgB,GAAG,OAAO,kBAAkB,CAAC,MAAM,OAAO,kBAAkB,CAAC,CAAA;AAEzF,eAAO,MAAM,iBAAiB;;;;;;;CAO7B,CAAA;AAED,eAAO,MAAM,wBAAwB,UAMpC,CAAA;AAID,eAAO,MAAM,cAAc;+BACE,gBAAgB;yCAgBN,MAAM,EAAE,SAAS,gBAAgB;8BAO5C,gBAAgB;CAO3C,CAAA"}
@@ -0,0 +1,50 @@
1
+ export const HOOK_NAME = "evidence-enforcement";
2
+ export const ENFORCEMENT_LEVELS = {
3
+ CREATIVE: 0,
4
+ STANDARD: 1,
5
+ STRICT: 2,
6
+ };
7
+ export const EVIDENCE_PATTERNS = {
8
+ EVIDENCE_BLOCK: /\*\*Evidence(?:\s+for\s+[\w-]+)?\*\*:/i,
9
+ EXECUTION_SECTION: /\*\*Execution\*\*:/i,
10
+ VERIFICATION_SECTION: /\*\*Verification\*\*:/i,
11
+ CHECKLIST_SECTION: /\*\*Checklist\*\*:/i,
12
+ TRUST_MARKERS: /\*\*(?:Confidence|Trust)\*\*:/i,
13
+ CHECKLIST_ITEM: /\[\s*[xX]\s*\]/,
14
+ };
15
+ export const REQUIRED_CHECKLIST_ITEMS = [
16
+ "Tool executed",
17
+ "Output captured",
18
+ "Result matches",
19
+ "No workarounds",
20
+ "Independently verifiable",
21
+ ];
22
+ const DOCS_PATH = "~/.config/opencode/plugins/agent-enforcement/docs/EVIDENCE_SYSTEM.md";
23
+ export const ERROR_MESSAGES = {
24
+ NO_EVIDENCE_BLOCK: (level) => `[${HOOK_NAME}] ${level === ENFORCEMENT_LEVELS.STRICT ? "BLOCKED" : "WARNING"}: ` +
25
+ "Cannot mark todo complete without evidence block.\n\n" +
26
+ "Required format:\n" +
27
+ "**Evidence for [todo-id]**:\n" +
28
+ "**Execution**: [command/tool used]\n" +
29
+ "**Verification**: [what was checked]\n" +
30
+ "**Checklist**:\n" +
31
+ "- [x] Tool executed successfully\n" +
32
+ "- [x] Output captured\n" +
33
+ "- [x] Result matches expected\n" +
34
+ "- [x] No workarounds used\n" +
35
+ "- [x] Independently verifiable\n" +
36
+ "**Trust**: 🟢 HIGH | ✅ Ground Truth\n\n" +
37
+ `See ${DOCS_PATH} for full documentation.`,
38
+ INCOMPLETE_CHECKLIST: (missingItems, level) => `[${HOOK_NAME}] ${level === ENFORCEMENT_LEVELS.STRICT ? "BLOCKED" : "WARNING"}: ` +
39
+ "Evidence checklist incomplete.\n\n" +
40
+ `Missing checklist items:\n${missingItems.map(item => `- [ ] ${item}`).join("\n")}\n\n` +
41
+ `All items must be checked [x] before marking todo complete.\n\n` +
42
+ `See ${DOCS_PATH} for checklist requirements.`,
43
+ NO_TRUST_MARKERS: (level) => `[${HOOK_NAME}] ${level === ENFORCEMENT_LEVELS.STRICT ? "BLOCKED" : "WARNING"}: ` +
44
+ "Trust markers missing.\n\n" +
45
+ "Required:\n" +
46
+ "**Confidence**: 🟢 HIGH | 🟡 MEDIUM | 🔴 LOW\n" +
47
+ "**Evidence**: ✅ Ground Truth | ⚠️ Simulation | ❌ Assumption\n\n" +
48
+ `See ${DOCS_PATH} for trust marker details.`,
49
+ };
50
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,SAAS,GAAG,sBAAsB,CAAA;AAE/C,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,QAAQ,EAAE,CAAC;IACX,QAAQ,EAAE,CAAC;IACX,MAAM,EAAE,CAAC;CACD,CAAA;AAIV,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,cAAc,EAAE,wCAAwC;IACxD,iBAAiB,EAAE,qBAAqB;IACxC,oBAAoB,EAAE,wBAAwB;IAC9C,iBAAiB,EAAE,qBAAqB;IACxC,aAAa,EAAE,gCAAgC;IAC/C,cAAc,EAAE,gBAAgB;CACjC,CAAA;AAED,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC,eAAe;IACf,iBAAiB;IACjB,gBAAgB;IAChB,gBAAgB;IAChB,0BAA0B;CAC3B,CAAA;AAED,MAAM,SAAS,GAAG,sEAAsE,CAAA;AAExF,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,iBAAiB,EAAE,CAAC,KAAuB,EAAE,EAAE,CAC7C,IAAI,SAAS,KAAK,KAAK,KAAK,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,IAAI;QACjF,uDAAuD;QACvD,oBAAoB;QACpB,+BAA+B;QAC/B,sCAAsC;QACtC,wCAAwC;QACxC,kBAAkB;QAClB,oCAAoC;QACpC,yBAAyB;QACzB,iCAAiC;QACjC,6BAA6B;QAC7B,kCAAkC;QAClC,yCAAyC;QACzC,OAAO,SAAS,0BAA0B;IAE5C,oBAAoB,EAAE,CAAC,YAAsB,EAAE,KAAuB,EAAE,EAAE,CACxE,IAAI,SAAS,KAAK,KAAK,KAAK,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,IAAI;QACjF,oCAAoC;QACpC,6BAA6B,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM;QACvF,iEAAiE;QACjE,OAAO,SAAS,8BAA8B;IAEhD,gBAAgB,EAAE,CAAC,KAAuB,EAAE,EAAE,CAC5C,IAAI,SAAS,KAAK,KAAK,KAAK,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,IAAI;QACjF,4BAA4B;QAC5B,aAAa;QACb,gDAAgD;QAChD,iEAAiE;QACjE,OAAO,SAAS,4BAA4B;CAC/C,CAAA"}
@@ -0,0 +1,7 @@
1
+ export type EnvironmentType = "claude-code" | "opencode";
2
+ export interface EnvironmentDetection {
3
+ type: EnvironmentType;
4
+ hasOhMyOpenCode: boolean;
5
+ }
6
+ export declare function detectEnvironment(directory?: string): EnvironmentDetection;
7
+ //# sourceMappingURL=environment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"environment.d.ts","sourceRoot":"","sources":["../../src/detection/environment.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,eAAe,GAAG,aAAa,GAAG,UAAU,CAAA;AAExD,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,eAAe,CAAA;IACrB,eAAe,EAAE,OAAO,CAAA;CACzB;AAED,wBAAgB,iBAAiB,CAAC,SAAS,GAAE,MAAsB,GAAG,oBAAoB,CAOzF"}
@@ -0,0 +1,30 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import os from "node:os";
4
+ export function detectEnvironment(directory = process.cwd()) {
5
+ if (process.env.CLAUDE_PLUGIN_ROOT) {
6
+ return { type: "claude-code", hasOhMyOpenCode: false };
7
+ }
8
+ const hasOhMyOpenCode = checkForOhMyOpenCode(directory);
9
+ return { type: "opencode", hasOhMyOpenCode };
10
+ }
11
+ function checkForOhMyOpenCode(directory) {
12
+ const configPaths = [
13
+ join(directory, "opencode.json"),
14
+ join(directory, "opencode.jsonc"),
15
+ join(os.homedir(), ".config/opencode.json"),
16
+ ];
17
+ return configPaths.some(configPath => {
18
+ if (!existsSync(configPath)) {
19
+ return false;
20
+ }
21
+ try {
22
+ const content = readFileSync(configPath, "utf-8");
23
+ return content.includes("disabled_hooks") || content.includes("oh-my-opencode");
24
+ }
25
+ catch {
26
+ return false;
27
+ }
28
+ });
29
+ }
30
+ //# sourceMappingURL=environment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"environment.js","sourceRoot":"","sources":["../../src/detection/environment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,MAAM,SAAS,CAAA;AASxB,MAAM,UAAU,iBAAiB,CAAC,YAAoB,OAAO,CAAC,GAAG,EAAE;IACjE,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,CAAC;QACnC,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,eAAe,EAAE,KAAK,EAAE,CAAA;IACxD,CAAC;IAED,MAAM,eAAe,GAAG,oBAAoB,CAAC,SAAS,CAAC,CAAA;IACvD,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,eAAe,EAAE,CAAA;AAC9C,CAAC;AAED,SAAS,oBAAoB,CAAC,SAAiB;IAC7C,MAAM,WAAW,GAAG;QAClB,IAAI,CAAC,SAAS,EAAE,eAAe,CAAC;QAChC,IAAI,CAAC,SAAS,EAAE,gBAAgB,CAAC;QACjC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,uBAAuB,CAAC;KAC5C,CAAA;IAED,OAAO,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE;QACnC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC5B,OAAO,KAAK,CAAA;QACd,CAAC;QACD,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAA;YACjD,OAAO,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAA;QACjF,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAA;QACd,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC"}
package/dist/hook.d.ts ADDED
@@ -0,0 +1,35 @@
1
+ import type { PluginInput } from "@opencode-ai/plugin";
2
+ import { type EnforcementLevel } from "./constants";
3
+ import { type Logger } from "./adapters/logger";
4
+ import { type MessageReader } from "./adapters/message-reader";
5
+ export interface EnforcementOptions {
6
+ level?: EnforcementLevel;
7
+ logger?: Logger;
8
+ messageReader?: MessageReader;
9
+ messageStoragePath?: string;
10
+ }
11
+ export declare function createEvidenceEnforcementHook(ctx: PluginInput, options?: EnforcementOptions): {
12
+ event: ({ event }: {
13
+ event: {
14
+ type: string;
15
+ properties?: any;
16
+ };
17
+ }) => Promise<void>;
18
+ "tool.execute.before": (input: {
19
+ tool: string;
20
+ sessionID: string;
21
+ callID: string;
22
+ }, output: {
23
+ args: Record<string, unknown>;
24
+ }) => Promise<void>;
25
+ "tool.execute.after": (input: {
26
+ tool: string;
27
+ sessionID: string;
28
+ callID: string;
29
+ }, output: {
30
+ title: string;
31
+ output: string;
32
+ metadata: unknown;
33
+ }) => Promise<void>;
34
+ };
35
+ //# sourceMappingURL=hook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook.d.ts","sourceRoot":"","sources":["../src/hook.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,EAEL,KAAK,gBAAgB,EAKtB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAiB,KAAK,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAC9D,OAAO,EAEL,KAAK,aAAa,EACnB,MAAM,2BAA2B,CAAA;AAclC,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,gBAAgB,CAAA;IACxB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,aAAa,CAAC,EAAE,aAAa,CAAA;IAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED,wBAAgB,6BAA6B,CAC3C,GAAG,EAAE,WAAW,EAChB,OAAO,GAAE,kBAAuB;uBAcL;QAAE,KAAK,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,UAAU,CAAC,EAAE,GAAG,CAAA;SAAE,CAAA;KAAE,KAAG,OAAO,CAAC,IAAI,CAAC;mCA0B7E;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,UAClD;QAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,KACxC,OAAO,CAAC,IAAI,CAAC;kCAuFP;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,UAClD;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,KAC3D,OAAO,CAAC,IAAI,CAAC;EAmCnB"}