terry-core 0.1.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.
@@ -0,0 +1,3 @@
1
+ import type { AuditEntry } from "./types.js";
2
+ export declare function appendAudit(entry: AuditEntry): Promise<void>;
3
+ export declare function readAuditLines(limit?: number): Promise<string[]>;
package/dist/audit.js ADDED
@@ -0,0 +1,17 @@
1
+ import fs from "node:fs/promises";
2
+ import { auditLogFile, ensureTerryDirs } from "./config.js";
3
+ export async function appendAudit(entry) {
4
+ await ensureTerryDirs();
5
+ await fs.appendFile(auditLogFile(), `${JSON.stringify(entry)}\n`, "utf8");
6
+ }
7
+ export async function readAuditLines(limit = 200) {
8
+ await ensureTerryDirs();
9
+ try {
10
+ const raw = await fs.readFile(auditLogFile(), "utf8");
11
+ const lines = raw.trim().split("\n");
12
+ return lines.slice(Math.max(0, lines.length - limit));
13
+ }
14
+ catch {
15
+ return [];
16
+ }
17
+ }
@@ -0,0 +1,13 @@
1
+ import { type WorkspaceSettings } from "./types.js";
2
+ export declare function terryHomeDir(): string;
3
+ export declare function workspacesFile(): string;
4
+ export declare function auditLogFile(): string;
5
+ export declare function usageDir(): string;
6
+ export declare function cloudSyncConfigFile(): string;
7
+ export declare function ensureTerryDirs(): Promise<void>;
8
+ type WorkspaceMap = Record<string, WorkspaceSettings>;
9
+ export declare function loadWorkspaceMap(): Promise<WorkspaceMap>;
10
+ export declare function saveWorkspaceMap(map: WorkspaceMap): Promise<void>;
11
+ export declare function getWorkspaceSettings(workspacePath: string): Promise<WorkspaceSettings>;
12
+ export declare function setWorkspaceSettings(workspacePath: string, patch: Partial<WorkspaceSettings>): Promise<WorkspaceSettings>;
13
+ export {};
package/dist/config.js ADDED
@@ -0,0 +1,107 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import os from "node:os";
4
+ const DEFAULT_MODE = "general";
5
+ const DEFAULT_EXECUTION_MODE = "safe";
6
+ const DEFAULT_TOOLS = {
7
+ listDirectory: "enabled",
8
+ readFile: "enabled",
9
+ writeFile: "enabled",
10
+ searchInFiles: "enabled",
11
+ gitStatus: "enabled",
12
+ gitDiff: "enabled",
13
+ runCommand: "enabled"
14
+ };
15
+ export function terryHomeDir() {
16
+ return path.join(os.homedir(), ".terry");
17
+ }
18
+ export function workspacesFile() {
19
+ return path.join(terryHomeDir(), "workspaces.json");
20
+ }
21
+ export function auditLogFile() {
22
+ return path.join(terryHomeDir(), "audit.log.jsonl");
23
+ }
24
+ export function usageDir() {
25
+ return path.join(terryHomeDir(), "usage");
26
+ }
27
+ export function cloudSyncConfigFile() {
28
+ return path.join(terryHomeDir(), "cloud-sync.json");
29
+ }
30
+ export async function ensureTerryDirs() {
31
+ await fs.mkdir(terryHomeDir(), { recursive: true });
32
+ await fs.mkdir(usageDir(), { recursive: true });
33
+ }
34
+ export async function loadWorkspaceMap() {
35
+ await ensureTerryDirs();
36
+ try {
37
+ const raw = await fs.readFile(workspacesFile(), "utf8");
38
+ return JSON.parse(raw);
39
+ }
40
+ catch {
41
+ return {};
42
+ }
43
+ }
44
+ export async function saveWorkspaceMap(map) {
45
+ await ensureTerryDirs();
46
+ await fs.writeFile(workspacesFile(), JSON.stringify(map, null, 2), "utf8");
47
+ }
48
+ export async function getWorkspaceSettings(workspacePath) {
49
+ const map = await loadWorkspaceMap();
50
+ const key = path.resolve(workspacePath);
51
+ if (!map[key]) {
52
+ map[key] = {
53
+ workspacePath: key,
54
+ dryRun: false,
55
+ mode: DEFAULT_MODE,
56
+ executionMode: DEFAULT_EXECUTION_MODE,
57
+ tools: { ...DEFAULT_TOOLS }
58
+ };
59
+ await saveWorkspaceMap(map);
60
+ }
61
+ else if (!map[key].mode || !map[key].executionMode) {
62
+ map[key] = {
63
+ ...map[key],
64
+ mode: DEFAULT_MODE,
65
+ executionMode: map[key].executionMode ?? DEFAULT_EXECUTION_MODE,
66
+ tools: { ...DEFAULT_TOOLS, ...map[key].tools }
67
+ };
68
+ await saveWorkspaceMap(map);
69
+ }
70
+ else {
71
+ const levels = Object.values(map[key].tools ?? {});
72
+ const allDisabled = levels.length > 0 && levels.every((level) => level === "disabled");
73
+ const legacyPattern = map[key].tools?.listDirectory === "disabled" &&
74
+ map[key].tools?.readFile === "disabled" &&
75
+ map[key].tools?.searchInFiles === "disabled" &&
76
+ map[key].tools?.gitStatus === "disabled" &&
77
+ map[key].tools?.gitDiff === "disabled" &&
78
+ map[key].tools?.writeFile === "ask" &&
79
+ map[key].tools?.runCommand === "ask";
80
+ if (allDisabled || legacyPattern) {
81
+ map[key] = {
82
+ ...map[key],
83
+ tools: { ...DEFAULT_TOOLS }
84
+ };
85
+ await saveWorkspaceMap(map);
86
+ }
87
+ }
88
+ return map[key];
89
+ }
90
+ export async function setWorkspaceSettings(workspacePath, patch) {
91
+ const map = await loadWorkspaceMap();
92
+ const key = path.resolve(workspacePath);
93
+ const current = map[key] ?? {
94
+ workspacePath: key,
95
+ dryRun: false,
96
+ mode: DEFAULT_MODE,
97
+ executionMode: DEFAULT_EXECUTION_MODE,
98
+ tools: { ...DEFAULT_TOOLS }
99
+ };
100
+ map[key] = {
101
+ ...current,
102
+ ...patch,
103
+ tools: { ...current.tools, ...(patch.tools ?? {}) }
104
+ };
105
+ await saveWorkspaceMap(map);
106
+ return map[key];
107
+ }
@@ -0,0 +1,3 @@
1
+ import type { ActionExplanation } from "./types.js";
2
+ export declare function setLastExplanation(workspacePath: string, explanation: ActionExplanation): Promise<void>;
3
+ export declare function getLastExplanation(workspacePath: string): Promise<ActionExplanation | null>;
@@ -0,0 +1,11 @@
1
+ import { workspaceDataFile, readJsonFile, writeJsonFile, ensureWorkspaceDataDirs } from "./workspace-data.js";
2
+ function explainPath(workspacePath) {
3
+ return workspaceDataFile(workspacePath, "explain-last.json");
4
+ }
5
+ export async function setLastExplanation(workspacePath, explanation) {
6
+ await ensureWorkspaceDataDirs(workspacePath);
7
+ await writeJsonFile(explainPath(workspacePath), explanation);
8
+ }
9
+ export async function getLastExplanation(workspacePath) {
10
+ return readJsonFile(explainPath(workspacePath), null);
11
+ }
@@ -0,0 +1,2 @@
1
+ import type { HealthReport } from "./types.js";
2
+ export declare function runWorkspaceHealth(workspacePath: string): Promise<HealthReport>;
package/dist/health.js ADDED
@@ -0,0 +1,105 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { execFile } from "node:child_process";
4
+ import { promisify } from "node:util";
5
+ const execFileAsync = promisify(execFile);
6
+ async function runNpm(args, cwd) {
7
+ if (process.platform === "win32") {
8
+ return execFileAsync("cmd.exe", ["/d", "/s", "/c", `npm ${args.join(" ")}`], { cwd });
9
+ }
10
+ return execFileAsync("npm", args, { cwd });
11
+ }
12
+ async function readPackageJson(workspacePath) {
13
+ try {
14
+ const raw = await fs.readFile(path.join(workspacePath, "package.json"), "utf8");
15
+ return JSON.parse(raw);
16
+ }
17
+ catch {
18
+ return null;
19
+ }
20
+ }
21
+ function scoreFromChecks(checks) {
22
+ let score = 100;
23
+ for (const check of checks) {
24
+ if (check.status === "warn")
25
+ score -= 10;
26
+ if (check.status === "fail")
27
+ score -= 25;
28
+ }
29
+ return Math.max(0, score);
30
+ }
31
+ function recommendationsFromChecks(checks) {
32
+ const out = [];
33
+ for (const check of checks) {
34
+ if (check.status === "fail")
35
+ out.push(`Fix failing check: ${check.name}`);
36
+ if (check.status === "warn")
37
+ out.push(`Review warning: ${check.name}`);
38
+ }
39
+ return out.slice(0, 8);
40
+ }
41
+ export async function runWorkspaceHealth(workspacePath) {
42
+ const checks = [];
43
+ const pkg = await readPackageJson(workspacePath);
44
+ try {
45
+ const { stdout } = await execFileAsync("git", ["status", "--short"], { cwd: workspacePath });
46
+ const dirty = stdout.trim().length > 0;
47
+ checks.push({
48
+ name: "git_clean_tree",
49
+ status: dirty ? "warn" : "pass",
50
+ detail: dirty ? "Working tree has local modifications." : "Working tree clean."
51
+ });
52
+ }
53
+ catch {
54
+ checks.push({ name: "git_available", status: "warn", detail: "Git status unavailable." });
55
+ }
56
+ const scripts = pkg?.scripts ?? {};
57
+ for (const scriptName of ["lint", "test", "build"]) {
58
+ if (!scripts[scriptName]) {
59
+ checks.push({
60
+ name: `script_${scriptName}`,
61
+ status: "warn",
62
+ detail: `No npm script "${scriptName}" found.`
63
+ });
64
+ continue;
65
+ }
66
+ try {
67
+ await runNpm(["run", scriptName, "--if-present"], workspacePath);
68
+ checks.push({
69
+ name: `script_${scriptName}`,
70
+ status: "pass",
71
+ detail: `npm run ${scriptName} succeeded.`
72
+ });
73
+ }
74
+ catch {
75
+ checks.push({
76
+ name: `script_${scriptName}`,
77
+ status: "fail",
78
+ detail: `npm run ${scriptName} failed.`
79
+ });
80
+ }
81
+ }
82
+ try {
83
+ const { stdout } = await runNpm(["outdated", "--json"], workspacePath);
84
+ const parsed = (stdout ? JSON.parse(stdout) : {});
85
+ const count = Object.keys(parsed ?? {}).length;
86
+ checks.push({
87
+ name: "dependencies_freshness",
88
+ status: count > 0 ? "warn" : "pass",
89
+ detail: count > 0 ? `${count} dependencies are outdated.` : "Dependencies are up to date."
90
+ });
91
+ }
92
+ catch {
93
+ checks.push({
94
+ name: "dependencies_freshness",
95
+ status: "warn",
96
+ detail: "Could not determine outdated dependencies."
97
+ });
98
+ }
99
+ return {
100
+ score: scoreFromChecks(checks),
101
+ checks,
102
+ recommendations: recommendationsFromChecks(checks),
103
+ generatedAt: new Date().toISOString()
104
+ };
105
+ }
@@ -0,0 +1,17 @@
1
+ export * from "./types.js";
2
+ export * from "./config.js";
3
+ export * from "./sandbox.js";
4
+ export * from "./audit.js";
5
+ export * from "./tools.js";
6
+ export * from "./permissions.js";
7
+ export * from "./orchestrator.js";
8
+ export * from "./workspace-data.js";
9
+ export * from "./policy.js";
10
+ export * from "./patches.js";
11
+ export * from "./explain.js";
12
+ export * from "./sessions.js";
13
+ export * from "./repo.js";
14
+ export * from "./status.js";
15
+ export * from "./templates.js";
16
+ export * from "./health.js";
17
+ export * from "./usage.js";
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ export * from "./types.js";
2
+ export * from "./config.js";
3
+ export * from "./sandbox.js";
4
+ export * from "./audit.js";
5
+ export * from "./tools.js";
6
+ export * from "./permissions.js";
7
+ export * from "./orchestrator.js";
8
+ export * from "./workspace-data.js";
9
+ export * from "./policy.js";
10
+ export * from "./patches.js";
11
+ export * from "./explain.js";
12
+ export * from "./sessions.js";
13
+ export * from "./repo.js";
14
+ export * from "./status.js";
15
+ export * from "./templates.js";
16
+ export * from "./health.js";
17
+ export * from "./usage.js";
@@ -0,0 +1,2 @@
1
+ import type { DecideNextAction } from "./types.js";
2
+ export declare const rulesOrchestrator: DecideNextAction;
@@ -0,0 +1,58 @@
1
+ const TOOL_HINTS = [
2
+ { tool: "listDirectory", match: /\b(list|ls|folders?|files?)\b/i },
3
+ { tool: "readFile", match: /\b(read|open|show)\b.*\b(file|md|json|ts|js)\b/i },
4
+ { tool: "writeFile", match: /\b(write|create|update|edit|save)\b.*\b(file|doc|readme|json|ts|js)\b/i },
5
+ { tool: "searchInFiles", match: /\b(search|find|grep|look for)\b/i },
6
+ { tool: "gitStatus", match: /\bgit status|repo status|what changed\b/i },
7
+ { tool: "gitDiff", match: /\bgit diff|show diff|changes\b/i },
8
+ { tool: "runCommand", match: /\brun|execute|command|npm|pnpm|yarn|test|build\b/i }
9
+ ];
10
+ function inferTool(content) {
11
+ for (const hint of TOOL_HINTS) {
12
+ if (hint.match.test(content))
13
+ return hint.tool;
14
+ }
15
+ return null;
16
+ }
17
+ function inferArgs(tool, content) {
18
+ if (tool === "searchInFiles") {
19
+ const phrase = content.split("search").pop()?.trim() || content;
20
+ return { query: phrase.replace(/^for\s+/i, "") };
21
+ }
22
+ if (tool === "runCommand") {
23
+ return { cmd: "echo", args: [content] };
24
+ }
25
+ if (tool === "listDirectory")
26
+ return { pathRelativeToWorkspace: "." };
27
+ if (tool === "gitStatus")
28
+ return {};
29
+ if (tool === "gitDiff")
30
+ return {};
31
+ if (tool === "readFile")
32
+ return { pathRelativeToWorkspace: "README.md" };
33
+ if (tool === "writeFile") {
34
+ return {
35
+ pathRelativeToWorkspace: "README.md",
36
+ content: `# Terry update\n\nGenerated from task:\n${content}\n`
37
+ };
38
+ }
39
+ return {};
40
+ }
41
+ export const rulesOrchestrator = (messages) => {
42
+ const lastUser = [...messages].reverse().find((m) => m.role === "user");
43
+ if (!lastUser)
44
+ return { kind: "summarize", summary: "No task provided." };
45
+ const tool = inferTool(lastUser.content);
46
+ if (!tool) {
47
+ return {
48
+ kind: "summarize",
49
+ summary: "I can help with workspace files, search, git, and command planning. Ask a concrete repo or file operation."
50
+ };
51
+ }
52
+ return {
53
+ kind: "ask_permission",
54
+ tool,
55
+ args: inferArgs(tool, lastUser.content),
56
+ reason: `Detected request likely needs tool: ${tool}.`
57
+ };
58
+ };
@@ -0,0 +1,12 @@
1
+ import type { PatchProposal } from "./types.js";
2
+ type PatchOptions = {
3
+ persist?: boolean;
4
+ preApproveAll?: boolean;
5
+ };
6
+ export declare function createPatchProposal(workspacePath: string, relPath: string, updatedContent: string, options?: PatchOptions): Promise<PatchProposal>;
7
+ export declare function getPatchProposal(workspacePath: string, patchId: string): Promise<PatchProposal | null>;
8
+ export declare function listPatchProposals(workspacePath: string, status?: PatchProposal["status"]): Promise<PatchProposal[]>;
9
+ export declare function setPatchHunkApproval(workspacePath: string, patchId: string, hunkIds: string[] | "all", approved: boolean): Promise<PatchProposal>;
10
+ export declare function applyPatchProposal(workspacePath: string, patchId: string): Promise<PatchProposal>;
11
+ export declare function rejectPatchProposal(workspacePath: string, patchId: string): Promise<PatchProposal>;
12
+ export {};
@@ -0,0 +1,126 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { createPatch, parsePatch } from "diff";
4
+ import { resolveInWorkspace } from "./sandbox.js";
5
+ import { ensureWorkspaceDataDirs, listJsonFiles, readJsonFile, workspaceSubdir, writeJsonFile } from "./workspace-data.js";
6
+ function patchDir(workspacePath) {
7
+ return workspaceSubdir(workspacePath, "pending-patches");
8
+ }
9
+ function patchFile(workspacePath, patchId) {
10
+ return path.join(patchDir(workspacePath), `${patchId}.json`);
11
+ }
12
+ function buildHunkId(index) {
13
+ return `hunk-${index + 1}`;
14
+ }
15
+ function inferRiskLevel(filePath, before, after) {
16
+ const ext = path.extname(filePath).toLowerCase();
17
+ const changedLineCount = Math.abs(after.split("\n").length - before.split("\n").length);
18
+ if (ext === ".env" || ext.endsWith("config") || ext === ".yml" || ext === ".yaml")
19
+ return "high";
20
+ if (changedLineCount > 80)
21
+ return "high";
22
+ if (changedLineCount > 20)
23
+ return "medium";
24
+ return "low";
25
+ }
26
+ function nowIso() {
27
+ return new Date().toISOString();
28
+ }
29
+ export async function createPatchProposal(workspacePath, relPath, updatedContent, options = {}) {
30
+ await ensureWorkspaceDataDirs(workspacePath);
31
+ const fullPath = resolveInWorkspace(workspacePath, relPath);
32
+ const originalContent = await fs.readFile(fullPath, "utf8").catch(() => "");
33
+ const unifiedDiff = createPatch(relPath, originalContent, updatedContent, "before", "after", { context: 3 });
34
+ const parsed = parsePatch(unifiedDiff);
35
+ const first = parsed[0];
36
+ const hunks = (first?.hunks ?? []).map((hunk, index) => {
37
+ const before = [];
38
+ const after = [];
39
+ for (const line of hunk.lines) {
40
+ if (line.startsWith("-") && !line.startsWith("---"))
41
+ before.push(line.slice(1));
42
+ if (line.startsWith("+") && !line.startsWith("+++"))
43
+ after.push(line.slice(1));
44
+ }
45
+ return {
46
+ id: buildHunkId(index),
47
+ header: `@@ -${hunk.oldStart},${hunk.oldLines} +${hunk.newStart},${hunk.newLines} @@`,
48
+ before,
49
+ after,
50
+ approved: options.preApproveAll ?? false
51
+ };
52
+ });
53
+ const patchId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
54
+ const proposal = {
55
+ id: patchId,
56
+ filePath: relPath,
57
+ unifiedDiff,
58
+ hunks,
59
+ riskLevel: inferRiskLevel(relPath, originalContent, updatedContent),
60
+ status: "pending",
61
+ createdAt: nowIso(),
62
+ updatedAt: nowIso(),
63
+ originalContent,
64
+ updatedContent
65
+ };
66
+ if (options.persist ?? true) {
67
+ await writeJsonFile(patchFile(workspacePath, patchId), proposal);
68
+ }
69
+ return proposal;
70
+ }
71
+ export async function getPatchProposal(workspacePath, patchId) {
72
+ return readJsonFile(patchFile(workspacePath, patchId), null);
73
+ }
74
+ export async function listPatchProposals(workspacePath, status) {
75
+ await ensureWorkspaceDataDirs(workspacePath);
76
+ const fileNames = await listJsonFiles(patchDir(workspacePath));
77
+ const proposals = [];
78
+ for (const name of fileNames) {
79
+ const filePath = path.join(patchDir(workspacePath), name);
80
+ const value = await readJsonFile(filePath, null);
81
+ if (!value)
82
+ continue;
83
+ if (status && value.status !== status)
84
+ continue;
85
+ proposals.push(value);
86
+ }
87
+ return proposals.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
88
+ }
89
+ export async function setPatchHunkApproval(workspacePath, patchId, hunkIds, approved) {
90
+ const proposal = await getPatchProposal(workspacePath, patchId);
91
+ if (!proposal)
92
+ throw new Error(`Patch proposal not found: ${patchId}`);
93
+ proposal.hunks = proposal.hunks.map((hunk) => {
94
+ const match = hunkIds === "all" ? true : hunkIds.includes(hunk.id);
95
+ return match ? { ...hunk, approved } : hunk;
96
+ });
97
+ proposal.updatedAt = nowIso();
98
+ await writeJsonFile(patchFile(workspacePath, patchId), proposal);
99
+ return proposal;
100
+ }
101
+ export async function applyPatchProposal(workspacePath, patchId) {
102
+ const proposal = await getPatchProposal(workspacePath, patchId);
103
+ if (!proposal)
104
+ throw new Error(`Patch proposal not found: ${patchId}`);
105
+ if (proposal.status !== "pending")
106
+ throw new Error(`Patch is already ${proposal.status}.`);
107
+ const unresolved = proposal.hunks.filter((hunk) => !hunk.approved);
108
+ if (unresolved.length > 0)
109
+ throw new Error(`Patch has ${unresolved.length} unapproved hunks.`);
110
+ const fullPath = resolveInWorkspace(workspacePath, proposal.filePath);
111
+ await fs.mkdir(path.dirname(fullPath), { recursive: true });
112
+ await fs.writeFile(fullPath, proposal.updatedContent, "utf8");
113
+ proposal.status = "applied";
114
+ proposal.updatedAt = nowIso();
115
+ await writeJsonFile(patchFile(workspacePath, patchId), proposal);
116
+ return proposal;
117
+ }
118
+ export async function rejectPatchProposal(workspacePath, patchId) {
119
+ const proposal = await getPatchProposal(workspacePath, patchId);
120
+ if (!proposal)
121
+ throw new Error(`Patch proposal not found: ${patchId}`);
122
+ proposal.status = "rejected";
123
+ proposal.updatedAt = nowIso();
124
+ await writeJsonFile(patchFile(workspacePath, patchId), proposal);
125
+ return proposal;
126
+ }
@@ -0,0 +1,6 @@
1
+ import type { PermissionLevel, ToolName, WorkspaceSettings } from "./types.js";
2
+ export declare function getToolPermission(settings: WorkspaceSettings, tool: ToolName): PermissionLevel;
3
+ export declare function shouldAskEveryTime(tool: ToolName): tool is "writeFile" | "runCommand";
4
+ export declare function isToolAllowedWithoutPrompt(settings: WorkspaceSettings, tool: ToolName): boolean;
5
+ export declare function isToolEnabled(settings: WorkspaceSettings, tool: ToolName): boolean;
6
+ export declare function toolRiskLevel(tool: ToolName): "low" | "medium" | "high";
@@ -0,0 +1,20 @@
1
+ export function getToolPermission(settings, tool) {
2
+ return settings.tools[tool] ?? "disabled";
3
+ }
4
+ export function shouldAskEveryTime(tool) {
5
+ return tool === "runCommand" || tool === "writeFile";
6
+ }
7
+ export function isToolAllowedWithoutPrompt(settings, tool) {
8
+ const level = getToolPermission(settings, tool);
9
+ return level === "enabled" && !shouldAskEveryTime(tool);
10
+ }
11
+ export function isToolEnabled(settings, tool) {
12
+ return getToolPermission(settings, tool) !== "disabled";
13
+ }
14
+ export function toolRiskLevel(tool) {
15
+ if (tool === "runCommand")
16
+ return "high";
17
+ if (tool === "writeFile")
18
+ return "medium";
19
+ return "low";
20
+ }
@@ -0,0 +1,9 @@
1
+ import type { ExecutionMode, ToolName, WorkspaceSettings } from "./types.js";
2
+ export declare function isReadOnlyTool(tool: ToolName): boolean;
3
+ export declare function isCommandAllowlisted(command: string): boolean;
4
+ export declare function executionMode(settings: WorkspaceSettings): ExecutionMode;
5
+ export declare function shouldExecuteInMode(settings: WorkspaceSettings, tool: ToolName, args: unknown): {
6
+ execute: boolean;
7
+ ask: boolean;
8
+ reason: string;
9
+ };
package/dist/policy.js ADDED
@@ -0,0 +1,33 @@
1
+ const READ_ONLY_TOOLS = ["listDirectory", "readFile", "searchInFiles", "gitStatus", "gitDiff"];
2
+ const COMMAND_ALLOWLIST = new Set(["git", "npm", "pnpm", "yarn", "node", "npx"]);
3
+ export function isReadOnlyTool(tool) {
4
+ return READ_ONLY_TOOLS.includes(tool);
5
+ }
6
+ export function isCommandAllowlisted(command) {
7
+ return COMMAND_ALLOWLIST.has(command.trim().toLowerCase());
8
+ }
9
+ export function executionMode(settings) {
10
+ return settings.executionMode;
11
+ }
12
+ export function shouldExecuteInMode(settings, tool, args) {
13
+ const mode = executionMode(settings);
14
+ if (mode === "plan") {
15
+ return { execute: false, ask: false, reason: "Plan mode does not execute tools." };
16
+ }
17
+ if (mode === "safe") {
18
+ if (isReadOnlyTool(tool))
19
+ return { execute: true, ask: false, reason: "Safe mode auto-executes read-only tools." };
20
+ return { execute: false, ask: true, reason: "Safe mode requires approval for writes and commands." };
21
+ }
22
+ if (tool === "runCommand") {
23
+ const cmd = args?.cmd ?? "";
24
+ if (!isCommandAllowlisted(cmd)) {
25
+ return {
26
+ execute: false,
27
+ ask: true,
28
+ reason: `Autonomous mode requires approval for non-allowlisted command: ${cmd || "unknown"}.`
29
+ };
30
+ }
31
+ }
32
+ return { execute: true, ask: false, reason: "Autonomous mode executes approved plan directly." };
33
+ }
package/dist/repo.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import type { ImpactResult, RepoGraph } from "./types.js";
2
+ export declare function scanWorkspaceGraph(workspacePath: string): Promise<RepoGraph>;
3
+ export declare function getWorkspaceGraph(workspacePath: string): Promise<RepoGraph | null>;
4
+ export declare function graphToTree(graph: RepoGraph): string;
5
+ export declare function suggestTarget(graph: RepoGraph): string | undefined;
6
+ export declare function calculateImpact(workspacePath: string, target: string): Promise<ImpactResult>;