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.
Files changed (58) hide show
  1. package/README.md +440 -0
  2. package/dist/agents.d.ts +8 -0
  3. package/dist/agents.js +82 -0
  4. package/dist/audit.d.ts +27 -0
  5. package/dist/audit.js +55 -0
  6. package/dist/compliance.d.ts +30 -0
  7. package/dist/compliance.js +108 -0
  8. package/dist/config.d.ts +11 -0
  9. package/dist/config.js +43 -0
  10. package/dist/examples.d.ts +6 -0
  11. package/dist/examples.js +40 -0
  12. package/dist/exports.d.ts +13 -0
  13. package/dist/exports.js +6 -0
  14. package/dist/hooks.d.ts +24 -0
  15. package/dist/hooks.js +108 -0
  16. package/dist/index.d.ts +2 -0
  17. package/dist/index.js +542 -0
  18. package/dist/knowledge.d.ts +17 -0
  19. package/dist/knowledge.js +55 -0
  20. package/dist/loader.d.ts +64 -0
  21. package/dist/loader.js +222 -0
  22. package/dist/sandbox.d.ts +28 -0
  23. package/dist/sandbox.js +54 -0
  24. package/dist/sdk-hooks.d.ts +8 -0
  25. package/dist/sdk-hooks.js +31 -0
  26. package/dist/sdk-types.d.ts +127 -0
  27. package/dist/sdk-types.js +1 -0
  28. package/dist/sdk.d.ts +6 -0
  29. package/dist/sdk.js +444 -0
  30. package/dist/session.d.ts +15 -0
  31. package/dist/session.js +127 -0
  32. package/dist/skills.d.ts +18 -0
  33. package/dist/skills.js +104 -0
  34. package/dist/tool-loader.d.ts +3 -0
  35. package/dist/tool-loader.js +138 -0
  36. package/dist/tools/cli.d.ts +3 -0
  37. package/dist/tools/cli.js +86 -0
  38. package/dist/tools/index.d.ts +13 -0
  39. package/dist/tools/index.js +29 -0
  40. package/dist/tools/memory.d.ts +3 -0
  41. package/dist/tools/memory.js +128 -0
  42. package/dist/tools/read.d.ts +3 -0
  43. package/dist/tools/read.js +46 -0
  44. package/dist/tools/sandbox-cli.d.ts +4 -0
  45. package/dist/tools/sandbox-cli.js +48 -0
  46. package/dist/tools/sandbox-memory.d.ts +4 -0
  47. package/dist/tools/sandbox-memory.js +117 -0
  48. package/dist/tools/sandbox-read.d.ts +4 -0
  49. package/dist/tools/sandbox-read.js +25 -0
  50. package/dist/tools/sandbox-write.d.ts +4 -0
  51. package/dist/tools/sandbox-write.js +26 -0
  52. package/dist/tools/shared.d.ts +38 -0
  53. package/dist/tools/shared.js +69 -0
  54. package/dist/tools/write.d.ts +3 -0
  55. package/dist/tools/write.js +28 -0
  56. package/dist/workflows.d.ts +8 -0
  57. package/dist/workflows.js +81 -0
  58. 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
+ }
@@ -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
+ }
@@ -0,0 +1,6 @@
1
+ export interface ExampleEntry {
2
+ name: string;
3
+ content: string;
4
+ }
5
+ export declare function loadExamples(agentDir: string): Promise<ExampleEntry[]>;
6
+ export declare function formatExamplesForPrompt(examples: ExampleEntry[]): string;
@@ -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";
@@ -0,0 +1,6 @@
1
+ // SDK core
2
+ export { query, tool } from "./sdk.js";
3
+ export { createSandboxContext } from "./sandbox.js";
4
+ export { initLocalSession } from "./session.js";
5
+ // Loader (escape hatch)
6
+ export { loadAgent } from "./loader.js";
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};