@aigne/doc-smith 0.8.12-beta.5 → 0.8.12-beta.7
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/.aigne/doc-smith/history.yaml +37 -0
- package/.aigne/doc-smith/preferences.yml +12 -0
- package/.aigne/doc-smith/upload-cache.yaml +30 -0
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +19 -0
- package/agents/generate/update-document-structure.yaml +9 -3
- package/agents/history/view.mjs +5 -2
- package/agents/publish/publish-docs.mjs +12 -10
- package/agents/update/generate-document.yaml +10 -4
- package/agents/update/update-document-detail.yaml +9 -3
- package/agents/update/user-review-document.mjs +2 -1
- package/docs/guides-cleaning-up.ja.md +16 -15
- package/docs/guides-cleaning-up.md +5 -4
- package/docs/guides-cleaning-up.zh-TW.md +16 -15
- package/docs/guides-cleaning-up.zh.md +12 -11
- package/docs/guides-generating-documentation.ja.md +34 -32
- package/docs/guides-generating-documentation.md +15 -13
- package/docs/guides-generating-documentation.zh-TW.md +34 -32
- package/docs/guides-generating-documentation.zh.md +30 -28
- package/docs/guides-managing-history.ja.md +17 -20
- package/docs/guides-managing-history.md +5 -8
- package/docs/guides-managing-history.zh-TW.md +18 -21
- package/docs/guides-managing-history.zh.md +13 -16
- package/docs/guides-translating-documentation.ja.md +17 -17
- package/docs/guides-translating-documentation.md +1 -1
- package/docs/guides-translating-documentation.zh-TW.md +21 -21
- package/docs/guides-translating-documentation.zh.md +18 -18
- package/docs/guides-updating-documentation.ja.md +35 -35
- package/docs/guides-updating-documentation.md +4 -4
- package/docs/guides-updating-documentation.zh-TW.md +27 -27
- package/docs/guides-updating-documentation.zh.md +26 -26
- package/docs/overview.ja.md +13 -13
- package/docs/overview.md +1 -1
- package/docs/overview.zh-TW.md +19 -19
- package/docs/overview.zh.md +16 -16
- package/package.json +1 -1
- package/prompts/common/afs/afs-tools-usage.md +5 -0
- package/prompts/common/afs/use-afs-instruction.md +1 -0
- package/prompts/detail/generate/system-prompt.md +0 -13
- package/prompts/detail/generate/user-prompt.md +7 -0
- package/prompts/detail/update/system-prompt.md +1 -2
- package/prompts/detail/update/user-prompt.md +7 -0
- package/prompts/structure/generate/system-prompt.md +0 -19
- package/prompts/structure/generate/user-prompt.md +22 -1
- package/prompts/structure/update/system-prompt.md +0 -17
- package/prompts/structure/update/user-prompt.md +24 -0
- package/tests/agents/history/view.test.mjs +97 -0
- package/tests/agents/publish/publish-docs.test.mjs +0 -33
- package/tests/utils/history-utils.test.mjs +125 -97
- package/utils/auth-utils.mjs +2 -2
- package/utils/file-utils.mjs +1 -1
- package/utils/history-utils.mjs +3 -3
- package/agents/update/fs-tools/glob.mjs +0 -184
- package/agents/update/fs-tools/grep.mjs +0 -317
- package/agents/update/fs-tools/read-file.mjs +0 -309
- package/tests/agents/update/fs-tools/glob.test.mjs +0 -438
- package/tests/agents/update/fs-tools/grep.test.mjs +0 -279
- package/tests/agents/update/fs-tools/read-file.test.mjs +0 -549
|
@@ -446,39 +446,6 @@ describe("publish-docs", () => {
|
|
|
446
446
|
expect(saveValueToConfigSpy).not.toHaveBeenCalledWith("boardId", expect.anything());
|
|
447
447
|
});
|
|
448
448
|
|
|
449
|
-
// ERROR HANDLING TESTS
|
|
450
|
-
test("should handle failed official access token (null)", async () => {
|
|
451
|
-
loadConfigFromFileSpy.mockResolvedValue({});
|
|
452
|
-
getOfficialAccessTokenSpy.mockResolvedValue(null);
|
|
453
|
-
mockOptions.prompts.select.mockResolvedValue("default");
|
|
454
|
-
|
|
455
|
-
const result = await publishDocs(
|
|
456
|
-
{
|
|
457
|
-
docsDir: "./docs",
|
|
458
|
-
appUrl: "https://docsmith.aigne.io",
|
|
459
|
-
},
|
|
460
|
-
mockOptions,
|
|
461
|
-
);
|
|
462
|
-
|
|
463
|
-
expect(result.message).toBe("❌ Failed to publish docs: Failed to get official access token");
|
|
464
|
-
});
|
|
465
|
-
|
|
466
|
-
test("should handle failed official access token (undefined)", async () => {
|
|
467
|
-
loadConfigFromFileSpy.mockResolvedValue({});
|
|
468
|
-
getOfficialAccessTokenSpy.mockResolvedValue(undefined);
|
|
469
|
-
mockOptions.prompts.select.mockResolvedValue("default");
|
|
470
|
-
|
|
471
|
-
const result = await publishDocs(
|
|
472
|
-
{
|
|
473
|
-
docsDir: "./docs",
|
|
474
|
-
appUrl: "https://docsmith.aigne.io",
|
|
475
|
-
},
|
|
476
|
-
mockOptions,
|
|
477
|
-
);
|
|
478
|
-
|
|
479
|
-
expect(result.message).toBe("❌ Failed to publish docs: Failed to get official access token");
|
|
480
|
-
});
|
|
481
|
-
|
|
482
449
|
test("should handle checkCacheSession failure", async () => {
|
|
483
450
|
loadConfigFromFileSpy.mockResolvedValue({});
|
|
484
451
|
getOfficialAccessTokenSpy.mockResolvedValue("valid-token");
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test, mock, spyOn } from "bun:test";
|
|
2
2
|
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { DOC_SMITH_DIR } from "../../utils/constants/index.mjs";
|
|
5
|
-
import {
|
|
5
|
+
import { recordUpdate, getHistory } from "../../utils/history-utils.mjs";
|
|
6
6
|
|
|
7
|
+
// Mock the child_process module
|
|
7
8
|
const TEST_DIR = join(process.cwd(), `${DOC_SMITH_DIR}-test`);
|
|
8
9
|
const ORIGINAL_CWD = process.cwd();
|
|
9
10
|
|
|
10
|
-
describe("History Utils
|
|
11
|
-
|
|
11
|
+
describe("History Utils", () => {
|
|
12
|
+
let execSyncMock;
|
|
13
|
+
|
|
14
|
+
beforeEach(async () => {
|
|
12
15
|
// Clean up test directory
|
|
13
16
|
if (existsSync(TEST_DIR)) {
|
|
14
17
|
rmSync(TEST_DIR, { recursive: true });
|
|
@@ -17,6 +20,9 @@ describe("History Utils - Unified", () => {
|
|
|
17
20
|
|
|
18
21
|
// Change to test directory
|
|
19
22
|
process.chdir(TEST_DIR);
|
|
23
|
+
|
|
24
|
+
// Get the mocked execSync
|
|
25
|
+
execSyncMock = spyOn(await import("node:child_process"), "execSync");
|
|
20
26
|
});
|
|
21
27
|
|
|
22
28
|
afterEach(() => {
|
|
@@ -27,11 +33,8 @@ describe("History Utils - Unified", () => {
|
|
|
27
33
|
if (existsSync(TEST_DIR)) {
|
|
28
34
|
rmSync(TEST_DIR, { recursive: true });
|
|
29
35
|
}
|
|
30
|
-
});
|
|
31
36
|
|
|
32
|
-
|
|
33
|
-
const hasGit = isGitAvailable();
|
|
34
|
-
expect(typeof hasGit).toBe("boolean");
|
|
37
|
+
mock.restore();
|
|
35
38
|
});
|
|
36
39
|
|
|
37
40
|
test("skips recording on empty feedback", () => {
|
|
@@ -46,133 +49,158 @@ describe("History Utils - Unified", () => {
|
|
|
46
49
|
expect(history.entries.length).toBe(0);
|
|
47
50
|
});
|
|
48
51
|
|
|
49
|
-
test("
|
|
52
|
+
test("git not available - write to YAML only", () => {
|
|
53
|
+
// Mock git as not available
|
|
54
|
+
execSyncMock.mockImplementation((command) => {
|
|
55
|
+
if (command === "git --version") {
|
|
56
|
+
throw new Error("git: command not found");
|
|
57
|
+
}
|
|
58
|
+
throw new Error("Unknown command");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Record update
|
|
50
62
|
recordUpdate({
|
|
51
|
-
operation: "
|
|
63
|
+
operation: "document_update",
|
|
52
64
|
feedback: "Test feedback",
|
|
65
|
+
documentPath: "/test",
|
|
53
66
|
});
|
|
54
67
|
|
|
68
|
+
// Verify YAML record was created
|
|
55
69
|
const history = getHistory();
|
|
56
70
|
expect(history.entries.length).toBe(1);
|
|
57
71
|
expect(history.entries[0].feedback).toBe("Test feedback");
|
|
58
|
-
expect(history.entries[0].operation).toBe("
|
|
72
|
+
expect(history.entries[0].operation).toBe("document_update");
|
|
73
|
+
expect(history.entries[0].documentPath).toBe("/test");
|
|
59
74
|
expect(history.entries[0].timestamp).toBeDefined();
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
test("records document path when provided as string", () => {
|
|
63
|
-
recordUpdate({
|
|
64
|
-
operation: "document_update",
|
|
65
|
-
feedback: "Update document",
|
|
66
|
-
documentPath: "/getting-started",
|
|
67
|
-
});
|
|
68
75
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
expect(
|
|
76
|
+
// Verify no git repository was created
|
|
77
|
+
const gitDir = join(process.cwd(), DOC_SMITH_DIR, ".git");
|
|
78
|
+
expect(existsSync(gitDir)).toBe(false);
|
|
72
79
|
});
|
|
73
80
|
|
|
74
|
-
test("
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
test("current directory is git repo - skip git integration", () => {
|
|
82
|
+
// Mock git as available and in repo
|
|
83
|
+
execSyncMock.mockImplementation((command) => {
|
|
84
|
+
if (command === "git --version") {
|
|
85
|
+
return "git version 2.30.0";
|
|
86
|
+
}
|
|
87
|
+
if (command === "git rev-parse --is-inside-work-tree") {
|
|
88
|
+
return "true";
|
|
89
|
+
}
|
|
90
|
+
throw new Error("Unknown command");
|
|
79
91
|
});
|
|
80
92
|
|
|
81
|
-
|
|
82
|
-
expect(history.entries.length).toBe(1);
|
|
83
|
-
expect(history.entries[0].feedback).toBe("Update single document");
|
|
84
|
-
expect(history.entries[0].documentPath).toBe("/getting-started");
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test("does not include documentPath field when documentPath is null", () => {
|
|
93
|
+
// Record update
|
|
88
94
|
recordUpdate({
|
|
89
95
|
operation: "structure_update",
|
|
90
|
-
feedback: "
|
|
91
|
-
documentPath: null,
|
|
96
|
+
feedback: "Structure update",
|
|
92
97
|
});
|
|
93
98
|
|
|
99
|
+
// Verify YAML record was created
|
|
94
100
|
const history = getHistory();
|
|
95
101
|
expect(history.entries.length).toBe(1);
|
|
96
|
-
expect(history.entries[0].
|
|
102
|
+
expect(history.entries[0].feedback).toBe("Structure update");
|
|
103
|
+
|
|
104
|
+
// Verify no new git repository was created (since we're already in a git repo)
|
|
105
|
+
const gitDir = join(process.cwd(), DOC_SMITH_DIR, ".git");
|
|
106
|
+
expect(existsSync(gitDir)).toBe(false);
|
|
97
107
|
});
|
|
98
108
|
|
|
99
|
-
test("
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
109
|
+
test("write YAML records and commit to git", () => {
|
|
110
|
+
// Ensure the DOC_SMITH_DIR exists
|
|
111
|
+
const docSmithDir = join(process.cwd(), DOC_SMITH_DIR);
|
|
112
|
+
mkdirSync(docSmithDir, { recursive: true });
|
|
113
|
+
|
|
114
|
+
// Mock git as available but not in repo
|
|
115
|
+
execSyncMock.mockImplementation((command) => {
|
|
116
|
+
if (command === "git --version") {
|
|
117
|
+
return "git version 2.30.0";
|
|
118
|
+
}
|
|
119
|
+
if (command === "git rev-parse --is-inside-work-tree") {
|
|
120
|
+
throw new Error("not a git repository");
|
|
121
|
+
}
|
|
122
|
+
if (command === "git init") {
|
|
123
|
+
return "Initialized empty Git repository";
|
|
124
|
+
}
|
|
125
|
+
if (command === 'git add .gitignore && git commit -m "Initialize doc-smith history"') {
|
|
126
|
+
return "Initial commit";
|
|
127
|
+
}
|
|
128
|
+
if (command === "git add docs/ config.yaml preferences.yml history.yaml") {
|
|
129
|
+
return "Files added";
|
|
130
|
+
}
|
|
131
|
+
if (command === "git diff --cached --quiet") {
|
|
132
|
+
throw new Error("Has changes");
|
|
133
|
+
}
|
|
134
|
+
if (command === 'git commit -m "First update"') {
|
|
135
|
+
return "Commit successful";
|
|
136
|
+
}
|
|
137
|
+
if (command === 'git commit -m "Second update"') {
|
|
138
|
+
return "Commit successful";
|
|
139
|
+
}
|
|
140
|
+
throw new Error("Unknown command: ", command);
|
|
141
|
+
});
|
|
107
142
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
143
|
+
// Record multiple updates - this should trigger git repo creation and commits
|
|
144
|
+
recordUpdate({
|
|
145
|
+
operation: "document_update",
|
|
146
|
+
feedback: "First update",
|
|
147
|
+
documentPath: "/first",
|
|
148
|
+
});
|
|
113
149
|
|
|
114
|
-
test("handles multiple updates", () => {
|
|
115
|
-
recordUpdate({ operation: "structure_update", feedback: "Update 1" });
|
|
116
|
-
recordUpdate({ operation: "document_update", feedback: "Update 2", documentPath: "/home" });
|
|
117
|
-
recordUpdate({ operation: "document_update", feedback: "Update 3", documentPath: "/about" });
|
|
118
150
|
recordUpdate({
|
|
119
151
|
operation: "translation_update",
|
|
120
|
-
feedback: "
|
|
121
|
-
documentPath: "/
|
|
152
|
+
feedback: "Second update",
|
|
153
|
+
documentPath: "/second",
|
|
122
154
|
});
|
|
123
155
|
|
|
156
|
+
// Verify YAML records
|
|
124
157
|
const history = getHistory();
|
|
125
|
-
expect(history.entries.length).toBe(
|
|
126
|
-
expect(history.entries[0].feedback).toBe("
|
|
127
|
-
expect(history.entries[
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
expect(
|
|
131
|
-
expect(
|
|
132
|
-
|
|
158
|
+
expect(history.entries.length).toBe(2);
|
|
159
|
+
expect(history.entries[0].feedback).toBe("Second update"); // Newest first
|
|
160
|
+
expect(history.entries[1].feedback).toBe("First update");
|
|
161
|
+
|
|
162
|
+
// Verify git commands were called
|
|
163
|
+
expect(execSyncMock).toHaveBeenCalledWith("git --version", { stdio: "ignore" });
|
|
164
|
+
expect(execSyncMock).toHaveBeenCalledWith("git rev-parse --is-inside-work-tree", {
|
|
165
|
+
cwd: process.cwd(),
|
|
166
|
+
stdio: "pipe",
|
|
167
|
+
encoding: "utf8",
|
|
168
|
+
});
|
|
169
|
+
expect(execSyncMock).toHaveBeenCalledWith("git init", {
|
|
170
|
+
cwd: join(process.cwd(), DOC_SMITH_DIR),
|
|
171
|
+
stdio: "ignore",
|
|
172
|
+
});
|
|
133
173
|
});
|
|
134
174
|
|
|
135
|
-
test("
|
|
136
|
-
|
|
175
|
+
test("query history.yaml - handle various scenarios", () => {
|
|
176
|
+
// Test empty history
|
|
177
|
+
let history = getHistory();
|
|
137
178
|
expect(history.entries).toBeDefined();
|
|
138
179
|
expect(history.entries.length).toBe(0);
|
|
139
|
-
});
|
|
140
180
|
|
|
141
|
-
|
|
142
|
-
// Create corrupted YAML file
|
|
181
|
+
// Test corrupted YAML file
|
|
143
182
|
const historyPath = join(process.cwd(), DOC_SMITH_DIR, "history.yaml");
|
|
144
183
|
mkdirSync(join(process.cwd(), DOC_SMITH_DIR), { recursive: true });
|
|
145
|
-
writeFileSync(historyPath, "invalid: yaml: content:
|
|
184
|
+
writeFileSync(historyPath, "invalid: yaml: content: 「「「", "utf8");
|
|
146
185
|
|
|
147
|
-
|
|
186
|
+
history = getHistory();
|
|
148
187
|
expect(history.entries).toBeDefined();
|
|
149
188
|
expect(history.entries.length).toBe(0);
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
test(`creates ${DOC_SMITH_DIR} directory if not exists`, () => {
|
|
153
|
-
recordUpdate({
|
|
154
|
-
operation: "document_update",
|
|
155
|
-
feedback: "Test",
|
|
156
|
-
});
|
|
157
189
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
expect(
|
|
173
|
-
|
|
174
|
-
// Validate it's a valid date
|
|
175
|
-
const date = new Date(timestamp);
|
|
176
|
-
expect(date.toISOString()).toBe(timestamp);
|
|
190
|
+
// Test valid history
|
|
191
|
+
const validHistory = {
|
|
192
|
+
entries: [
|
|
193
|
+
{
|
|
194
|
+
timestamp: "2023-01-01T00:00:00.000Z",
|
|
195
|
+
operation: "document_update",
|
|
196
|
+
feedback: "Valid entry",
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
};
|
|
200
|
+
writeFileSync(historyPath, JSON.stringify(validHistory), "utf8");
|
|
201
|
+
|
|
202
|
+
history = getHistory();
|
|
203
|
+
expect(history.entries.length).toBe(1);
|
|
204
|
+
expect(history.entries[0].feedback).toBe("Valid entry");
|
|
177
205
|
});
|
|
178
206
|
});
|
package/utils/auth-utils.mjs
CHANGED
|
@@ -157,7 +157,7 @@ export async function getAccessToken(appUrl, ltToken = "") {
|
|
|
157
157
|
* @param {string} baseUrl - The official service URL
|
|
158
158
|
* @returns {Promise<string>} - The access token
|
|
159
159
|
*/
|
|
160
|
-
export async function getOfficialAccessToken(baseUrl) {
|
|
160
|
+
export async function getOfficialAccessToken(baseUrl, openPage = true) {
|
|
161
161
|
// Early parameter validation
|
|
162
162
|
if (!baseUrl) {
|
|
163
163
|
throw new Error("baseUrl parameter is required for getOfficialAccessToken.");
|
|
@@ -188,7 +188,7 @@ export async function getOfficialAccessToken(baseUrl) {
|
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
// If token is found, return it
|
|
191
|
-
if (accessToken) {
|
|
191
|
+
if (accessToken || !openPage) {
|
|
192
192
|
return accessToken;
|
|
193
193
|
}
|
|
194
194
|
|
package/utils/file-utils.mjs
CHANGED
|
@@ -12,7 +12,7 @@ import { INTELLIGENT_SUGGESTION_TOKEN_THRESHOLD } from "./constants/index.mjs";
|
|
|
12
12
|
* @param {string} dir - Directory path to check
|
|
13
13
|
* @returns {boolean} True if inside a git repository
|
|
14
14
|
*/
|
|
15
|
-
function isInGitRepository(dir) {
|
|
15
|
+
export function isInGitRepository(dir) {
|
|
16
16
|
try {
|
|
17
17
|
execSync("git rev-parse --is-inside-work-tree", {
|
|
18
18
|
cwd: dir,
|
package/utils/history-utils.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { parse, stringify } from "yaml";
|
|
5
5
|
import { DOC_SMITH_DIR } from "./constants/index.mjs";
|
|
6
|
+
import { isInGitRepository } from "./file-utils.mjs";
|
|
6
7
|
|
|
7
8
|
const HISTORY_FILE = "history.yaml";
|
|
8
9
|
|
|
@@ -161,9 +162,8 @@ export function recordUpdate({ operation, feedback, documentPath = null }) {
|
|
|
161
162
|
// Always record in YAML
|
|
162
163
|
recordUpdateYaml({ operation, feedback, documentPath });
|
|
163
164
|
|
|
164
|
-
// Also record in git if available
|
|
165
|
-
if (isGitAvailable()) {
|
|
166
|
-
// Initialize git repo on first update if not exists
|
|
165
|
+
// Also record in git if git is available and not in a git repository
|
|
166
|
+
if (isGitAvailable() && !isInGitRepository(process.cwd())) {
|
|
167
167
|
ensureGitRepo();
|
|
168
168
|
recordUpdateGit({ feedback });
|
|
169
169
|
} else {
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import fsPromises from "node:fs/promises";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Sorts files by modification time (newest first), then alphabetically
|
|
7
|
-
*/
|
|
8
|
-
function sortFilesByModTime(files, basePath) {
|
|
9
|
-
return files
|
|
10
|
-
.map((file) => {
|
|
11
|
-
const fullPath = path.isAbsolute(file) ? file : path.resolve(basePath, file);
|
|
12
|
-
try {
|
|
13
|
-
const stats = fs.statSync(fullPath);
|
|
14
|
-
return {
|
|
15
|
-
path: file,
|
|
16
|
-
fullPath,
|
|
17
|
-
mtime: stats.mtimeMs || 0,
|
|
18
|
-
};
|
|
19
|
-
} catch {
|
|
20
|
-
return {
|
|
21
|
-
path: file,
|
|
22
|
-
fullPath,
|
|
23
|
-
mtime: 0,
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
})
|
|
27
|
-
.sort((a, b) => {
|
|
28
|
-
// Sort by modification time (newest first), then alphabetically
|
|
29
|
-
if (b.mtime !== a.mtime) {
|
|
30
|
-
return b.mtime - a.mtime;
|
|
31
|
-
}
|
|
32
|
-
return a.path.localeCompare(b.path);
|
|
33
|
-
})
|
|
34
|
-
.map((item) => item.path);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Filters out common ignore patterns
|
|
39
|
-
*/
|
|
40
|
-
function shouldIgnoreFile(filePath) {
|
|
41
|
-
const ignorePatterns = [
|
|
42
|
-
"node_modules",
|
|
43
|
-
".git",
|
|
44
|
-
".DS_Store",
|
|
45
|
-
".vscode",
|
|
46
|
-
".idea",
|
|
47
|
-
"dist",
|
|
48
|
-
"build",
|
|
49
|
-
"*.log",
|
|
50
|
-
"coverage",
|
|
51
|
-
".nyc_output",
|
|
52
|
-
".cache",
|
|
53
|
-
];
|
|
54
|
-
|
|
55
|
-
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
56
|
-
|
|
57
|
-
return ignorePatterns.some((pattern) => {
|
|
58
|
-
if (pattern.includes("*")) {
|
|
59
|
-
// Simple wildcard matching for patterns like "*.log"
|
|
60
|
-
const regex = new RegExp(pattern.replace(/\*/g, ".*"));
|
|
61
|
-
return regex.test(path.basename(normalizedPath));
|
|
62
|
-
} else {
|
|
63
|
-
// Directory or file name matching
|
|
64
|
-
return (
|
|
65
|
-
normalizedPath.includes(`/${pattern}/`) ||
|
|
66
|
-
normalizedPath.endsWith(`/${pattern}`) ||
|
|
67
|
-
normalizedPath.startsWith(`${pattern}/`) ||
|
|
68
|
-
normalizedPath === pattern
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export default async function glob({
|
|
75
|
-
pattern,
|
|
76
|
-
case_sensitive = false,
|
|
77
|
-
respect_git_ignore = true,
|
|
78
|
-
limit = 100,
|
|
79
|
-
}) {
|
|
80
|
-
let result = [];
|
|
81
|
-
let error = null;
|
|
82
|
-
const searchDir = process.cwd();
|
|
83
|
-
|
|
84
|
-
try {
|
|
85
|
-
// Validate required parameters
|
|
86
|
-
if (!pattern || typeof pattern !== "string" || pattern.trim() === "") {
|
|
87
|
-
throw new Error("Pattern parameter is required and cannot be empty");
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Use Node.js built-in glob
|
|
91
|
-
const globOptions = {
|
|
92
|
-
cwd: searchDir,
|
|
93
|
-
nodir: true, // Only return files, not directories
|
|
94
|
-
dot: true, // Include hidden files
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
// Note: Node.js fs.glob doesn't support case_sensitive option directly
|
|
98
|
-
// We'll handle case sensitivity in post-processing if needed
|
|
99
|
-
const iter = fsPromises.glob(pattern, globOptions);
|
|
100
|
-
const files = [];
|
|
101
|
-
|
|
102
|
-
for await (const file of iter) {
|
|
103
|
-
if (files.length >= limit) break;
|
|
104
|
-
|
|
105
|
-
// Apply ignore filters
|
|
106
|
-
if (shouldIgnoreFile(file)) {
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Handle case sensitivity if needed
|
|
111
|
-
if (case_sensitive === false) {
|
|
112
|
-
// Node.js glob is case-sensitive by default on most systems
|
|
113
|
-
// For case-insensitive matching, we rely on the pattern itself
|
|
114
|
-
// or the filesystem behavior
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
files.push(file);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Sort files by modification time (newest first)
|
|
121
|
-
const sortedFiles = sortFilesByModTime(files, searchDir);
|
|
122
|
-
|
|
123
|
-
// Build result message
|
|
124
|
-
let message;
|
|
125
|
-
if (sortedFiles.length === 0) {
|
|
126
|
-
message = `No files found matching pattern "${pattern}"`;
|
|
127
|
-
} else {
|
|
128
|
-
const fileCount = sortedFiles.length;
|
|
129
|
-
const truncated = files.length >= limit;
|
|
130
|
-
message = `Found ${fileCount}${truncated ? "+" : ""} file(s) matching "${pattern}"`;
|
|
131
|
-
message += ", sorted by modification time (newest first):";
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
result = {
|
|
135
|
-
files: sortedFiles,
|
|
136
|
-
count: sortedFiles.length,
|
|
137
|
-
message,
|
|
138
|
-
truncated: files.length >= limit,
|
|
139
|
-
};
|
|
140
|
-
} catch (err) {
|
|
141
|
-
error = err;
|
|
142
|
-
result = {
|
|
143
|
-
files: [],
|
|
144
|
-
count: 0,
|
|
145
|
-
message: `Error during glob search: ${err.message}`,
|
|
146
|
-
truncated: false,
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return {
|
|
151
|
-
command: "glob",
|
|
152
|
-
arguments: {
|
|
153
|
-
pattern,
|
|
154
|
-
case_sensitive,
|
|
155
|
-
respect_git_ignore,
|
|
156
|
-
limit,
|
|
157
|
-
},
|
|
158
|
-
result,
|
|
159
|
-
error: error && { message: error.message },
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
glob.input_schema = {
|
|
164
|
-
type: "object",
|
|
165
|
-
properties: {
|
|
166
|
-
pattern: {
|
|
167
|
-
type: "string",
|
|
168
|
-
description: 'The glob pattern to match files against (e.g., "**/*.js", "src/**/*.ts")',
|
|
169
|
-
},
|
|
170
|
-
case_sensitive: {
|
|
171
|
-
type: "boolean",
|
|
172
|
-
description: "Optional: Whether the search should be case-sensitive (defaults to false)",
|
|
173
|
-
},
|
|
174
|
-
respect_git_ignore: {
|
|
175
|
-
type: "boolean",
|
|
176
|
-
description: "Optional: Whether to respect .gitignore patterns (defaults to true)",
|
|
177
|
-
},
|
|
178
|
-
limit: {
|
|
179
|
-
type: "number",
|
|
180
|
-
description: "Optional: Maximum number of files to return (defaults to 100)",
|
|
181
|
-
},
|
|
182
|
-
},
|
|
183
|
-
required: ["pattern"],
|
|
184
|
-
};
|