gitclaw 0.3.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 +440 -0
- package/dist/agents.d.ts +8 -0
- package/dist/agents.js +82 -0
- package/dist/audit.d.ts +27 -0
- package/dist/audit.js +55 -0
- package/dist/compliance.d.ts +30 -0
- package/dist/compliance.js +108 -0
- package/dist/config.d.ts +11 -0
- package/dist/config.js +43 -0
- package/dist/examples.d.ts +6 -0
- package/dist/examples.js +40 -0
- package/dist/exports.d.ts +13 -0
- package/dist/exports.js +6 -0
- package/dist/hooks.d.ts +24 -0
- package/dist/hooks.js +108 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +542 -0
- package/dist/knowledge.d.ts +17 -0
- package/dist/knowledge.js +55 -0
- package/dist/loader.d.ts +64 -0
- package/dist/loader.js +222 -0
- package/dist/sandbox.d.ts +28 -0
- package/dist/sandbox.js +54 -0
- package/dist/sdk-hooks.d.ts +8 -0
- package/dist/sdk-hooks.js +31 -0
- package/dist/sdk-types.d.ts +127 -0
- package/dist/sdk-types.js +1 -0
- package/dist/sdk.d.ts +6 -0
- package/dist/sdk.js +444 -0
- package/dist/session.d.ts +15 -0
- package/dist/session.js +127 -0
- package/dist/skills.d.ts +18 -0
- package/dist/skills.js +104 -0
- package/dist/tool-loader.d.ts +3 -0
- package/dist/tool-loader.js +138 -0
- package/dist/tools/cli.d.ts +3 -0
- package/dist/tools/cli.js +86 -0
- package/dist/tools/index.d.ts +13 -0
- package/dist/tools/index.js +29 -0
- package/dist/tools/memory.d.ts +3 -0
- package/dist/tools/memory.js +128 -0
- package/dist/tools/read.d.ts +3 -0
- package/dist/tools/read.js +46 -0
- package/dist/tools/sandbox-cli.d.ts +4 -0
- package/dist/tools/sandbox-cli.js +48 -0
- package/dist/tools/sandbox-memory.d.ts +4 -0
- package/dist/tools/sandbox-memory.js +117 -0
- package/dist/tools/sandbox-read.d.ts +4 -0
- package/dist/tools/sandbox-read.js +25 -0
- package/dist/tools/sandbox-write.d.ts +4 -0
- package/dist/tools/sandbox-write.js +26 -0
- package/dist/tools/shared.d.ts +38 -0
- package/dist/tools/shared.js +69 -0
- package/dist/tools/write.d.ts +3 -0
- package/dist/tools/write.js +28 -0
- package/dist/workflows.d.ts +8 -0
- package/dist/workflows.js +81 -0
- package/package.json +57 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { readFile } from "fs/promises";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import yaml from "js-yaml";
|
|
4
|
+
/**
|
|
5
|
+
* Validate compliance section of agent manifest against spec rules.
|
|
6
|
+
*/
|
|
7
|
+
export function validateCompliance(manifest) {
|
|
8
|
+
const warnings = [];
|
|
9
|
+
const compliance = manifest.compliance;
|
|
10
|
+
if (!compliance)
|
|
11
|
+
return warnings;
|
|
12
|
+
// Rule: High/critical risk agents should have human_in_the_loop
|
|
13
|
+
if ((compliance.risk_level === "high" || compliance.risk_level === "critical") &&
|
|
14
|
+
!compliance.human_in_the_loop) {
|
|
15
|
+
warnings.push({
|
|
16
|
+
rule: "high_risk_hitl",
|
|
17
|
+
message: `Agent with risk_level "${compliance.risk_level}" should have human_in_the_loop enabled`,
|
|
18
|
+
severity: "warning",
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
// Rule: Critical risk agents must have audit logging
|
|
22
|
+
if (compliance.risk_level === "critical" && !compliance.recordkeeping?.audit_logging) {
|
|
23
|
+
warnings.push({
|
|
24
|
+
rule: "critical_audit",
|
|
25
|
+
message: "Critical risk agents must have recordkeeping.audit_logging enabled",
|
|
26
|
+
severity: "error",
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
// Rule: If regulatory frameworks specified, recordkeeping should exist
|
|
30
|
+
if (compliance.regulatory_frameworks &&
|
|
31
|
+
compliance.regulatory_frameworks.length > 0 &&
|
|
32
|
+
!compliance.recordkeeping) {
|
|
33
|
+
warnings.push({
|
|
34
|
+
rule: "regulatory_recordkeeping",
|
|
35
|
+
message: "Agents with regulatory frameworks should have recordkeeping configured",
|
|
36
|
+
severity: "warning",
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
// Rule: Review required for high/critical risk
|
|
40
|
+
if ((compliance.risk_level === "high" || compliance.risk_level === "critical") &&
|
|
41
|
+
!compliance.review) {
|
|
42
|
+
warnings.push({
|
|
43
|
+
rule: "high_risk_review",
|
|
44
|
+
message: `Agent with risk_level "${compliance.risk_level}" should have review configuration`,
|
|
45
|
+
severity: "warning",
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
// Rule: Audit logging requires retention policy
|
|
49
|
+
if (compliance.recordkeeping?.audit_logging && !compliance.recordkeeping?.retention_days) {
|
|
50
|
+
warnings.push({
|
|
51
|
+
rule: "audit_retention",
|
|
52
|
+
message: "Audit logging enabled but no retention_days specified",
|
|
53
|
+
severity: "warning",
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
// Rule: Data classification should be specified for regulated agents
|
|
57
|
+
if (compliance.regulatory_frameworks && !compliance.data_classification) {
|
|
58
|
+
warnings.push({
|
|
59
|
+
rule: "data_classification",
|
|
60
|
+
message: "Regulated agents should specify data_classification",
|
|
61
|
+
severity: "warning",
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return warnings;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Load compliance directory files and format summary for system prompt.
|
|
68
|
+
*/
|
|
69
|
+
export async function loadComplianceContext(agentDir) {
|
|
70
|
+
const complianceDir = join(agentDir, "compliance");
|
|
71
|
+
const parts = [];
|
|
72
|
+
// Load regulatory map
|
|
73
|
+
try {
|
|
74
|
+
const raw = await readFile(join(complianceDir, "regulatory-map.yaml"), "utf-8");
|
|
75
|
+
const map = yaml.load(raw);
|
|
76
|
+
if (map?.frameworks) {
|
|
77
|
+
const frameworks = Object.keys(map.frameworks).join(", ");
|
|
78
|
+
parts.push(`Regulatory frameworks: ${frameworks}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// No regulatory map
|
|
83
|
+
}
|
|
84
|
+
// Load validation schedule
|
|
85
|
+
try {
|
|
86
|
+
const raw = await readFile(join(complianceDir, "validation-schedule.yaml"), "utf-8");
|
|
87
|
+
const schedule = yaml.load(raw);
|
|
88
|
+
if (schedule?.checks && schedule.checks.length > 0) {
|
|
89
|
+
const checkList = schedule.checks
|
|
90
|
+
.map((c) => `- ${c.name} (${c.frequency})${c.description ? `: ${c.description}` : ""}`)
|
|
91
|
+
.join("\n");
|
|
92
|
+
parts.push(`Validation schedule:\n${checkList}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// No validation schedule
|
|
97
|
+
}
|
|
98
|
+
if (parts.length === 0)
|
|
99
|
+
return "";
|
|
100
|
+
return `# Compliance\n\n${parts.join("\n\n")}`;
|
|
101
|
+
}
|
|
102
|
+
export function formatComplianceWarnings(warnings) {
|
|
103
|
+
if (warnings.length === 0)
|
|
104
|
+
return "";
|
|
105
|
+
return warnings
|
|
106
|
+
.map((w) => ` ${w.severity === "error" ? "✗" : "⚠"} [${w.rule}] ${w.message}`)
|
|
107
|
+
.join("\n");
|
|
108
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface EnvConfig {
|
|
2
|
+
log_level?: string;
|
|
3
|
+
model_override?: string;
|
|
4
|
+
[key: string]: any;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Load environment configuration.
|
|
8
|
+
* Loads config/default.yaml, then merges config/<env>.yaml on top.
|
|
9
|
+
* Env is determined by --env flag or GITCLAW_ENV environment variable.
|
|
10
|
+
*/
|
|
11
|
+
export declare function loadEnvConfig(agentDir: string, env?: string): Promise<EnvConfig>;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { readFile } from "fs/promises";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import yaml from "js-yaml";
|
|
4
|
+
function deepMerge(base, override) {
|
|
5
|
+
const result = { ...base };
|
|
6
|
+
for (const key of Object.keys(override)) {
|
|
7
|
+
if (result[key] &&
|
|
8
|
+
typeof result[key] === "object" &&
|
|
9
|
+
!Array.isArray(result[key]) &&
|
|
10
|
+
typeof override[key] === "object" &&
|
|
11
|
+
!Array.isArray(override[key])) {
|
|
12
|
+
result[key] = deepMerge(result[key], override[key]);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
result[key] = override[key];
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
async function loadYamlFile(path) {
|
|
21
|
+
try {
|
|
22
|
+
const raw = await readFile(path, "utf-8");
|
|
23
|
+
return yaml.load(raw) || {};
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return {};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Load environment configuration.
|
|
31
|
+
* Loads config/default.yaml, then merges config/<env>.yaml on top.
|
|
32
|
+
* Env is determined by --env flag or GITCLAW_ENV environment variable.
|
|
33
|
+
*/
|
|
34
|
+
export async function loadEnvConfig(agentDir, env) {
|
|
35
|
+
const configDir = join(agentDir, "config");
|
|
36
|
+
const envName = env || process.env.GITCLAW_ENV;
|
|
37
|
+
const base = await loadYamlFile(join(configDir, "default.yaml"));
|
|
38
|
+
if (envName) {
|
|
39
|
+
const envOverride = await loadYamlFile(join(configDir, `${envName}.yaml`));
|
|
40
|
+
return deepMerge(base, envOverride);
|
|
41
|
+
}
|
|
42
|
+
return base;
|
|
43
|
+
}
|
package/dist/examples.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { readFile, readdir, stat } from "fs/promises";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
export async function loadExamples(agentDir) {
|
|
4
|
+
const examplesDir = join(agentDir, "examples");
|
|
5
|
+
try {
|
|
6
|
+
const s = await stat(examplesDir);
|
|
7
|
+
if (!s.isDirectory())
|
|
8
|
+
return [];
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
const entries = await readdir(examplesDir);
|
|
14
|
+
const examples = [];
|
|
15
|
+
for (const entry of entries) {
|
|
16
|
+
if (!entry.endsWith(".md"))
|
|
17
|
+
continue;
|
|
18
|
+
try {
|
|
19
|
+
const content = await readFile(join(examplesDir, entry), "utf-8");
|
|
20
|
+
const name = entry.replace(/\.md$/, "");
|
|
21
|
+
examples.push({ name, content: content.trim() });
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// Skip unreadable files
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return examples.sort((a, b) => a.name.localeCompare(b.name));
|
|
28
|
+
}
|
|
29
|
+
export function formatExamplesForPrompt(examples) {
|
|
30
|
+
if (examples.length === 0)
|
|
31
|
+
return "";
|
|
32
|
+
const blocks = examples
|
|
33
|
+
.map((e) => `<example name="${e.name}">\n${e.content}\n</example>`)
|
|
34
|
+
.join("\n\n");
|
|
35
|
+
return `# Examples
|
|
36
|
+
|
|
37
|
+
<examples>
|
|
38
|
+
${blocks}
|
|
39
|
+
</examples>`;
|
|
40
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { query, tool } from "./sdk.js";
|
|
2
|
+
export type { Query, QueryOptions, LocalRepoOptions, SandboxOptions, GCMessage, GCAssistantMessage, GCUserMessage, GCToolUseMessage, GCToolResultMessage, GCSystemMessage, GCStreamDelta, GCToolDefinition, GCHooks, GCHookResult, GCPreToolUseContext, GCHookContext, } from "./sdk-types.js";
|
|
3
|
+
export type { AgentManifest, LoadedAgent } from "./loader.js";
|
|
4
|
+
export type { SkillMetadata } from "./skills.js";
|
|
5
|
+
export type { WorkflowMetadata } from "./workflows.js";
|
|
6
|
+
export type { SubAgentMetadata } from "./agents.js";
|
|
7
|
+
export type { ComplianceWarning } from "./compliance.js";
|
|
8
|
+
export type { EnvConfig } from "./config.js";
|
|
9
|
+
export type { SandboxConfig, SandboxContext } from "./sandbox.js";
|
|
10
|
+
export { createSandboxContext } from "./sandbox.js";
|
|
11
|
+
export type { LocalSession } from "./session.js";
|
|
12
|
+
export { initLocalSession } from "./session.js";
|
|
13
|
+
export { loadAgent } from "./loader.js";
|
package/dist/exports.js
ADDED
package/dist/hooks.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { AgentTool } from "@mariozechner/pi-agent-core";
|
|
2
|
+
export interface HookDefinition {
|
|
3
|
+
script: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface HooksConfig {
|
|
7
|
+
hooks: {
|
|
8
|
+
on_session_start?: HookDefinition[];
|
|
9
|
+
pre_tool_use?: HookDefinition[];
|
|
10
|
+
post_response?: HookDefinition[];
|
|
11
|
+
on_error?: HookDefinition[];
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export interface HookResult {
|
|
15
|
+
action: "allow" | "block" | "modify";
|
|
16
|
+
reason?: string;
|
|
17
|
+
args?: Record<string, any>;
|
|
18
|
+
}
|
|
19
|
+
export declare function loadHooksConfig(agentDir: string): Promise<HooksConfig | null>;
|
|
20
|
+
export declare function runHooks(hooks: HookDefinition[] | undefined, agentDir: string, input: Record<string, any>): Promise<HookResult>;
|
|
21
|
+
/**
|
|
22
|
+
* Wraps a tool's execute function with pre_tool_use hook support.
|
|
23
|
+
*/
|
|
24
|
+
export declare function wrapToolWithHooks<T extends AgentTool<any>>(tool: T, hooksConfig: HooksConfig, agentDir: string, sessionId: string): T;
|
package/dist/hooks.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
import { readFile } from "fs/promises";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import yaml from "js-yaml";
|
|
5
|
+
export async function loadHooksConfig(agentDir) {
|
|
6
|
+
const hooksPath = join(agentDir, "hooks", "hooks.yaml");
|
|
7
|
+
try {
|
|
8
|
+
const raw = await readFile(hooksPath, "utf-8");
|
|
9
|
+
const config = yaml.load(raw);
|
|
10
|
+
if (!config?.hooks)
|
|
11
|
+
return null;
|
|
12
|
+
return config;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async function executeHook(hook, agentDir, input) {
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
const scriptPath = join(agentDir, "hooks", hook.script);
|
|
21
|
+
const child = spawn("sh", [scriptPath], {
|
|
22
|
+
cwd: agentDir,
|
|
23
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
24
|
+
env: { ...process.env },
|
|
25
|
+
});
|
|
26
|
+
let stdout = "";
|
|
27
|
+
let stderr = "";
|
|
28
|
+
child.stdout.on("data", (data) => {
|
|
29
|
+
stdout += data.toString("utf-8");
|
|
30
|
+
});
|
|
31
|
+
child.stderr.on("data", (data) => {
|
|
32
|
+
stderr += data.toString("utf-8");
|
|
33
|
+
});
|
|
34
|
+
child.stdin.write(JSON.stringify(input));
|
|
35
|
+
child.stdin.end();
|
|
36
|
+
const timeout = setTimeout(() => {
|
|
37
|
+
child.kill("SIGTERM");
|
|
38
|
+
reject(new Error(`Hook "${hook.script}" timed out after 10s`));
|
|
39
|
+
}, 10_000);
|
|
40
|
+
child.on("error", (err) => {
|
|
41
|
+
clearTimeout(timeout);
|
|
42
|
+
reject(new Error(`Hook "${hook.script}" failed to start: ${err.message}`));
|
|
43
|
+
});
|
|
44
|
+
child.on("close", (code) => {
|
|
45
|
+
clearTimeout(timeout);
|
|
46
|
+
if (code !== 0) {
|
|
47
|
+
reject(new Error(`Hook "${hook.script}" exited with code ${code}: ${stderr.trim()}`));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
const result = JSON.parse(stdout.trim());
|
|
52
|
+
resolve(result);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// If hook doesn't return JSON, treat as allow
|
|
56
|
+
resolve({ action: "allow" });
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
export async function runHooks(hooks, agentDir, input) {
|
|
62
|
+
if (!hooks || hooks.length === 0) {
|
|
63
|
+
return { action: "allow" };
|
|
64
|
+
}
|
|
65
|
+
for (const hook of hooks) {
|
|
66
|
+
try {
|
|
67
|
+
const result = await executeHook(hook, agentDir, input);
|
|
68
|
+
if (result.action === "block") {
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
if (result.action === "modify") {
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
console.error(`Hook error: ${err.message}`);
|
|
77
|
+
// Hook errors don't block execution by default
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return { action: "allow" };
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Wraps a tool's execute function with pre_tool_use hook support.
|
|
84
|
+
*/
|
|
85
|
+
export function wrapToolWithHooks(tool, hooksConfig, agentDir, sessionId) {
|
|
86
|
+
const preToolHooks = hooksConfig.hooks.pre_tool_use;
|
|
87
|
+
if (!preToolHooks || preToolHooks.length === 0)
|
|
88
|
+
return tool;
|
|
89
|
+
const originalExecute = tool.execute;
|
|
90
|
+
const wrappedTool = {
|
|
91
|
+
...tool,
|
|
92
|
+
execute: async (toolCallId, args, signal, onUpdate) => {
|
|
93
|
+
const hookInput = {
|
|
94
|
+
event: "pre_tool_use",
|
|
95
|
+
session_id: sessionId,
|
|
96
|
+
tool: tool.name,
|
|
97
|
+
args,
|
|
98
|
+
};
|
|
99
|
+
const result = await runHooks(preToolHooks, agentDir, hookInput);
|
|
100
|
+
if (result.action === "block") {
|
|
101
|
+
throw new Error(`Tool "${tool.name}" blocked by hook: ${result.reason || "no reason given"}`);
|
|
102
|
+
}
|
|
103
|
+
const finalArgs = result.action === "modify" && result.args ? result.args : args;
|
|
104
|
+
return originalExecute.call(tool, toolCallId, finalArgs, signal, onUpdate);
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
return wrappedTool;
|
|
108
|
+
}
|
package/dist/index.d.ts
ADDED