@within-7/minto 0.2.0 → 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/dist/commands/agents/AgentsCommand.js +22 -24
- package/dist/commands/agents/AgentsCommand.js.map +2 -2
- package/dist/commands/context.js +2 -1
- package/dist/commands/context.js.map +2 -2
- package/dist/commands/export.js +2 -1
- package/dist/commands/export.js.map +2 -2
- package/dist/commands/mcp-interactive.js +7 -6
- package/dist/commands/mcp-interactive.js.map +2 -2
- package/dist/commands/model.js +3 -2
- package/dist/commands/model.js.map +2 -2
- package/dist/commands/permissions.js +4 -3
- package/dist/commands/permissions.js.map +2 -2
- package/dist/commands/plugin/AddMarketplaceForm.js +3 -2
- package/dist/commands/plugin/AddMarketplaceForm.js.map +2 -2
- package/dist/commands/plugin/ConfirmDialog.js +2 -1
- package/dist/commands/plugin/ConfirmDialog.js.map +2 -2
- package/dist/commands/plugin/ErrorView.js +2 -1
- package/dist/commands/plugin/ErrorView.js.map +2 -2
- package/dist/commands/plugin/InstalledPluginsByMarketplace.js +5 -4
- package/dist/commands/plugin/InstalledPluginsByMarketplace.js.map +2 -2
- package/dist/commands/plugin/InstalledPluginsManager.js +5 -4
- package/dist/commands/plugin/InstalledPluginsManager.js.map +2 -2
- package/dist/commands/plugin/MainMenu.js +2 -1
- package/dist/commands/plugin/MainMenu.js.map +2 -2
- package/dist/commands/plugin/MarketplaceManager.js +5 -4
- package/dist/commands/plugin/MarketplaceManager.js.map +2 -2
- package/dist/commands/plugin/MarketplaceSelector.js +4 -3
- package/dist/commands/plugin/MarketplaceSelector.js.map +2 -2
- package/dist/commands/plugin/PlaceholderScreen.js +3 -2
- package/dist/commands/plugin/PlaceholderScreen.js.map +2 -2
- package/dist/commands/plugin/PluginBrowser.js +6 -5
- package/dist/commands/plugin/PluginBrowser.js.map +2 -2
- package/dist/commands/plugin/PluginDetailsInstall.js +5 -4
- package/dist/commands/plugin/PluginDetailsInstall.js.map +2 -2
- package/dist/commands/plugin/PluginDetailsManage.js +4 -3
- package/dist/commands/plugin/PluginDetailsManage.js.map +2 -2
- package/dist/commands/plugin.js +16 -15
- package/dist/commands/plugin.js.map +2 -2
- package/dist/commands/sandbox.js +4 -3
- package/dist/commands/sandbox.js.map +2 -2
- package/dist/commands/setup.js +2 -1
- package/dist/commands/setup.js.map +2 -2
- package/dist/commands/status.js +2 -1
- package/dist/commands/status.js.map +2 -2
- package/dist/commands/undo.js +245 -0
- package/dist/commands/undo.js.map +7 -0
- package/dist/commands.js +2 -0
- package/dist/commands.js.map +2 -2
- package/dist/components/AgentThinkingBlock.js +1 -1
- package/dist/components/AgentThinkingBlock.js.map +2 -2
- package/dist/components/AsciiLogo.js +7 -8
- package/dist/components/AsciiLogo.js.map +2 -2
- package/dist/components/AskUserQuestionDialog/AskUserQuestionDialog.js +3 -2
- package/dist/components/AskUserQuestionDialog/AskUserQuestionDialog.js.map +2 -2
- package/dist/components/AskUserQuestionDialog/QuestionView.js +2 -1
- package/dist/components/AskUserQuestionDialog/QuestionView.js.map +2 -2
- package/dist/components/CollapsibleHint.js +2 -1
- package/dist/components/CollapsibleHint.js.map +2 -2
- package/dist/components/Config.js +3 -2
- package/dist/components/Config.js.map +2 -2
- package/dist/components/ConsoleOAuthFlow.js +2 -1
- package/dist/components/ConsoleOAuthFlow.js.map +2 -2
- package/dist/components/Cost.js +2 -1
- package/dist/components/Cost.js.map +2 -2
- package/dist/components/HeaderBar.js +13 -8
- package/dist/components/HeaderBar.js.map +2 -2
- package/dist/components/HistorySearchOverlay.js +4 -3
- package/dist/components/HistorySearchOverlay.js.map +2 -2
- package/dist/components/HotkeyHelpPanel.js +8 -11
- package/dist/components/HotkeyHelpPanel.js.map +2 -2
- package/dist/components/InvalidConfigDialog.js +2 -1
- package/dist/components/InvalidConfigDialog.js.map +2 -2
- package/dist/components/Logo.js +23 -67
- package/dist/components/Logo.js.map +2 -2
- package/dist/components/MCPServerApprovalDialog.js +2 -1
- package/dist/components/MCPServerApprovalDialog.js.map +2 -2
- package/dist/components/MCPServerDialogCopy.js +2 -1
- package/dist/components/MCPServerDialogCopy.js.map +2 -2
- package/dist/components/MCPServerMultiselectDialog.js +2 -1
- package/dist/components/MCPServerMultiselectDialog.js.map +2 -2
- package/dist/components/MessageSelector.js +4 -3
- package/dist/components/MessageSelector.js.map +2 -2
- package/dist/components/ModeIndicator.js +2 -1
- package/dist/components/ModeIndicator.js.map +2 -2
- package/dist/components/ModelConfig.js +4 -3
- package/dist/components/ModelConfig.js.map +2 -2
- package/dist/components/ModelListManager.js +4 -3
- package/dist/components/ModelListManager.js.map +2 -2
- package/dist/components/ModelSelector/ModelSelector.js +26 -13
- package/dist/components/ModelSelector/ModelSelector.js.map +2 -2
- package/dist/components/Onboarding.js +3 -2
- package/dist/components/Onboarding.js.map +2 -2
- package/dist/components/OperationSummary.js +130 -0
- package/dist/components/OperationSummary.js.map +7 -0
- package/dist/components/PromptInput.js +88 -75
- package/dist/components/PromptInput.js.map +2 -2
- package/dist/components/SensitiveFileWarning.js +31 -0
- package/dist/components/SensitiveFileWarning.js.map +7 -0
- package/dist/components/Spinner.js +71 -22
- package/dist/components/Spinner.js.map +2 -2
- package/dist/components/StructuredDiff.js +6 -8
- package/dist/components/StructuredDiff.js.map +2 -2
- package/dist/components/SubagentBlock.js +4 -2
- package/dist/components/SubagentBlock.js.map +2 -2
- package/dist/components/SubagentProgress.js +7 -4
- package/dist/components/SubagentProgress.js.map +2 -2
- package/dist/components/TaskCard.js +14 -11
- package/dist/components/TaskCard.js.map +2 -2
- package/dist/components/TextInput.js +9 -1
- package/dist/components/TextInput.js.map +2 -2
- package/dist/components/TodoPanel.js +44 -26
- package/dist/components/TodoPanel.js.map +2 -2
- package/dist/components/ToolUseLoader.js +2 -2
- package/dist/components/ToolUseLoader.js.map +2 -2
- package/dist/components/TreeConnector.js +4 -3
- package/dist/components/TreeConnector.js.map +2 -2
- package/dist/components/TrustDialog.js +2 -1
- package/dist/components/TrustDialog.js.map +2 -2
- package/dist/components/binary-feedback/BinaryFeedbackView.js +2 -1
- package/dist/components/binary-feedback/BinaryFeedbackView.js.map +2 -2
- package/dist/components/messages/AssistantTextMessage.js +17 -9
- package/dist/components/messages/AssistantTextMessage.js.map +2 -2
- package/dist/components/messages/AssistantToolUseMessage.js +8 -4
- package/dist/components/messages/AssistantToolUseMessage.js.map +2 -2
- package/dist/components/messages/GroupRenderer.js +2 -1
- package/dist/components/messages/GroupRenderer.js.map +2 -2
- package/dist/components/messages/NestedTasksPreview.js +13 -1
- package/dist/components/messages/NestedTasksPreview.js.map +2 -2
- package/dist/components/messages/ParallelTasksGroupView.js +4 -3
- package/dist/components/messages/ParallelTasksGroupView.js.map +2 -2
- package/dist/components/messages/TaskInModuleView.js +35 -15
- package/dist/components/messages/TaskInModuleView.js.map +2 -2
- package/dist/components/messages/TaskOutputContent.js +9 -6
- package/dist/components/messages/TaskOutputContent.js.map +2 -2
- package/dist/components/messages/UserPromptMessage.js +2 -2
- package/dist/components/messages/UserPromptMessage.js.map +2 -2
- package/dist/constants/colors.js +90 -72
- package/dist/constants/colors.js.map +2 -2
- package/dist/constants/toolInputExamples.js +84 -0
- package/dist/constants/toolInputExamples.js.map +7 -0
- package/dist/core/backupManager.js +321 -0
- package/dist/core/backupManager.js.map +7 -0
- package/dist/core/costTracker.js +9 -18
- package/dist/core/costTracker.js.map +2 -2
- package/dist/core/gitAutoCommit.js +287 -0
- package/dist/core/gitAutoCommit.js.map +7 -0
- package/dist/core/index.js +3 -0
- package/dist/core/index.js.map +2 -2
- package/dist/core/operationTracker.js +212 -0
- package/dist/core/operationTracker.js.map +7 -0
- package/dist/core/permissions/rules/allowedToolsRule.js +1 -1
- package/dist/core/permissions/rules/allowedToolsRule.js.map +2 -2
- package/dist/core/permissions/rules/autoEscalationRule.js +5 -0
- package/dist/core/permissions/rules/autoEscalationRule.js.map +2 -2
- package/dist/core/permissions/rules/projectBoundaryRule.js +5 -0
- package/dist/core/permissions/rules/projectBoundaryRule.js.map +2 -2
- package/dist/core/permissions/rules/sensitivePathsRule.js +5 -0
- package/dist/core/permissions/rules/sensitivePathsRule.js.map +2 -2
- package/dist/core/tokenStats.js +9 -0
- package/dist/core/tokenStats.js.map +7 -0
- package/dist/core/tokenStatsManager.js +331 -0
- package/dist/core/tokenStatsManager.js.map +7 -0
- package/dist/entrypoints/cli.js +115 -87
- package/dist/entrypoints/cli.js.map +2 -2
- package/dist/hooks/useAgentTokenStats.js +72 -0
- package/dist/hooks/useAgentTokenStats.js.map +7 -0
- package/dist/hooks/useAgentTranscripts.js +30 -6
- package/dist/hooks/useAgentTranscripts.js.map +2 -2
- package/dist/hooks/useLogMessages.js +12 -1
- package/dist/hooks/useLogMessages.js.map +2 -2
- package/dist/i18n/locales/en.js +6 -5
- package/dist/i18n/locales/en.js.map +2 -2
- package/dist/i18n/locales/zh-CN.js +6 -5
- package/dist/i18n/locales/zh-CN.js.map +2 -2
- package/dist/i18n/types.js.map +1 -1
- package/dist/permissions.js +28 -1
- package/dist/permissions.js.map +2 -2
- package/dist/query.js +78 -4
- package/dist/query.js.map +3 -3
- package/dist/screens/REPL.js +23 -3
- package/dist/screens/REPL.js.map +2 -2
- package/dist/services/claude.js +54 -3
- package/dist/services/claude.js.map +2 -2
- package/dist/services/intelligentCompactor.js +1 -1
- package/dist/services/intelligentCompactor.js.map +2 -2
- package/dist/services/mcpClient.js +81 -25
- package/dist/services/mcpClient.js.map +2 -2
- package/dist/services/sandbox/filesystemBoundary.js +58 -17
- package/dist/services/sandbox/filesystemBoundary.js.map +2 -2
- package/dist/tools/AskExpertModelTool/AskExpertModelTool.js +3 -2
- package/dist/tools/AskExpertModelTool/AskExpertModelTool.js.map +2 -2
- package/dist/tools/AskUserQuestionTool/AskUserQuestionTool.js +2 -1
- package/dist/tools/AskUserQuestionTool/AskUserQuestionTool.js.map +2 -2
- package/dist/tools/BashTool/BashTool.js +22 -3
- package/dist/tools/BashTool/BashTool.js.map +2 -2
- package/dist/tools/BashTool/prompt.js +178 -34
- package/dist/tools/BashTool/prompt.js.map +2 -2
- package/dist/tools/FileEditTool/prompt.js +6 -3
- package/dist/tools/FileEditTool/prompt.js.map +2 -2
- package/dist/tools/FileWriteTool/prompt.js +4 -2
- package/dist/tools/FileWriteTool/prompt.js.map +2 -2
- package/dist/tools/MultiEditTool/prompt.js +5 -3
- package/dist/tools/MultiEditTool/prompt.js.map +2 -2
- package/dist/tools/NotebookEditTool/NotebookEditTool.js +2 -1
- package/dist/tools/NotebookEditTool/NotebookEditTool.js.map +2 -2
- package/dist/tools/PlanModeTool/EnterPlanModeTool.js +3 -2
- package/dist/tools/PlanModeTool/EnterPlanModeTool.js.map +2 -2
- package/dist/tools/PlanModeTool/ExitPlanModeTool.js +3 -2
- package/dist/tools/PlanModeTool/ExitPlanModeTool.js.map +2 -2
- package/dist/tools/PlanModeTool/prompt.js +1 -1
- package/dist/tools/PlanModeTool/prompt.js.map +1 -1
- package/dist/tools/SkillTool/SkillTool.js +4 -3
- package/dist/tools/SkillTool/SkillTool.js.map +2 -2
- package/dist/tools/SkillTool/prompt.js +1 -1
- package/dist/tools/SkillTool/prompt.js.map +1 -1
- package/dist/tools/TaskOutputTool/TaskOutputTool.js +3 -2
- package/dist/tools/TaskOutputTool/TaskOutputTool.js.map +2 -2
- package/dist/tools/TaskTool/TaskTool.js +8 -0
- package/dist/tools/TaskTool/TaskTool.js.map +2 -2
- package/dist/utils/CircuitBreaker.js +242 -0
- package/dist/utils/CircuitBreaker.js.map +7 -0
- package/dist/utils/ask.js +2 -0
- package/dist/utils/ask.js.map +2 -2
- package/dist/utils/config.js +47 -5
- package/dist/utils/config.js.map +2 -2
- package/dist/utils/credentials/CredentialStore.js +1 -0
- package/dist/utils/credentials/CredentialStore.js.map +7 -0
- package/dist/utils/credentials/EncryptedFileStore.js +157 -0
- package/dist/utils/credentials/EncryptedFileStore.js.map +7 -0
- package/dist/utils/credentials/index.js +37 -0
- package/dist/utils/credentials/index.js.map +7 -0
- package/dist/utils/credentials/migration.js +82 -0
- package/dist/utils/credentials/migration.js.map +7 -0
- package/dist/utils/markdown.js +13 -1
- package/dist/utils/markdown.js.map +2 -2
- package/dist/utils/permissions/filesystem.js +5 -1
- package/dist/utils/permissions/filesystem.js.map +2 -2
- package/dist/utils/safePath.js +132 -0
- package/dist/utils/safePath.js.map +7 -0
- package/dist/utils/sensitiveFiles.js +125 -0
- package/dist/utils/sensitiveFiles.js.map +7 -0
- package/dist/utils/taskDisplayUtils.js +9 -9
- package/dist/utils/taskDisplayUtils.js.map +2 -2
- package/dist/utils/theme.js +6 -6
- package/dist/utils/theme.js.map +1 -1
- package/dist/utils/toolRiskClassification.js +207 -0
- package/dist/utils/toolRiskClassification.js.map +7 -0
- package/dist/utils/tooling/safeRender.js +5 -4
- package/dist/utils/tooling/safeRender.js.map +2 -2
- package/dist/version.js +2 -2
- package/dist/version.js.map +1 -1
- package/package.json +9 -7
- package/dist/hooks/useCancelRequest.js +0 -31
- package/dist/hooks/useCancelRequest.js.map +0 -7
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { minimatch } from "minimatch";
|
|
2
|
+
import { getIsGit } from "../utils/git.js";
|
|
3
|
+
import { execFileNoThrow } from "../utils/execFileNoThrow.js";
|
|
4
|
+
import { getCwd } from "../utils/state.js";
|
|
5
|
+
import { operationTracker } from "./operationTracker.js";
|
|
6
|
+
const DEFAULT_GIT_CONFIG = {
|
|
7
|
+
enabled: false,
|
|
8
|
+
// Default OFF, user must enable
|
|
9
|
+
commitAfterTask: true,
|
|
10
|
+
excludePatterns: ["*.log", "node_modules/**", "dist/**", ".minto/**"],
|
|
11
|
+
commitMessagePrefix: "chore(minto): "
|
|
12
|
+
};
|
|
13
|
+
async function shouldAutoCommit(config) {
|
|
14
|
+
if (!config.enabled) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
const isGitRepo = await getIsGit();
|
|
18
|
+
if (!isGitRepo) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
const changes = await getChangedFiles();
|
|
22
|
+
if (changes.length === 0) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
const filesToCommit = filterExcludedFiles(changes, config.excludePatterns);
|
|
26
|
+
return filesToCommit.length > 0;
|
|
27
|
+
}
|
|
28
|
+
async function performAutoCommit(config, description) {
|
|
29
|
+
const isGitRepo = await getIsGit();
|
|
30
|
+
if (!isGitRepo) {
|
|
31
|
+
return {
|
|
32
|
+
success: false,
|
|
33
|
+
message: "Not a git repository"
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const allChanges = await getChangedFiles();
|
|
37
|
+
if (allChanges.length === 0) {
|
|
38
|
+
return {
|
|
39
|
+
success: false,
|
|
40
|
+
message: "No changes to commit"
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const filesToCommit = filterExcludedFiles(allChanges, config.excludePatterns);
|
|
44
|
+
if (filesToCommit.length === 0) {
|
|
45
|
+
return {
|
|
46
|
+
success: false,
|
|
47
|
+
message: "All changed files are excluded by patterns"
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const summary = await getChangeSummary(filesToCommit);
|
|
51
|
+
const stageResult = await stageFiles(filesToCommit.map((f) => f.path));
|
|
52
|
+
if (!stageResult.success) {
|
|
53
|
+
return {
|
|
54
|
+
success: false,
|
|
55
|
+
message: `Failed to stage files: ${stageResult.error}`
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const commitMessage = generateCommitMessage(config, summary, description);
|
|
59
|
+
const commitResult = await createCommit(commitMessage);
|
|
60
|
+
if (!commitResult.success) {
|
|
61
|
+
return {
|
|
62
|
+
success: false,
|
|
63
|
+
message: `Failed to create commit: ${commitResult.error}`
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
success: true,
|
|
68
|
+
message: `Committed ${filesToCommit.length} file(s)`,
|
|
69
|
+
commitHash: commitResult.hash,
|
|
70
|
+
filesCommitted: filesToCommit.map((f) => f.path)
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function generateCommitMessage(config, summary, description) {
|
|
74
|
+
const sanitizedDesc = description.trim().slice(0, 72);
|
|
75
|
+
const title = `${config.commitMessagePrefix}${sanitizedDesc}`;
|
|
76
|
+
const fileCount = summary.files.length;
|
|
77
|
+
const fileLines = summary.files.slice(0, 10).map((file) => {
|
|
78
|
+
const statusPrefix = getStatusLabel(file.status);
|
|
79
|
+
if (file.linesAdded !== void 0 || file.linesRemoved !== void 0) {
|
|
80
|
+
const added = file.linesAdded ?? 0;
|
|
81
|
+
const removed = file.linesRemoved ?? 0;
|
|
82
|
+
return `- ${file.path} (${statusPrefix}, +${added} -${removed})`;
|
|
83
|
+
}
|
|
84
|
+
return `- ${file.path} (${statusPrefix})`;
|
|
85
|
+
});
|
|
86
|
+
if (summary.files.length > 10) {
|
|
87
|
+
fileLines.push(`- ... and ${summary.files.length - 10} more file(s)`);
|
|
88
|
+
}
|
|
89
|
+
const lines = [
|
|
90
|
+
title,
|
|
91
|
+
"",
|
|
92
|
+
`Modified ${fileCount} file(s):`,
|
|
93
|
+
...fileLines,
|
|
94
|
+
"",
|
|
95
|
+
"\u{1F916} Auto-committed by Minto"
|
|
96
|
+
];
|
|
97
|
+
return lines.join("\n");
|
|
98
|
+
}
|
|
99
|
+
async function getChangedFiles() {
|
|
100
|
+
const { stdout, code } = await execFileNoThrow(
|
|
101
|
+
"git",
|
|
102
|
+
["status", "--porcelain", "-u"],
|
|
103
|
+
void 0,
|
|
104
|
+
void 0,
|
|
105
|
+
true
|
|
106
|
+
);
|
|
107
|
+
if (code !== 0) {
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
const files = [];
|
|
111
|
+
for (const line of stdout.split("\n")) {
|
|
112
|
+
if (!line.trim()) continue;
|
|
113
|
+
const status = line.substring(0, 2).trim();
|
|
114
|
+
const path = line.substring(3).trim();
|
|
115
|
+
if (!path) continue;
|
|
116
|
+
const normalizedStatus = normalizeStatus(status);
|
|
117
|
+
if (normalizedStatus) {
|
|
118
|
+
files.push({
|
|
119
|
+
path,
|
|
120
|
+
status: normalizedStatus
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return files;
|
|
125
|
+
}
|
|
126
|
+
async function getChangeSummary(files) {
|
|
127
|
+
let totalAdded = 0;
|
|
128
|
+
let totalRemoved = 0;
|
|
129
|
+
const trackedFiles = files.filter((f) => f.status !== "?");
|
|
130
|
+
if (trackedFiles.length > 0) {
|
|
131
|
+
const { stdout, code } = await execFileNoThrow(
|
|
132
|
+
"git",
|
|
133
|
+
["diff", "--numstat", "HEAD", "--", ...trackedFiles.map((f) => f.path)],
|
|
134
|
+
void 0,
|
|
135
|
+
void 0,
|
|
136
|
+
true
|
|
137
|
+
);
|
|
138
|
+
if (code === 0) {
|
|
139
|
+
for (const line of stdout.split("\n")) {
|
|
140
|
+
if (!line.trim()) continue;
|
|
141
|
+
const parts = line.split(" ");
|
|
142
|
+
if (parts.length >= 3) {
|
|
143
|
+
const added = parseInt(parts[0], 10) || 0;
|
|
144
|
+
const removed = parseInt(parts[1], 10) || 0;
|
|
145
|
+
const path = parts[2];
|
|
146
|
+
const file = files.find((f) => f.path === path);
|
|
147
|
+
if (file) {
|
|
148
|
+
file.linesAdded = added;
|
|
149
|
+
file.linesRemoved = removed;
|
|
150
|
+
}
|
|
151
|
+
totalAdded += added;
|
|
152
|
+
totalRemoved += removed;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const untrackedFiles = files.filter((f) => f.status === "?");
|
|
158
|
+
for (const file of untrackedFiles) {
|
|
159
|
+
const { stdout, code } = await execFileNoThrow(
|
|
160
|
+
"wc",
|
|
161
|
+
["-l", file.path],
|
|
162
|
+
void 0,
|
|
163
|
+
void 0,
|
|
164
|
+
true
|
|
165
|
+
);
|
|
166
|
+
if (code === 0) {
|
|
167
|
+
const lineCount = parseInt(stdout.trim().split(/\s+/)[0], 10) || 0;
|
|
168
|
+
file.linesAdded = lineCount;
|
|
169
|
+
file.linesRemoved = 0;
|
|
170
|
+
totalAdded += lineCount;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
files,
|
|
175
|
+
totalAdded,
|
|
176
|
+
totalRemoved
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function filterExcludedFiles(files, excludePatterns) {
|
|
180
|
+
return files.filter((file) => {
|
|
181
|
+
for (const pattern of excludePatterns) {
|
|
182
|
+
if (minimatch(file.path, pattern, { dot: true })) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return true;
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
async function stageFiles(paths) {
|
|
190
|
+
if (paths.length === 0) {
|
|
191
|
+
return { success: true };
|
|
192
|
+
}
|
|
193
|
+
const { code, stderr } = await execFileNoThrow(
|
|
194
|
+
"git",
|
|
195
|
+
["add", "--", ...paths],
|
|
196
|
+
void 0,
|
|
197
|
+
void 0,
|
|
198
|
+
true
|
|
199
|
+
);
|
|
200
|
+
if (code !== 0) {
|
|
201
|
+
return { success: false, error: stderr || "Unknown error" };
|
|
202
|
+
}
|
|
203
|
+
return { success: true };
|
|
204
|
+
}
|
|
205
|
+
async function createCommit(message) {
|
|
206
|
+
const { code, stdout, stderr } = await execFileNoThrow(
|
|
207
|
+
"git",
|
|
208
|
+
["commit", "-m", message],
|
|
209
|
+
void 0,
|
|
210
|
+
void 0,
|
|
211
|
+
true
|
|
212
|
+
);
|
|
213
|
+
if (code !== 0) {
|
|
214
|
+
return { success: false, error: stderr || "Unknown error" };
|
|
215
|
+
}
|
|
216
|
+
const match = stdout.match(/\[[\w/-]+ ([a-f0-9]+)\]/);
|
|
217
|
+
const hash = match ? match[1] : void 0;
|
|
218
|
+
return { success: true, hash };
|
|
219
|
+
}
|
|
220
|
+
function normalizeStatus(status) {
|
|
221
|
+
if (status.includes("?")) return "?";
|
|
222
|
+
if (status.includes("A")) return "A";
|
|
223
|
+
if (status.includes("D")) return "D";
|
|
224
|
+
if (status.includes("R")) return "R";
|
|
225
|
+
if (status.includes("C")) return "C";
|
|
226
|
+
if (status.includes("U")) return "U";
|
|
227
|
+
if (status.includes("M") || status.includes(" M")) return "M";
|
|
228
|
+
if (status.trim()) return "M";
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
function getStatusLabel(status) {
|
|
232
|
+
switch (status) {
|
|
233
|
+
case "M":
|
|
234
|
+
return "modified";
|
|
235
|
+
case "A":
|
|
236
|
+
return "new";
|
|
237
|
+
case "D":
|
|
238
|
+
return "deleted";
|
|
239
|
+
case "?":
|
|
240
|
+
return "new";
|
|
241
|
+
case "R":
|
|
242
|
+
return "renamed";
|
|
243
|
+
case "C":
|
|
244
|
+
return "copied";
|
|
245
|
+
case "U":
|
|
246
|
+
return "unmerged";
|
|
247
|
+
default:
|
|
248
|
+
return "changed";
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function getTrackedOperations() {
|
|
252
|
+
return operationTracker.getSummary();
|
|
253
|
+
}
|
|
254
|
+
function hasTrackedFileChanges() {
|
|
255
|
+
const summary = operationTracker.getSummary();
|
|
256
|
+
return summary.filesModified.length > 0 || summary.filesCreated.length > 0;
|
|
257
|
+
}
|
|
258
|
+
function enhanceWithTrackerData(files, trackerSummary) {
|
|
259
|
+
const modifiedMap = /* @__PURE__ */ new Map();
|
|
260
|
+
for (const mod of trackerSummary.filesModified) {
|
|
261
|
+
modifiedMap.set(mod.path, { added: mod.added, removed: mod.removed });
|
|
262
|
+
}
|
|
263
|
+
return files.map((file) => {
|
|
264
|
+
const tracked = modifiedMap.get(file.path);
|
|
265
|
+
if (tracked) {
|
|
266
|
+
return {
|
|
267
|
+
...file,
|
|
268
|
+
linesAdded: file.linesAdded ?? tracked.added,
|
|
269
|
+
linesRemoved: file.linesRemoved ?? tracked.removed
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
return file;
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
export {
|
|
276
|
+
DEFAULT_GIT_CONFIG,
|
|
277
|
+
enhanceWithTrackerData,
|
|
278
|
+
filterExcludedFiles,
|
|
279
|
+
generateCommitMessage,
|
|
280
|
+
getChangedFiles,
|
|
281
|
+
getCwd,
|
|
282
|
+
getTrackedOperations,
|
|
283
|
+
hasTrackedFileChanges,
|
|
284
|
+
performAutoCommit,
|
|
285
|
+
shouldAutoCommit
|
|
286
|
+
};
|
|
287
|
+
//# sourceMappingURL=gitAutoCommit.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/core/gitAutoCommit.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Git Auto-Commit System\n *\n * Provides automatic git commits after task completion.\n * Part of the \"\u96F6\u6210\u672C\u5F00\u7BB1\u5373\u7528\" (zero-cost out-of-box) optimization.\n *\n * Default: OFF - User must explicitly enable auto-commit.\n */\n\nimport { minimatch } from 'minimatch'\nimport { getIsGit } from '../utils/git'\nimport { execFileNoThrow } from '../utils/execFileNoThrow'\nimport { getCwd } from '../utils/state'\nimport { operationTracker, type OperationSummary } from './operationTracker'\n\n// ============================================================================\n// Types & Configuration\n// ============================================================================\n\n/**\n * Configuration for git auto-commit behavior\n */\nexport interface GitAutoCommitConfig {\n /** Whether auto-commit is enabled (default: false) */\n enabled: boolean\n\n /** Whether to commit after task completion (default: true) */\n commitAfterTask: boolean\n\n /** Glob patterns for files to exclude from auto-commit */\n excludePatterns: string[]\n\n /** Prefix for auto-generated commit messages */\n commitMessagePrefix: string\n}\n\n/**\n * Default configuration - auto-commit is OFF by default\n */\nexport const DEFAULT_GIT_CONFIG: GitAutoCommitConfig = {\n enabled: false, // Default OFF, user must enable\n commitAfterTask: true,\n excludePatterns: ['*.log', 'node_modules/**', 'dist/**', '.minto/**'],\n commitMessagePrefix: 'chore(minto): ',\n}\n\n/**\n * Result of an auto-commit operation\n */\nexport interface AutoCommitResult {\n success: boolean\n message: string\n commitHash?: string\n filesCommitted?: string[]\n}\n\n/**\n * Information about a changed file\n */\nexport interface ChangedFile {\n /** File path relative to repo root */\n path: string\n\n /** Change status: 'M' (modified), 'A' (added), 'D' (deleted), '?' (untracked) */\n status: 'M' | 'A' | 'D' | '?' | 'R' | 'C' | 'U'\n\n /** Number of lines added (for modified/added files) */\n linesAdded?: number\n\n /** Number of lines removed (for modified/deleted files) */\n linesRemoved?: number\n}\n\n/**\n * Summary of file changes for commit message generation\n */\nexport interface ChangeSummary {\n files: ChangedFile[]\n totalAdded: number\n totalRemoved: number\n}\n\n// ============================================================================\n// Core Functions\n// ============================================================================\n\n/**\n * Check if auto-commit should be performed\n *\n * @param config - Git auto-commit configuration\n * @returns true if auto-commit should proceed\n */\nexport async function shouldAutoCommit(\n config: GitAutoCommitConfig,\n): Promise<boolean> {\n // Check if auto-commit is enabled\n if (!config.enabled) {\n return false\n }\n\n // Check if we're in a git repository\n const isGitRepo = await getIsGit()\n if (!isGitRepo) {\n return false\n }\n\n // Check if there are changes to commit\n const changes = await getChangedFiles()\n if (changes.length === 0) {\n return false\n }\n\n // Filter out excluded files\n const filesToCommit = filterExcludedFiles(changes, config.excludePatterns)\n return filesToCommit.length > 0\n}\n\n/**\n * Perform an auto-commit with the given description\n *\n * @param config - Git auto-commit configuration\n * @param description - Short description of the changes\n * @returns Result of the commit operation\n */\nexport async function performAutoCommit(\n config: GitAutoCommitConfig,\n description: string,\n): Promise<AutoCommitResult> {\n // Validate we're in a git repo\n const isGitRepo = await getIsGit()\n if (!isGitRepo) {\n return {\n success: false,\n message: 'Not a git repository',\n }\n }\n\n // Get changed files\n const allChanges = await getChangedFiles()\n if (allChanges.length === 0) {\n return {\n success: false,\n message: 'No changes to commit',\n }\n }\n\n // Filter excluded files\n const filesToCommit = filterExcludedFiles(allChanges, config.excludePatterns)\n if (filesToCommit.length === 0) {\n return {\n success: false,\n message: 'All changed files are excluded by patterns',\n }\n }\n\n // Get detailed change summary for commit message\n const summary = await getChangeSummary(filesToCommit)\n\n // Stage files\n const stageResult = await stageFiles(filesToCommit.map(f => f.path))\n if (!stageResult.success) {\n return {\n success: false,\n message: `Failed to stage files: ${stageResult.error}`,\n }\n }\n\n // Generate commit message\n const commitMessage = generateCommitMessage(config, summary, description)\n\n // Create commit\n const commitResult = await createCommit(commitMessage)\n if (!commitResult.success) {\n return {\n success: false,\n message: `Failed to create commit: ${commitResult.error}`,\n }\n }\n\n return {\n success: true,\n message: `Committed ${filesToCommit.length} file(s)`,\n commitHash: commitResult.hash,\n filesCommitted: filesToCommit.map(f => f.path),\n }\n}\n\n/**\n * Generate a commit message from the change summary\n *\n * @param config - Git auto-commit configuration\n * @param summary - Summary of file changes\n * @param description - Short description of the changes\n * @returns Formatted commit message\n */\nexport function generateCommitMessage(\n config: GitAutoCommitConfig,\n summary: ChangeSummary,\n description: string,\n): string {\n // Sanitize description - remove leading/trailing whitespace, limit length\n const sanitizedDesc = description.trim().slice(0, 72)\n\n // Build title line\n const title = `${config.commitMessagePrefix}${sanitizedDesc}`\n\n // Build file list\n const fileCount = summary.files.length\n const fileLines = summary.files\n .slice(0, 10) // Limit to first 10 files\n .map(file => {\n const statusPrefix = getStatusLabel(file.status)\n if (file.linesAdded !== undefined || file.linesRemoved !== undefined) {\n const added = file.linesAdded ?? 0\n const removed = file.linesRemoved ?? 0\n return `- ${file.path} (${statusPrefix}, +${added} -${removed})`\n }\n return `- ${file.path} (${statusPrefix})`\n })\n\n // Add \"and X more\" if there are more files\n if (summary.files.length > 10) {\n fileLines.push(`- ... and ${summary.files.length - 10} more file(s)`)\n }\n\n // Build full message\n const lines = [\n title,\n '',\n `Modified ${fileCount} file(s):`,\n ...fileLines,\n '',\n '\uD83E\uDD16 Auto-committed by Minto',\n ]\n\n return lines.join('\\n')\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Get list of changed files from git status\n */\nexport async function getChangedFiles(): Promise<ChangedFile[]> {\n const { stdout, code } = await execFileNoThrow(\n 'git',\n ['status', '--porcelain', '-u'],\n undefined,\n undefined,\n true,\n )\n\n if (code !== 0) {\n return []\n }\n\n const files: ChangedFile[] = []\n\n for (const line of stdout.split('\\n')) {\n if (!line.trim()) continue\n\n // Parse git status porcelain format: XY filename\n // X = index status, Y = working tree status\n const status = line.substring(0, 2).trim()\n const path = line.substring(3).trim()\n\n if (!path) continue\n\n // Map status codes to our simplified format\n const normalizedStatus = normalizeStatus(status)\n if (normalizedStatus) {\n files.push({\n path,\n status: normalizedStatus,\n })\n }\n }\n\n return files\n}\n\n/**\n * Get detailed change summary including line counts\n */\nasync function getChangeSummary(files: ChangedFile[]): Promise<ChangeSummary> {\n let totalAdded = 0\n let totalRemoved = 0\n\n // Get numstat for tracked files\n const trackedFiles = files.filter(f => f.status !== '?')\n if (trackedFiles.length > 0) {\n const { stdout, code } = await execFileNoThrow(\n 'git',\n ['diff', '--numstat', 'HEAD', '--', ...trackedFiles.map(f => f.path)],\n undefined,\n undefined,\n true,\n )\n\n if (code === 0) {\n for (const line of stdout.split('\\n')) {\n if (!line.trim()) continue\n\n const parts = line.split('\\t')\n if (parts.length >= 3) {\n const added = parseInt(parts[0], 10) || 0\n const removed = parseInt(parts[1], 10) || 0\n const path = parts[2]\n\n // Find and update the file in our list\n const file = files.find(f => f.path === path)\n if (file) {\n file.linesAdded = added\n file.linesRemoved = removed\n }\n\n totalAdded += added\n totalRemoved += removed\n }\n }\n }\n }\n\n // For untracked files, count all lines as added\n const untrackedFiles = files.filter(f => f.status === '?')\n for (const file of untrackedFiles) {\n const { stdout, code } = await execFileNoThrow(\n 'wc',\n ['-l', file.path],\n undefined,\n undefined,\n true,\n )\n\n if (code === 0) {\n const lineCount = parseInt(stdout.trim().split(/\\s+/)[0], 10) || 0\n file.linesAdded = lineCount\n file.linesRemoved = 0\n totalAdded += lineCount\n }\n }\n\n return {\n files,\n totalAdded,\n totalRemoved,\n }\n}\n\n/**\n * Filter out files matching exclusion patterns\n */\nexport function filterExcludedFiles(\n files: ChangedFile[],\n excludePatterns: string[],\n): ChangedFile[] {\n return files.filter(file => {\n for (const pattern of excludePatterns) {\n if (minimatch(file.path, pattern, { dot: true })) {\n return false\n }\n }\n return true\n })\n}\n\n/**\n * Stage files for commit\n */\nasync function stageFiles(\n paths: string[],\n): Promise<{ success: boolean; error?: string }> {\n if (paths.length === 0) {\n return { success: true }\n }\n\n const { code, stderr } = await execFileNoThrow(\n 'git',\n ['add', '--', ...paths],\n undefined,\n undefined,\n true,\n )\n\n if (code !== 0) {\n return { success: false, error: stderr || 'Unknown error' }\n }\n\n return { success: true }\n}\n\n/**\n * Create a git commit\n */\nasync function createCommit(\n message: string,\n): Promise<{ success: boolean; hash?: string; error?: string }> {\n const { code, stdout, stderr } = await execFileNoThrow(\n 'git',\n ['commit', '-m', message],\n undefined,\n undefined,\n true,\n )\n\n if (code !== 0) {\n return { success: false, error: stderr || 'Unknown error' }\n }\n\n // Extract commit hash from output\n // Output format: \"[branch hash] message\"\n const match = stdout.match(/\\[[\\w/-]+ ([a-f0-9]+)\\]/)\n const hash = match ? match[1] : undefined\n\n return { success: true, hash }\n}\n\n/**\n * Normalize git status code to our simplified format\n */\nfunction normalizeStatus(\n status: string,\n): 'M' | 'A' | 'D' | '?' | 'R' | 'C' | 'U' | null {\n // Handle the various git status codes\n // First character is staging area, second is working tree\n\n if (status.includes('?')) return '?'\n if (status.includes('A')) return 'A'\n if (status.includes('D')) return 'D'\n if (status.includes('R')) return 'R'\n if (status.includes('C')) return 'C'\n if (status.includes('U')) return 'U'\n if (status.includes('M') || status.includes(' M')) return 'M'\n\n // For any other changes (like ' M' for unstaged modifications)\n if (status.trim()) return 'M'\n\n return null\n}\n\n/**\n * Get human-readable label for status code\n */\nfunction getStatusLabel(status: string): string {\n switch (status) {\n case 'M':\n return 'modified'\n case 'A':\n return 'new'\n case 'D':\n return 'deleted'\n case '?':\n return 'new'\n case 'R':\n return 'renamed'\n case 'C':\n return 'copied'\n case 'U':\n return 'unmerged'\n default:\n return 'changed'\n }\n}\n\n// ============================================================================\n// Operation Tracker Integration\n// ============================================================================\n\n/**\n * Get files modified during the current session from operationTracker\n *\n * This provides additional context about which files were touched by Minto,\n * which can be used to prioritize or filter files for auto-commit.\n */\nexport function getTrackedOperations(): OperationSummary {\n return operationTracker.getSummary()\n}\n\n/**\n * Check if operationTracker has any file modifications recorded\n */\nexport function hasTrackedFileChanges(): boolean {\n const summary = operationTracker.getSummary()\n return summary.filesModified.length > 0 || summary.filesCreated.length > 0\n}\n\n/**\n * Enhance changed files with line count data from operationTracker\n *\n * When operationTracker has recorded line changes, use that data\n * instead of computing it via git diff (which is more expensive).\n */\nexport function enhanceWithTrackerData(\n files: ChangedFile[],\n trackerSummary: OperationSummary,\n): ChangedFile[] {\n // Build a map of tracked modifications for fast lookup\n const modifiedMap = new Map<string, { added: number; removed: number }>()\n for (const mod of trackerSummary.filesModified) {\n modifiedMap.set(mod.path, { added: mod.added, removed: mod.removed })\n }\n\n // Enhance file data with tracker info\n return files.map(file => {\n const tracked = modifiedMap.get(file.path)\n if (tracked) {\n return {\n ...file,\n linesAdded: file.linesAdded ?? tracked.added,\n linesRemoved: file.linesRemoved ?? tracked.removed,\n }\n }\n return file\n })\n}\n\n// ============================================================================\n// Testing Utilities\n// ============================================================================\n\n/**\n * Get current working directory (for testing)\n * Re-exported from state for convenience\n */\nexport { getCwd }\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/core/index.js
CHANGED
|
@@ -2,4 +2,7 @@ export * from "./config/index.js";
|
|
|
2
2
|
export * from "./permissions/index.js";
|
|
3
3
|
export * from "./tools/index.js";
|
|
4
4
|
export * from "./costTracker.js";
|
|
5
|
+
export * from "./operationTracker.js";
|
|
6
|
+
export * from "./gitAutoCommit.js";
|
|
7
|
+
export * from "./backupManager.js";
|
|
5
8
|
//# sourceMappingURL=index.js.map
|
package/dist/core/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/core/index.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Core Module\n *\n * Centralized exports for core system functionality.\n */\n\n// Configuration\nexport * from './config'\n\n// Permissions\nexport * from './permissions'\n\n// Tools\nexport * from './tools'\n\n// Cost Tracking\nexport * from './costTracker'\n"],
|
|
5
|
-
"mappings": "AAOA,cAAc;AAGd,cAAc;AAGd,cAAc;AAGd,cAAc;",
|
|
4
|
+
"sourcesContent": ["/**\n * Core Module\n *\n * Centralized exports for core system functionality.\n */\n\n// Configuration\nexport * from './config'\n\n// Permissions\nexport * from './permissions'\n\n// Tools\nexport * from './tools'\n\n// Cost Tracking\nexport * from './costTracker'\n\n// Operation Tracking\nexport * from './operationTracker'\n\n// Git Auto-Commit\nexport * from './gitAutoCommit'\n\n// Backup Manager\nexport * from './backupManager'\n"],
|
|
5
|
+
"mappings": "AAOA,cAAc;AAGd,cAAc;AAGd,cAAc;AAGd,cAAc;AAGd,cAAc;AAGd,cAAc;AAGd,cAAc;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { EventEmitter } from "events";
|
|
2
|
+
class OperationTrackerImpl extends EventEmitter {
|
|
3
|
+
static instance = null;
|
|
4
|
+
/** All recorded operations in chronological order */
|
|
5
|
+
operations = [];
|
|
6
|
+
/** Counter for generating unique IDs */
|
|
7
|
+
idCounter = 0;
|
|
8
|
+
constructor() {
|
|
9
|
+
super();
|
|
10
|
+
this.setMaxListeners(50);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Get the singleton instance
|
|
14
|
+
*/
|
|
15
|
+
static getInstance() {
|
|
16
|
+
if (!OperationTrackerImpl.instance) {
|
|
17
|
+
OperationTrackerImpl.instance = new OperationTrackerImpl();
|
|
18
|
+
}
|
|
19
|
+
return OperationTrackerImpl.instance;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Record a new operation
|
|
23
|
+
*
|
|
24
|
+
* @param input - Operation details to record
|
|
25
|
+
* @returns The recorded operation with generated id and timestamp
|
|
26
|
+
*/
|
|
27
|
+
record(input) {
|
|
28
|
+
const record = {
|
|
29
|
+
id: `op-${++this.idCounter}-${Date.now()}`,
|
|
30
|
+
type: input.type,
|
|
31
|
+
description: input.description,
|
|
32
|
+
timestamp: Date.now(),
|
|
33
|
+
duration: input.duration,
|
|
34
|
+
details: input.details
|
|
35
|
+
};
|
|
36
|
+
this.operations.push(record);
|
|
37
|
+
this.emit("operation", record);
|
|
38
|
+
return record;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Get aggregated summary of all operations
|
|
42
|
+
*/
|
|
43
|
+
getSummary() {
|
|
44
|
+
const filesRead = /* @__PURE__ */ new Set();
|
|
45
|
+
const filesModified = /* @__PURE__ */ new Map();
|
|
46
|
+
const filesCreated = /* @__PURE__ */ new Set();
|
|
47
|
+
const commandsRun = [];
|
|
48
|
+
let tasksCompleted = 0;
|
|
49
|
+
let totalDuration = 0;
|
|
50
|
+
let webSearches = 0;
|
|
51
|
+
for (const op of this.operations) {
|
|
52
|
+
if (op.duration) {
|
|
53
|
+
totalDuration += op.duration;
|
|
54
|
+
}
|
|
55
|
+
switch (op.type) {
|
|
56
|
+
case "file_read":
|
|
57
|
+
if (op.details?.filePath) {
|
|
58
|
+
filesRead.add(op.details.filePath);
|
|
59
|
+
}
|
|
60
|
+
break;
|
|
61
|
+
case "file_edit":
|
|
62
|
+
if (op.details?.filePath) {
|
|
63
|
+
const existing = filesModified.get(op.details.filePath);
|
|
64
|
+
if (existing) {
|
|
65
|
+
existing.added += op.details.linesAdded ?? 0;
|
|
66
|
+
existing.removed += op.details.linesRemoved ?? 0;
|
|
67
|
+
} else {
|
|
68
|
+
filesModified.set(op.details.filePath, {
|
|
69
|
+
path: op.details.filePath,
|
|
70
|
+
added: op.details.linesAdded ?? 0,
|
|
71
|
+
removed: op.details.linesRemoved ?? 0
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
break;
|
|
76
|
+
case "file_create":
|
|
77
|
+
if (op.details?.filePath) {
|
|
78
|
+
filesCreated.add(op.details.filePath);
|
|
79
|
+
}
|
|
80
|
+
break;
|
|
81
|
+
case "bash_command":
|
|
82
|
+
if (op.details?.command) {
|
|
83
|
+
commandsRun.push(op.details.command);
|
|
84
|
+
}
|
|
85
|
+
break;
|
|
86
|
+
case "web_search":
|
|
87
|
+
webSearches++;
|
|
88
|
+
break;
|
|
89
|
+
case "task":
|
|
90
|
+
tasksCompleted++;
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
filesRead: Array.from(filesRead),
|
|
96
|
+
filesModified: Array.from(filesModified.values()),
|
|
97
|
+
filesCreated: Array.from(filesCreated),
|
|
98
|
+
commandsRun,
|
|
99
|
+
tasksCompleted,
|
|
100
|
+
totalDuration,
|
|
101
|
+
webSearches
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Get all recorded operations
|
|
106
|
+
*/
|
|
107
|
+
getOperations() {
|
|
108
|
+
return [...this.operations];
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get operation count by type
|
|
112
|
+
*/
|
|
113
|
+
getCountByType(type) {
|
|
114
|
+
return this.operations.filter((op) => op.type === type).length;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Get total operation count
|
|
118
|
+
*/
|
|
119
|
+
getTotalCount() {
|
|
120
|
+
return this.operations.length;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Subscribe to operation events
|
|
124
|
+
*
|
|
125
|
+
* @param callback - Called when an operation is recorded
|
|
126
|
+
* @returns Unsubscribe function
|
|
127
|
+
*/
|
|
128
|
+
onOperation(callback) {
|
|
129
|
+
this.on("operation", callback);
|
|
130
|
+
return () => this.off("operation", callback);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Clear all recorded operations
|
|
134
|
+
*/
|
|
135
|
+
clear() {
|
|
136
|
+
this.operations = [];
|
|
137
|
+
this.idCounter = 0;
|
|
138
|
+
this.emit("clear");
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Reset for testing (only available in test environment)
|
|
142
|
+
*/
|
|
143
|
+
resetForTests() {
|
|
144
|
+
if (process.env.NODE_ENV !== "test") {
|
|
145
|
+
throw new Error("resetForTests can only be called in tests");
|
|
146
|
+
}
|
|
147
|
+
this.clear();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const operationTracker = OperationTrackerImpl.getInstance();
|
|
151
|
+
function recordOperation(input) {
|
|
152
|
+
return operationTracker.record(input);
|
|
153
|
+
}
|
|
154
|
+
function getOperationSummary() {
|
|
155
|
+
return operationTracker.getSummary();
|
|
156
|
+
}
|
|
157
|
+
function recordFileRead(filePath, description) {
|
|
158
|
+
operationTracker.record({
|
|
159
|
+
type: "file_read",
|
|
160
|
+
description: description ?? `Read ${filePath}`,
|
|
161
|
+
details: { filePath }
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
function recordFileEdit(filePath, linesAdded, linesRemoved, description) {
|
|
165
|
+
operationTracker.record({
|
|
166
|
+
type: "file_edit",
|
|
167
|
+
description: description ?? `Edit ${filePath}`,
|
|
168
|
+
details: { filePath, linesAdded, linesRemoved }
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
function recordFileCreate(filePath, description) {
|
|
172
|
+
operationTracker.record({
|
|
173
|
+
type: "file_create",
|
|
174
|
+
description: description ?? `Create ${filePath}`,
|
|
175
|
+
details: { filePath }
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
function recordBashCommand(command, duration, description) {
|
|
179
|
+
operationTracker.record({
|
|
180
|
+
type: "bash_command",
|
|
181
|
+
description: description ?? `Run: ${command}`,
|
|
182
|
+
duration,
|
|
183
|
+
details: { command }
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
function recordWebSearch(searchQuery, description) {
|
|
187
|
+
operationTracker.record({
|
|
188
|
+
type: "web_search",
|
|
189
|
+
description: description ?? `Search: ${searchQuery}`,
|
|
190
|
+
details: { searchQuery }
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
function recordTaskComplete(description, toolCount, duration) {
|
|
194
|
+
operationTracker.record({
|
|
195
|
+
type: "task",
|
|
196
|
+
description,
|
|
197
|
+
duration,
|
|
198
|
+
details: { toolCount }
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
export {
|
|
202
|
+
getOperationSummary,
|
|
203
|
+
operationTracker,
|
|
204
|
+
recordBashCommand,
|
|
205
|
+
recordFileCreate,
|
|
206
|
+
recordFileEdit,
|
|
207
|
+
recordFileRead,
|
|
208
|
+
recordOperation,
|
|
209
|
+
recordTaskComplete,
|
|
210
|
+
recordWebSearch
|
|
211
|
+
};
|
|
212
|
+
//# sourceMappingURL=operationTracker.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/core/operationTracker.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Operation Tracker\n *\n * Tracks file operations, bash commands, and tasks executed during a session.\n * Provides aggregated summaries for user feedback on what changes were made.\n *\n * Architecture:\n * - Singleton pattern for global access\n * - Event-driven updates for real-time UI\n * - Categorized operation tracking (file_read, file_edit, file_create, bash_command, web_search, task)\n *\n * Usage:\n * ```typescript\n * import { operationTracker, recordOperation } from '@core/operationTracker'\n *\n * // Record a file read\n * recordOperation({\n * type: 'file_read',\n * description: 'Read configuration file',\n * details: { filePath: '/path/to/config.json' }\n * })\n *\n * // Get summary of all operations\n * const summary = operationTracker.getSummary()\n *\n * // Subscribe to updates\n * const unsubscribe = operationTracker.onOperation((record) => {\n * console.log('Operation recorded:', record)\n * })\n * ```\n */\n\nimport { EventEmitter } from 'events'\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Type of operation that can be tracked\n */\nexport type OperationType =\n | 'file_read'\n | 'file_edit'\n | 'file_create'\n | 'bash_command'\n | 'web_search'\n | 'task'\n\n/**\n * Details specific to each operation type\n */\nexport interface OperationDetails {\n /** File path for file operations */\n filePath?: string\n\n /** Command string for bash operations */\n command?: string\n\n /** Lines added for file edits */\n linesAdded?: number\n\n /** Lines removed for file edits */\n linesRemoved?: number\n\n /** Number of tools used for task operations */\n toolCount?: number\n\n /** Search query for web search operations */\n searchQuery?: string\n}\n\n/**\n * A single recorded operation\n */\nexport interface OperationRecord {\n /** Unique identifier for the operation */\n id: string\n\n /** Type of operation */\n type: OperationType\n\n /** Human-readable description */\n description: string\n\n /** Unix timestamp when operation started */\n timestamp: number\n\n /** Duration in milliseconds (if completed) */\n duration?: number\n\n /** Additional details specific to operation type */\n details?: OperationDetails\n}\n\n/**\n * Aggregated summary of all operations\n */\nexport interface OperationSummary {\n /** List of files that were read */\n filesRead: string[]\n\n /** List of files that were modified with change stats */\n filesModified: { path: string; added: number; removed: number }[]\n\n /** List of files that were created */\n filesCreated: string[]\n\n /** List of bash commands that were run */\n commandsRun: string[]\n\n /** Number of tasks completed */\n tasksCompleted: number\n\n /** Total duration of all operations in milliseconds */\n totalDuration: number\n\n /** Number of web searches performed */\n webSearches: number\n}\n\n/**\n * Input for recording an operation (id and timestamp auto-generated)\n */\nexport interface OperationInput {\n type: OperationType\n description: string\n duration?: number\n details?: OperationDetails\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * OperationTrackerImpl - Centralized operation tracking\n */\nclass OperationTrackerImpl extends EventEmitter {\n private static instance: OperationTrackerImpl | null = null\n\n /** All recorded operations in chronological order */\n private operations: OperationRecord[] = []\n\n /** Counter for generating unique IDs */\n private idCounter = 0\n\n private constructor() {\n super()\n this.setMaxListeners(50)\n }\n\n /**\n * Get the singleton instance\n */\n static getInstance(): OperationTrackerImpl {\n if (!OperationTrackerImpl.instance) {\n OperationTrackerImpl.instance = new OperationTrackerImpl()\n }\n return OperationTrackerImpl.instance\n }\n\n /**\n * Record a new operation\n *\n * @param input - Operation details to record\n * @returns The recorded operation with generated id and timestamp\n */\n record(input: OperationInput): OperationRecord {\n const record: OperationRecord = {\n id: `op-${++this.idCounter}-${Date.now()}`,\n type: input.type,\n description: input.description,\n timestamp: Date.now(),\n duration: input.duration,\n details: input.details,\n }\n\n this.operations.push(record)\n this.emit('operation', record)\n\n return record\n }\n\n /**\n * Get aggregated summary of all operations\n */\n getSummary(): OperationSummary {\n const filesRead = new Set<string>()\n const filesModified = new Map<\n string,\n { path: string; added: number; removed: number }\n >()\n const filesCreated = new Set<string>()\n const commandsRun: string[] = []\n let tasksCompleted = 0\n let totalDuration = 0\n let webSearches = 0\n\n for (const op of this.operations) {\n // Accumulate duration\n if (op.duration) {\n totalDuration += op.duration\n }\n\n switch (op.type) {\n case 'file_read':\n if (op.details?.filePath) {\n filesRead.add(op.details.filePath)\n }\n break\n\n case 'file_edit':\n if (op.details?.filePath) {\n const existing = filesModified.get(op.details.filePath)\n if (existing) {\n // Accumulate changes to the same file\n existing.added += op.details.linesAdded ?? 0\n existing.removed += op.details.linesRemoved ?? 0\n } else {\n filesModified.set(op.details.filePath, {\n path: op.details.filePath,\n added: op.details.linesAdded ?? 0,\n removed: op.details.linesRemoved ?? 0,\n })\n }\n }\n break\n\n case 'file_create':\n if (op.details?.filePath) {\n filesCreated.add(op.details.filePath)\n }\n break\n\n case 'bash_command':\n if (op.details?.command) {\n commandsRun.push(op.details.command)\n }\n break\n\n case 'web_search':\n webSearches++\n break\n\n case 'task':\n tasksCompleted++\n break\n }\n }\n\n return {\n filesRead: Array.from(filesRead),\n filesModified: Array.from(filesModified.values()),\n filesCreated: Array.from(filesCreated),\n commandsRun,\n tasksCompleted,\n totalDuration,\n webSearches,\n }\n }\n\n /**\n * Get all recorded operations\n */\n getOperations(): OperationRecord[] {\n return [...this.operations]\n }\n\n /**\n * Get operation count by type\n */\n getCountByType(type: OperationType): number {\n return this.operations.filter(op => op.type === type).length\n }\n\n /**\n * Get total operation count\n */\n getTotalCount(): number {\n return this.operations.length\n }\n\n /**\n * Subscribe to operation events\n *\n * @param callback - Called when an operation is recorded\n * @returns Unsubscribe function\n */\n onOperation(callback: (record: OperationRecord) => void): () => void {\n this.on('operation', callback)\n return () => this.off('operation', callback)\n }\n\n /**\n * Clear all recorded operations\n */\n clear(): void {\n this.operations = []\n this.idCounter = 0\n this.emit('clear')\n }\n\n /**\n * Reset for testing (only available in test environment)\n */\n resetForTests(): void {\n if (process.env.NODE_ENV !== 'test') {\n throw new Error('resetForTests can only be called in tests')\n }\n this.clear()\n }\n}\n\n// ============================================================================\n// Exports\n// ============================================================================\n\n/** Singleton instance */\nexport const operationTracker = OperationTrackerImpl.getInstance()\n\n/**\n * Record an operation\n *\n * Convenience function for recording operations without accessing the singleton directly.\n */\nexport function recordOperation(input: OperationInput): OperationRecord {\n return operationTracker.record(input)\n}\n\n/**\n * Get operation summary\n *\n * Convenience function for getting summary without accessing the singleton directly.\n */\nexport function getOperationSummary(): OperationSummary {\n return operationTracker.getSummary()\n}\n\n/**\n * Record a file read operation\n */\nexport function recordFileRead(filePath: string, description?: string): void {\n operationTracker.record({\n type: 'file_read',\n description: description ?? `Read ${filePath}`,\n details: { filePath },\n })\n}\n\n/**\n * Record a file edit operation\n */\nexport function recordFileEdit(\n filePath: string,\n linesAdded: number,\n linesRemoved: number,\n description?: string,\n): void {\n operationTracker.record({\n type: 'file_edit',\n description: description ?? `Edit ${filePath}`,\n details: { filePath, linesAdded, linesRemoved },\n })\n}\n\n/**\n * Record a file create operation\n */\nexport function recordFileCreate(filePath: string, description?: string): void {\n operationTracker.record({\n type: 'file_create',\n description: description ?? `Create ${filePath}`,\n details: { filePath },\n })\n}\n\n/**\n * Record a bash command execution\n */\nexport function recordBashCommand(\n command: string,\n duration?: number,\n description?: string,\n): void {\n operationTracker.record({\n type: 'bash_command',\n description: description ?? `Run: ${command}`,\n duration,\n details: { command },\n })\n}\n\n/**\n * Record a web search operation\n */\nexport function recordWebSearch(\n searchQuery: string,\n description?: string,\n): void {\n operationTracker.record({\n type: 'web_search',\n description: description ?? `Search: ${searchQuery}`,\n details: { searchQuery },\n })\n}\n\n/**\n * Record a task completion\n */\nexport function recordTaskComplete(\n description: string,\n toolCount?: number,\n duration?: number,\n): void {\n operationTracker.record({\n type: 'task',\n description,\n duration,\n details: { toolCount },\n })\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -18,7 +18,7 @@ function getPermissionKey(toolName, input, prefix) {
|
|
|
18
18
|
return `Bash(${command})`;
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
-
if (toolName === "Edit" || toolName === "Write" || toolName === "MultiEdit") {
|
|
21
|
+
if (toolName === "Edit" || toolName === "Replace" || toolName === "Write" || toolName === "MultiEdit") {
|
|
22
22
|
const filePath = input.file_path;
|
|
23
23
|
if (filePath) {
|
|
24
24
|
return `${toolName}(${filePath})`;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/core/permissions/rules/allowedToolsRule.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Allowed Tools Permission Rule\n *\n * Checks if a tool is in the allowed tools list.\n */\n\nimport type {\n PermissionRule,\n PermissionContext,\n PermissionResult,\n} from '../engine/types'\n\n/**\n * Tools that are always allowed (no permission needed)\n */\nconst ALWAYS_ALLOWED_TOOLS = new Set([\n 'Read',\n 'Glob',\n 'Grep',\n 'LS',\n 'LSP',\n 'TodoWrite',\n 'AskUserQuestion',\n 'Think',\n])\n\n/**\n * Generate permission key for a tool\n */\nexport function getPermissionKey(\n toolName: string,\n input: Record<string, unknown>,\n prefix: string | null,\n): string {\n if (toolName === 'Bash') {\n const command = input.command as string | undefined\n if (prefix) {\n return `Bash(${prefix}:*)`\n }\n if (command) {\n return `Bash(${command})`\n }\n }\n\n if (toolName === 'Edit'
|
|
5
|
-
"mappings": "AAeA,MAAM,uBAAuB,oBAAI,IAAI;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAKM,SAAS,iBACd,UACA,OACA,QACQ;AACR,MAAI,aAAa,QAAQ;AACvB,UAAM,UAAU,MAAM;AACtB,QAAI,QAAQ;AACV,aAAO,QAAQ,MAAM;AAAA,IACvB;AACA,QAAI,SAAS;AACX,aAAO,QAAQ,OAAO;AAAA,IACxB;AAAA,EACF;
|
|
4
|
+
"sourcesContent": ["/**\n * Allowed Tools Permission Rule\n *\n * Checks if a tool is in the allowed tools list.\n */\n\nimport type {\n PermissionRule,\n PermissionContext,\n PermissionResult,\n} from '../engine/types'\n\n/**\n * Tools that are always allowed (no permission needed)\n */\nconst ALWAYS_ALLOWED_TOOLS = new Set([\n 'Read',\n 'Glob',\n 'Grep',\n 'LS',\n 'LSP',\n 'TodoWrite',\n 'AskUserQuestion',\n 'Think',\n])\n\n/**\n * Generate permission key for a tool\n */\nexport function getPermissionKey(\n toolName: string,\n input: Record<string, unknown>,\n prefix: string | null,\n): string {\n if (toolName === 'Bash') {\n const command = input.command as string | undefined\n if (prefix) {\n return `Bash(${prefix}:*)`\n }\n if (command) {\n return `Bash(${command})`\n }\n }\n\n // Note: 'Replace' is the actual API name for FileWriteTool (userFacingName is 'Write')\n if (\n toolName === 'Edit' ||\n toolName === 'Replace' ||\n toolName === 'Write' ||\n toolName === 'MultiEdit'\n ) {\n const filePath = input.file_path as string | undefined\n if (filePath) {\n return `${toolName}(${filePath})`\n }\n }\n\n return toolName\n}\n\n/**\n * Allowed Tools Rule\n *\n * Checks if a tool operation is in the allowed tools list.\n */\nexport const allowedToolsRule: PermissionRule = {\n name: 'allowed-tools',\n description: 'Checks tool against allowed tools list',\n priority: 50, // Medium priority\n\n check(context: PermissionContext): PermissionResult {\n const toolName = context.tool.name\n\n // Always allowed tools don't need permission\n if (ALWAYS_ALLOWED_TOOLS.has(toolName)) {\n return { allowed: true }\n }\n\n // Check if tool doesn't need permissions\n if (context.tool.needsPermissions && !context.tool.needsPermissions()) {\n return { allowed: true }\n }\n\n // Generate permission key\n const permissionKey = getPermissionKey(toolName, context.input, null)\n\n // Check if in allowed tools list\n if (context.allowedTools.includes(toolName)) {\n return { allowed: true }\n }\n\n if (context.allowedTools.includes(permissionKey)) {\n return { allowed: true }\n }\n\n // Not in allowed list - prompt user\n return {\n allowed: true, // Allow but mark for prompting\n promptUser: true,\n permissionKey,\n message: `Permission needed for ${toolName}`,\n }\n },\n}\n"],
|
|
5
|
+
"mappings": "AAeA,MAAM,uBAAuB,oBAAI,IAAI;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAKM,SAAS,iBACd,UACA,OACA,QACQ;AACR,MAAI,aAAa,QAAQ;AACvB,UAAM,UAAU,MAAM;AACtB,QAAI,QAAQ;AACV,aAAO,QAAQ,MAAM;AAAA,IACvB;AACA,QAAI,SAAS;AACX,aAAO,QAAQ,OAAO;AAAA,IACxB;AAAA,EACF;AAGA,MACE,aAAa,UACb,aAAa,aACb,aAAa,WACb,aAAa,aACb;AACA,UAAM,WAAW,MAAM;AACvB,QAAI,UAAU;AACZ,aAAO,GAAG,QAAQ,IAAI,QAAQ;AAAA,IAChC;AAAA,EACF;AAEA,SAAO;AACT;AAOO,MAAM,mBAAmC;AAAA,EAC9C,MAAM;AAAA,EACN,aAAa;AAAA,EACb,UAAU;AAAA;AAAA,EAEV,MAAM,SAA8C;AAClD,UAAM,WAAW,QAAQ,KAAK;AAG9B,QAAI,qBAAqB,IAAI,QAAQ,GAAG;AACtC,aAAO,EAAE,SAAS,KAAK;AAAA,IACzB;AAGA,QAAI,QAAQ,KAAK,oBAAoB,CAAC,QAAQ,KAAK,iBAAiB,GAAG;AACrE,aAAO,EAAE,SAAS,KAAK;AAAA,IACzB;AAGA,UAAM,gBAAgB,iBAAiB,UAAU,QAAQ,OAAO,IAAI;AAGpE,QAAI,QAAQ,aAAa,SAAS,QAAQ,GAAG;AAC3C,aAAO,EAAE,SAAS,KAAK;AAAA,IACzB;AAEA,QAAI,QAAQ,aAAa,SAAS,aAAa,GAAG;AAChD,aAAO,EAAE,SAAS,KAAK;AAAA,IACzB;AAGA,WAAO;AAAA,MACL,SAAS;AAAA;AAAA,MACT,YAAY;AAAA,MACZ;AAAA,MACA,SAAS,yBAAyB,QAAQ;AAAA,IAC5C;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -116,17 +116,22 @@ function matchesEscalationPattern(command) {
|
|
|
116
116
|
}
|
|
117
117
|
function getOperationType(toolName, input) {
|
|
118
118
|
switch (toolName) {
|
|
119
|
+
case "View":
|
|
120
|
+
// Actual API name for FileReadTool
|
|
119
121
|
case "Read":
|
|
120
122
|
case "FileRead":
|
|
121
123
|
case "Glob":
|
|
122
124
|
case "Grep":
|
|
123
125
|
return "read";
|
|
126
|
+
case "Replace":
|
|
127
|
+
// Actual API name for FileWriteTool
|
|
124
128
|
case "Write":
|
|
125
129
|
case "FileWrite":
|
|
126
130
|
case "Edit":
|
|
127
131
|
case "FileEdit":
|
|
128
132
|
case "MultiEdit":
|
|
129
133
|
case "NotebookEdit":
|
|
134
|
+
case "NotebookEditCell":
|
|
130
135
|
return "write";
|
|
131
136
|
case "Bash": {
|
|
132
137
|
const command = input.command || "";
|