@adityaaria/spark 6.0.20 → 6.0.22
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/bin/spark-install.sh
CHANGED
|
@@ -99,8 +99,8 @@ print_help() {
|
|
|
99
99
|
bash bin/spark-install.sh [options]
|
|
100
100
|
|
|
101
101
|
Options:
|
|
102
|
-
-g, --global Install to global agent config (~/.
|
|
103
|
-
Default: project scope (./.
|
|
102
|
+
-g, --global Install to global agent config (~/.agents/skills/)
|
|
103
|
+
Default: project scope (./.agents/skills/)
|
|
104
104
|
--force Re-install even if already installed
|
|
105
105
|
--dry-run Show what would be done without making changes
|
|
106
106
|
-u, --uninstall Safely remove SPARK from agent configs
|
package/package.json
CHANGED
|
@@ -11,7 +11,12 @@ Use this skill to scan and document a codebase's architecture, operational rules
|
|
|
11
11
|
|
|
12
12
|
**Announce at start:** "spark detection 💥 Using project-scanner to analyze repository DNA"
|
|
13
13
|
|
|
14
|
-
**Save findings to:** The `.docs/` directory. **You must physically create this directory and save the files to disk using your tools. Do NOT just print the output to the user.**
|
|
14
|
+
**Save findings to:** The `.docs/` directory. **You must physically create this directory and save the files to disk using your tools. Do NOT just print the output to the user.**
|
|
15
|
+
|
|
16
|
+
## Multi-Project Workspaces (CRITICAL)
|
|
17
|
+
If the workspace contains multiple independent projects or repositories (e.g., a `frontend` folder and a `backend` folder), you MUST generate a separate `.docs/` directory inside EACH project's root folder (e.g., `frontend/.docs/`, `backend/.docs/`). Do NOT combine them into a single global `.docs/` or dump everything into just one project's folder. Treat each independent project as its own ecosystem that requires its own documentation.
|
|
18
|
+
|
|
19
|
+
For large projects, break down the documentation logically (e.g., `.docs/PROJECT_SCAN.md`, `.docs/API_CONTRACT.md`, `.docs/DOMAINS/`) instead of creating one massive monolithic file.
|
|
15
20
|
|
|
16
21
|
## Handling Massive Codebases (Subagent Delegation)
|
|
17
22
|
If the repository is extremely large and analyzing all four pillars sequentially risks exceeding context limits or taking too long:
|
|
@@ -53,7 +58,8 @@ Identify conventions, rules, and technical debt enforced or found in the codebas
|
|
|
53
58
|
## Execution Checklist
|
|
54
59
|
|
|
55
60
|
- `[ ]` **Step 1:** Announce skill usage exactly as required.
|
|
56
|
-
- `[ ]` **Step 2:**
|
|
57
|
-
- `[ ]` **Step 3:** Scan
|
|
58
|
-
- `[ ]` **Step 4:**
|
|
59
|
-
- `[ ]` **Step 5
|
|
61
|
+
- `[ ]` **Step 2:** Identify if the workspace contains multiple independent projects (e.g., frontend vs backend). If so, plan to scan and generate `.docs/` for EACH project separately.
|
|
62
|
+
- `[ ]` **Step 3:** Scan root configuration files and prioritize searching for Swagger/OpenAPI specifications.
|
|
63
|
+
- `[ ]` **Step 4:** Scan source directory structures to infer the language, framework, and architectural patterns.
|
|
64
|
+
- `[ ]` **Step 5:** Analyze testing frameworks, CI/CD workflows, and actively hunt for anti-patterns and legacy traps.
|
|
65
|
+
- `[ ]` **Step 6 (CRITICAL):** You MUST use your file-writing tools to physically create the `.docs/` directory (inside each project root if multi-repo) and save the markdown files (e.g. `PROJECT_SCAN.md`, `API_CONTRACT.md`) into it. DO NOT just print the summary into the chat! You must physically write the files to disk.
|
|
@@ -10,7 +10,12 @@ If you were dispatched as a subagent to execute a specific task, skip this skill
|
|
|
10
10
|
<EXTREMELY-IMPORTANT>
|
|
11
11
|
If you think there is even a 1% chance a skill might apply to what you are doing, you ABSOLUTELY MUST invoke the skill.
|
|
12
12
|
|
|
13
|
-
IF A SKILL APPLIES TO YOUR TASK, YOU DO NOT HAVE A CHOICE. YOU MUST USE IT.
|
|
13
|
+
IF A SKILL APPLIES TO YOUR TASK, OR IF THE USER EXPLICITLY REQUESTS A SKILL, YOU DO NOT HAVE A CHOICE. YOU MUST USE IT IMMEDIATELY.
|
|
14
|
+
|
|
15
|
+
**Do NOT ask clarifying questions first.**
|
|
16
|
+
**Do NOT start a conversation.**
|
|
17
|
+
**Do NOT explain what you are going to do.**
|
|
18
|
+
**JUST EXECUTE THE SKILL DIRECTLY AS YOUR FIRST TOOL CALL.**
|
|
14
19
|
|
|
15
20
|
This is not negotiable. This is not optional. You cannot rationalize your way out of this.
|
|
16
21
|
</EXTREMELY-IMPORTANT>
|
|
@@ -49,6 +54,8 @@ Skills speak in actions ("dispatch a subagent", "create a todo", "read a file")
|
|
|
49
54
|
|
|
50
55
|
**Invoke relevant or requested skills BEFORE any response or action.** Even a 1% chance a skill might apply means that you should invoke the skill to check. If an invoked skill turns out to be wrong for the situation, you don't need to use it.
|
|
51
56
|
|
|
57
|
+
**CRITICAL:** If the user mentions a specific skill by name (e.g., "scan project", "project-scanner", "run deployment"), you MUST invoke that skill IMMEDIATELY as your very first tool call. Do not answer conversationally. Do not ask for confirmation. Execute the skill first.
|
|
58
|
+
|
|
52
59
|
## Announcement Format
|
|
53
60
|
When you invoke a skill, you MUST announce it to the developer using exactly this format as the very first thing you say:
|
|
54
61
|
`spark detection 💥 Using [skill] to [purpose]`
|
package/src/cli/output.js
CHANGED
|
@@ -24,7 +24,7 @@ export function printHelp() {
|
|
|
24
24
|
printLine('');
|
|
25
25
|
printMuted('Wraps the native SPARK installer (bin/spark-install.sh).');
|
|
26
26
|
printLine('');
|
|
27
|
-
printLine(labelValue('Options', '-g, --global Install to global agent config (~/.
|
|
27
|
+
printLine(labelValue('Options', '-g, --global Install to global agent config (~/.agents/skills/)'));
|
|
28
28
|
printLine(labelValue(' ', '--force Re-install even if already installed'));
|
|
29
29
|
printLine(labelValue(' ', '--dry-run Show what would be done without making changes'));
|
|
30
30
|
printLine(labelValue(' ', '-h, --help Show this help message'));
|
|
@@ -9,9 +9,8 @@
|
|
|
9
9
|
<!-- Marked.js for Markdown parsing -->
|
|
10
10
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
11
11
|
<!-- Vis-Network for Interactive Graphs -->
|
|
12
|
-
<script type="text/javascript" src="https://
|
|
13
|
-
|
|
14
|
-
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
12
|
+
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/vis-network/9.1.9/standalone/umd/vis-network.min.js"></script>
|
|
13
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js"></script>
|
|
15
14
|
<style>
|
|
16
15
|
:root {
|
|
17
16
|
--bg-color: #0d0d0d;
|
|
@@ -491,9 +490,12 @@
|
|
|
491
490
|
totalFiles++;
|
|
492
491
|
if (isDanger) {
|
|
493
492
|
dangerFilesCount++;
|
|
494
|
-
dangerListHTML += `<div style="padding: 12px 16px; background: var(--bg-color); border: 1px solid var(--glass-border); border-radius: 8px; margin-bottom:
|
|
495
|
-
<
|
|
496
|
-
|
|
493
|
+
dangerListHTML += `<div style="padding: 12px 16px; background: var(--bg-color); border: 1px solid var(--glass-border); border-radius: 8px; margin-bottom: 12px; display: flex; flex-direction: column; gap: 8px;">
|
|
494
|
+
<div style="display: flex; justify-content: space-between; align-items: center;">
|
|
495
|
+
<span style="font-family: var(--font-mono); font-size: 13px; color: var(--text-main);">${node.path}</span>
|
|
496
|
+
<span style="font-size: 10px; color: var(--danger); border: 1px solid var(--glass-border); padding: 4px 8px; border-radius: 20px;">Flagged</span>
|
|
497
|
+
</div>
|
|
498
|
+
<div style="font-size: 12px; color: var(--text-muted); line-height: 1.4;">${node.dangerReason || 'Legacy anti-pattern detected.'}</div>
|
|
497
499
|
</div>`;
|
|
498
500
|
}
|
|
499
501
|
}
|
|
@@ -611,22 +613,89 @@ ${steps}
|
|
|
611
613
|
}
|
|
612
614
|
|
|
613
615
|
function renderGraph() {
|
|
614
|
-
if(networkInstance) return; //
|
|
615
|
-
|
|
616
|
-
// Mock graph data, in a real scenario we'd parse .docs/
|
|
617
|
-
const nodes = new vis.DataSet([
|
|
618
|
-
{ id: 1, label: 'Client App', color: '#00ffaa', shape: 'box' },
|
|
619
|
-
{ id: 2, label: 'API Gateway', color: '#00ffaa', shape: 'box' },
|
|
620
|
-
{ id: 3, label: 'Auth Service', color: '#ff4757', shape: 'box' },
|
|
621
|
-
{ id: 4, label: 'Database', color: '#f1f5f9', shape: 'database' },
|
|
622
|
-
]);
|
|
623
|
-
const edges = new vis.DataSet([
|
|
624
|
-
{ from: 1, to: 2, arrows: 'to' },
|
|
625
|
-
{ from: 2, to: 3, arrows: 'to', label: 'verify token' },
|
|
626
|
-
{ from: 3, to: 4, arrows: 'to' },
|
|
627
|
-
]);
|
|
628
|
-
|
|
616
|
+
if (networkInstance) return; // Already rendered
|
|
617
|
+
|
|
629
618
|
const container = document.getElementById('network-graph');
|
|
619
|
+
if (typeof vis === 'undefined') {
|
|
620
|
+
container.innerHTML = '<div style="padding: 40px; text-align: center; color: var(--danger);">Failed to load graph library. Please check your internet connection or use a local build.</div>';
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Dynamic Mermaid Parser
|
|
625
|
+
let nodesMap = new Map();
|
|
626
|
+
let edgesList = [];
|
|
627
|
+
let nodeIdCounter = 1;
|
|
628
|
+
|
|
629
|
+
function parseNode(str) {
|
|
630
|
+
const match = str.trim().match(/^([a-zA-Z0-9_-]+)(.*)$/);
|
|
631
|
+
if (!match) return null;
|
|
632
|
+
const rawId = match[1];
|
|
633
|
+
let label = rawId;
|
|
634
|
+
let shape = 'box';
|
|
635
|
+
const rest = match[2].trim();
|
|
636
|
+
if (rest) {
|
|
637
|
+
const labelMatch = rest.match(/[\[\(\{]+(.*?)[\]\}\)]+/);
|
|
638
|
+
if (labelMatch) label = labelMatch[1].replace(/["']/g, '');
|
|
639
|
+
if (rest.includes('[((') || rest.includes('[([')) shape = 'database';
|
|
640
|
+
else if (rest.includes('{')) shape = 'diamond';
|
|
641
|
+
else if (rest.includes('((')) shape = 'ellipse';
|
|
642
|
+
}
|
|
643
|
+
return { rawId, label, shape };
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
docsData.forEach(doc => {
|
|
647
|
+
const mermaidRegex = /```mermaid\s+([\s\S]*?)```/g;
|
|
648
|
+
let match;
|
|
649
|
+
while ((match = mermaidRegex.exec(doc.content)) !== null) {
|
|
650
|
+
const lines = match[1].split('\n');
|
|
651
|
+
lines.forEach(line => {
|
|
652
|
+
let l = line.trim();
|
|
653
|
+
if (!l || l.startsWith('graph') || l.startsWith('flowchart') || l.startsWith('subgraph') || l.startsWith('end')) return;
|
|
654
|
+
|
|
655
|
+
const parts = l.split('-->');
|
|
656
|
+
if (parts.length >= 2) {
|
|
657
|
+
let fromPart = parts[0].trim();
|
|
658
|
+
let toPart = parts.slice(1).join('-->').trim();
|
|
659
|
+
|
|
660
|
+
let edgeLabel = '';
|
|
661
|
+
if (toPart.startsWith('|')) {
|
|
662
|
+
const pipeEnd = toPart.indexOf('|', 1);
|
|
663
|
+
if (pipeEnd > -1) {
|
|
664
|
+
edgeLabel = toPart.substring(1, pipeEnd).trim();
|
|
665
|
+
toPart = toPart.substring(pipeEnd + 1).trim();
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
const fNode = parseNode(fromPart);
|
|
670
|
+
const tNode = parseNode(toPart);
|
|
671
|
+
|
|
672
|
+
if (fNode && tNode) {
|
|
673
|
+
if (!nodesMap.has(fNode.rawId)) {
|
|
674
|
+
nodesMap.set(fNode.rawId, { id: nodeIdCounter++, label: fNode.label, shape: fNode.shape, color: { background: '#151515', border: '#2a2a2a' } });
|
|
675
|
+
}
|
|
676
|
+
if (!nodesMap.has(tNode.rawId)) {
|
|
677
|
+
nodesMap.set(tNode.rawId, { id: nodeIdCounter++, label: tNode.label, shape: tNode.shape, color: { background: '#151515', border: '#2a2a2a' } });
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
edgesList.push({
|
|
681
|
+
from: nodesMap.get(fNode.rawId).id,
|
|
682
|
+
to: nodesMap.get(tNode.rawId).id,
|
|
683
|
+
arrows: 'to',
|
|
684
|
+
label: edgeLabel
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
if (nodesMap.size === 0) {
|
|
693
|
+
container.innerHTML = '<div style="padding: 40px; text-align: center; color: var(--text-muted);">No Mermaid architecture graphs found in .docs/ files. Run the project-scanner skill first.</div>';
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
const nodes = new vis.DataSet(Array.from(nodesMap.values()));
|
|
698
|
+
const edges = new vis.DataSet(edgesList);
|
|
630
699
|
const data = { nodes, edges };
|
|
631
700
|
const options = {
|
|
632
701
|
nodes: {
|
|
@@ -735,8 +804,15 @@ ${steps}
|
|
|
735
804
|
},
|
|
736
805
|
response: [{
|
|
737
806
|
name: "Success",
|
|
807
|
+
originalRequest: {
|
|
808
|
+
method: ep.method,
|
|
809
|
+
header: [{ key: "Authorization", value: "Bearer {{token}}" }, { key: "Content-Type", value: "application/json" }],
|
|
810
|
+
body: ep.method !== 'GET' ? { mode: "raw", raw: JSON.stringify(mock, null, 2) } : undefined,
|
|
811
|
+
url: { raw: `{{baseUrl}}/${pathArr.join('/')}`, host: ["{{baseUrl}}"], path: pathArr }
|
|
812
|
+
},
|
|
738
813
|
status: "OK",
|
|
739
814
|
code: 200,
|
|
815
|
+
_postman_previewlanguage: "json",
|
|
740
816
|
header: [{ key: "Content-Type", value: "application/json" }],
|
|
741
817
|
body: JSON.stringify({ success: true, data: mock }, null, 2)
|
|
742
818
|
}]
|
|
@@ -751,8 +827,15 @@ ${steps}
|
|
|
751
827
|
},
|
|
752
828
|
response: [{
|
|
753
829
|
name: "Bad Request",
|
|
830
|
+
originalRequest: {
|
|
831
|
+
method: ep.method,
|
|
832
|
+
header: [{ key: "Authorization", value: "Bearer {{token}}" }, { key: "Content-Type", value: "application/json" }],
|
|
833
|
+
body: ep.method !== 'GET' ? { mode: "raw", raw: "{}" } : undefined,
|
|
834
|
+
url: { raw: `{{baseUrl}}/${pathArr.join('/')}`, host: ["{{baseUrl}}"], path: pathArr }
|
|
835
|
+
},
|
|
754
836
|
status: "Bad Request",
|
|
755
837
|
code: 400,
|
|
838
|
+
_postman_previewlanguage: "json",
|
|
756
839
|
header: [{ key: "Content-Type", value: "application/json" }],
|
|
757
840
|
body: JSON.stringify({ success: false, error: { code: "VALIDATION_ERROR", message: "Invalid parameters provided" } }, null, 2)
|
|
758
841
|
}]
|
|
@@ -766,8 +849,14 @@ ${steps}
|
|
|
766
849
|
},
|
|
767
850
|
response: [{
|
|
768
851
|
name: "Unauthorized",
|
|
852
|
+
originalRequest: {
|
|
853
|
+
method: ep.method,
|
|
854
|
+
header: [], // No auth
|
|
855
|
+
url: { raw: `{{baseUrl}}/${pathArr.join('/')}`, host: ["{{baseUrl}}"], path: pathArr }
|
|
856
|
+
},
|
|
769
857
|
status: "Unauthorized",
|
|
770
858
|
code: 401,
|
|
859
|
+
_postman_previewlanguage: "json",
|
|
771
860
|
header: [{ key: "Content-Type", value: "application/json" }],
|
|
772
861
|
body: JSON.stringify({ success: false, error: { code: "UNAUTHORIZED", message: "Missing or invalid token" } }, null, 2)
|
|
773
862
|
}]
|
package/src/dashboard/server.js
CHANGED
|
@@ -45,28 +45,60 @@ const server = http.createServer((req, res) => {
|
|
|
45
45
|
// API Route: Get File Tree (for Heatmap)
|
|
46
46
|
if (req.url === '/api/tree' && req.method === 'GET') {
|
|
47
47
|
res.setHeader('Content-Type', 'application/json');
|
|
48
|
-
const ignoreDirs = ['node_modules', '.git', '.docs', '.claude', '.cursor', '.codex', 'dist', 'build'];
|
|
49
48
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
49
|
+
const getDirectoryTree = (dirPath, relativePath = '') => {
|
|
50
|
+
const items = fs.readdirSync(dirPath);
|
|
51
|
+
let result = [];
|
|
52
|
+
|
|
53
|
+
for (const item of items) {
|
|
54
|
+
if (['node_modules', '.git', '.docs', '.agents', '.spark'].includes(item)) continue;
|
|
55
|
+
|
|
56
|
+
const fullPath = path.join(dirPath, item);
|
|
57
|
+
const itemRelativePath = path.join(relativePath, item);
|
|
58
|
+
const stat = fs.statSync(fullPath);
|
|
59
|
+
|
|
60
|
+
let isDanger = false;
|
|
61
|
+
let dangerReason = '';
|
|
62
|
+
|
|
63
|
+
const lowerItem = item.toLowerCase();
|
|
64
|
+
if (lowerItem.includes('controller')) {
|
|
65
|
+
isDanger = true;
|
|
66
|
+
dangerReason = 'Anti-Pattern: "God Object" tendency. Move business logic to specific Domain Services instead of Controllers.';
|
|
67
|
+
} else if (lowerItem.includes('utils')) {
|
|
68
|
+
isDanger = true;
|
|
69
|
+
dangerReason = 'Legacy Trap: "Utils" folders become untracked dumping grounds for dead code. Use specific domain modules.';
|
|
70
|
+
} else if (lowerItem.includes('helper')) {
|
|
71
|
+
isDanger = true;
|
|
72
|
+
dangerReason = 'Legacy Trap: "Helpers" lack clear boundaries and violate single responsibility principles.';
|
|
73
|
+
} else if (lowerItem.includes('api') && stat.isDirectory()) {
|
|
74
|
+
isDanger = true;
|
|
75
|
+
dangerReason = 'Anti-Pattern: Generic "API" folders often lead to poor separation of concerns. Group by feature/domain instead.';
|
|
64
76
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
77
|
+
|
|
78
|
+
if (stat.isDirectory()) {
|
|
79
|
+
const children = getDirectoryTree(fullPath, itemRelativePath);
|
|
80
|
+
result.push({
|
|
81
|
+
name: item,
|
|
82
|
+
path: itemRelativePath,
|
|
83
|
+
type: 'dir',
|
|
84
|
+
isDanger,
|
|
85
|
+
dangerReason,
|
|
86
|
+
children
|
|
87
|
+
});
|
|
88
|
+
} else {
|
|
89
|
+
result.push({
|
|
90
|
+
name: item,
|
|
91
|
+
path: itemRelativePath,
|
|
92
|
+
type: 'file',
|
|
93
|
+
isDanger,
|
|
94
|
+
dangerReason
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return result;
|
|
99
|
+
};
|
|
68
100
|
|
|
69
|
-
return res.end(JSON.stringify({ tree:
|
|
101
|
+
return res.end(JSON.stringify({ tree: getDirectoryTree(process.cwd()) }));
|
|
70
102
|
}
|
|
71
103
|
|
|
72
104
|
// API Route: Get SPARK Readme (for Guide)
|
|
@@ -97,7 +129,7 @@ const server = http.createServer((req, res) => {
|
|
|
97
129
|
}
|
|
98
130
|
|
|
99
131
|
// 2. Custom skills
|
|
100
|
-
const customDir = path.join(process.cwd(), '.
|
|
132
|
+
const customDir = path.join(process.cwd(), '.agents', 'skills');
|
|
101
133
|
if (fs.existsSync(customDir)) {
|
|
102
134
|
try {
|
|
103
135
|
const items = fs.readdirSync(customDir);
|
|
@@ -119,8 +151,8 @@ const server = http.createServer((req, res) => {
|
|
|
119
151
|
}
|
|
120
152
|
|
|
121
153
|
// Save to a safe, user-level directory that SPARK updates won't touch
|
|
122
|
-
// The Anthropic standard allows putting skills in .
|
|
123
|
-
const customSkillsDir = path.join(process.cwd(), '.
|
|
154
|
+
// The Anthropic/Gemini standard allows putting skills in .agents/skills/
|
|
155
|
+
const customSkillsDir = path.join(process.cwd(), '.agents', 'skills', data.name);
|
|
124
156
|
|
|
125
157
|
try {
|
|
126
158
|
fs.mkdirSync(customSkillsDir, { recursive: true });
|