@brunobrise/xfeat 1.2.0 → 1.4.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/.github/workflows/ci.yml +12 -12
- package/.vscode/settings.json +1 -1
- package/README.md +7 -2
- package/index.js +24 -3
- package/index.test.js +26 -0
- package/package.json +2 -1
package/.github/workflows/ci.yml
CHANGED
|
@@ -2,9 +2,9 @@ name: CI
|
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
4
|
push:
|
|
5
|
-
branches: [
|
|
5
|
+
branches: ["main"]
|
|
6
6
|
pull_request:
|
|
7
|
-
branches: [
|
|
7
|
+
branches: ["main"]
|
|
8
8
|
|
|
9
9
|
jobs:
|
|
10
10
|
build:
|
|
@@ -15,13 +15,13 @@ jobs:
|
|
|
15
15
|
node-version: [18.x, 20.x]
|
|
16
16
|
|
|
17
17
|
steps:
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
- name: Use Node.js ${{ matrix.node-version }}
|
|
20
|
+
uses: actions/setup-node@v4
|
|
21
|
+
with:
|
|
22
|
+
node-version: ${{ matrix.node-version }}
|
|
23
|
+
cache: "npm"
|
|
24
|
+
- run: npm ci
|
|
25
|
+
- run: npm run lint
|
|
26
|
+
- run: npm test
|
|
27
|
+
- run: npm audit --audit-level=critical
|
package/.vscode/settings.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{}
|
|
1
|
+
{}
|
package/README.md
CHANGED
|
@@ -11,11 +11,15 @@
|
|
|
11
11
|
- **Agentic Source Code Reading:** Rather than guessing based off function identifiers, the AI utilizes a specialized `view_file` tool to selectively dive into the raw source code wherever AST context is insufficient.
|
|
12
12
|
- **Automated Mermaid Diagrams:** Visually maps out how files interact at macro and global architecture levels.
|
|
13
13
|
- **Structured Markdown Deliverables:** Produces a neat, hierarchical `FEATURES.md` report encompassing everything from the executive summary to granular file logic.
|
|
14
|
-
- **Smart Directory Traversal:** Adheres to your local `.gitignore` rules to avoid processing build artifacts and generic dependencies.
|
|
14
|
+
- **Smart Directory Traversal:** Adheres to your local `.gitignore` and optional custom `.xfeatignore` rules to avoid processing build artifacts and generic dependencies.
|
|
15
15
|
|
|
16
16
|
## How It Works
|
|
17
17
|
|
|
18
|
-
The engine executes in an expanding
|
|
18
|
+
The engine executes in an expanding 4-stage pipeline:
|
|
19
|
+
|
|
20
|
+
0. **AI Pre-filtering (Stage 0):**
|
|
21
|
+
- Interactively requests permission to AI-filter the target files.
|
|
22
|
+
- Cleans the file list by intelligently removing trivial boilerplate, config files, and UI assets dynamically, saving time and tokens.
|
|
19
23
|
|
|
20
24
|
1. **Micro Analysis (File-Level):**
|
|
21
25
|
- Identifies granular structural signatures across source files.
|
|
@@ -94,6 +98,7 @@ The foundational Tree-sitter AST parser natively understands:
|
|
|
94
98
|
- TypeScript (`.ts`, `.tsx`)
|
|
95
99
|
- Python (`.py`)
|
|
96
100
|
- Rust (`.rs`)
|
|
101
|
+
- Go (`.go`)
|
|
97
102
|
|
|
98
103
|
## License
|
|
99
104
|
|
package/index.js
CHANGED
|
@@ -51,6 +51,17 @@ async function getIgnores(targetDir) {
|
|
|
51
51
|
} catch (err) {
|
|
52
52
|
// No .gitignore found, proceed with defaults
|
|
53
53
|
}
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const xfeatignoreContent = await fs.readFile(
|
|
57
|
+
path.join(targetDir, ".xfeatignore"),
|
|
58
|
+
"utf8",
|
|
59
|
+
);
|
|
60
|
+
ig.add(xfeatignoreContent);
|
|
61
|
+
} catch (err) {
|
|
62
|
+
// No .xfeatignore found, silently continue
|
|
63
|
+
}
|
|
64
|
+
|
|
54
65
|
return ig;
|
|
55
66
|
}
|
|
56
67
|
|
|
@@ -154,7 +165,8 @@ async function extractStructure(filePath) {
|
|
|
154
165
|
const isClass =
|
|
155
166
|
type.includes("class") ||
|
|
156
167
|
type.includes("struct") ||
|
|
157
|
-
type.includes("interface")
|
|
168
|
+
type.includes("interface") ||
|
|
169
|
+
type === "type_spec";
|
|
158
170
|
const isFunction =
|
|
159
171
|
type.includes("function") ||
|
|
160
172
|
type.includes("method") ||
|
|
@@ -169,8 +181,14 @@ async function extractStructure(filePath) {
|
|
|
169
181
|
if (isClass) {
|
|
170
182
|
const nameNode =
|
|
171
183
|
node.childForFieldName("name") ||
|
|
172
|
-
node.children.find((c) => c.type === "identifier")
|
|
173
|
-
|
|
184
|
+
node.children.find((c) => c.type === "identifier") ||
|
|
185
|
+
node.children.find((c) => c.type === "type_identifier");
|
|
186
|
+
if (nameNode) {
|
|
187
|
+
structure.classes.push(nameNode.text);
|
|
188
|
+
if (ext === ".go" && /^[A-Z]/.test(nameNode.text)) {
|
|
189
|
+
structure.exports.push(nameNode.text);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
174
192
|
}
|
|
175
193
|
|
|
176
194
|
if (isFunction) {
|
|
@@ -179,6 +197,9 @@ async function extractStructure(filePath) {
|
|
|
179
197
|
node.children.find((c) => c.type === "identifier");
|
|
180
198
|
if (nameNode && !nameNode.text.startsWith("__")) {
|
|
181
199
|
structure.functions.push(nameNode.text);
|
|
200
|
+
if (ext === ".go" && /^[A-Z]/.test(nameNode.text)) {
|
|
201
|
+
structure.exports.push(nameNode.text);
|
|
202
|
+
}
|
|
182
203
|
}
|
|
183
204
|
}
|
|
184
205
|
|
package/index.test.js
CHANGED
|
@@ -53,6 +53,17 @@ def py_function():
|
|
|
53
53
|
`,
|
|
54
54
|
);
|
|
55
55
|
|
|
56
|
+
await fs.writeFile(
|
|
57
|
+
path.join(testDir, "sample.go"),
|
|
58
|
+
`
|
|
59
|
+
package main
|
|
60
|
+
import "fmt"
|
|
61
|
+
type MyGoStruct struct {}
|
|
62
|
+
func (m *MyGoStruct) GoMethod() {}
|
|
63
|
+
func GoFunction() {}
|
|
64
|
+
`,
|
|
65
|
+
);
|
|
66
|
+
|
|
56
67
|
await fs.writeFile(path.join(testDir, "unsupported.txt"), "Hello world");
|
|
57
68
|
|
|
58
69
|
// Initialize TreeSitter before testing extraction
|
|
@@ -132,6 +143,21 @@ def py_function():
|
|
|
132
143
|
expect(structure.functions).toContain("rs_function");
|
|
133
144
|
});
|
|
134
145
|
|
|
146
|
+
it("should extract AST structure for Go files", async () => {
|
|
147
|
+
const goFilePath = path.join(testDir, "sample.go");
|
|
148
|
+
const structure = await extractStructure(goFilePath);
|
|
149
|
+
|
|
150
|
+
expect(structure).not.toBeNull();
|
|
151
|
+
expect(structure.file).toBe(goFilePath);
|
|
152
|
+
expect(structure.classes).toContain("MyGoStruct");
|
|
153
|
+
expect(structure.functions).toContain("GoMethod");
|
|
154
|
+
expect(structure.functions).toContain("GoFunction");
|
|
155
|
+
expect(structure.exports).toContain("MyGoStruct");
|
|
156
|
+
expect(structure.exports).toContain("GoMethod");
|
|
157
|
+
expect(structure.exports).toContain("GoFunction");
|
|
158
|
+
expect(structure.imports).toContain('"fmt"');
|
|
159
|
+
});
|
|
160
|
+
|
|
135
161
|
it("should return null for unsupported file extensions", async () => {
|
|
136
162
|
const txtFilePath = path.join(testDir, "unsupported.txt");
|
|
137
163
|
const structure = await extractStructure(txtFilePath);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brunobrise/xfeat",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Automated AI-driven CLI for codebase analysis and feature extraction.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"map-stream": "^0.0.7",
|
|
43
43
|
"tree-sitter-bash": "^0.25.1",
|
|
44
44
|
"tree-sitter-css": "^0.25.0",
|
|
45
|
+
"tree-sitter-go": "^0.25.0",
|
|
45
46
|
"tree-sitter-html": "^0.23.2",
|
|
46
47
|
"tree-sitter-javascript": "^0.25.0",
|
|
47
48
|
"tree-sitter-json": "^0.24.8",
|