@thelogicatelier/sylva 1.0.11 → 1.0.13

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.
@@ -83,21 +83,24 @@ async function braveSearch(query, options) {
83
83
  });
84
84
  if (!response.ok) {
85
85
  const statusText = response.statusText || "Unknown error";
86
+ const errorMsg = `HTTP ${response.status} (${statusText})`;
86
87
  if (response.status === 401 || response.status === 403) {
87
88
  console.warn(`❌ Brave Search API authentication failed (HTTP ${response.status}: ${statusText}).\n` +
88
89
  ` Your BRAVE_API_KEY may be invalid or expired.\n` +
89
90
  ` Get a valid key at: https://brave.com/search/api/\n` +
90
91
  ` Framework detection still works — only web doc references are affected.`);
92
+ throw new Error(`Authentication failed ${errorMsg}. Valid API key required.`);
91
93
  }
92
94
  else if (response.status === 429) {
93
95
  console.warn(`⚠️ Brave Search API rate limit exceeded (HTTP 429).\n` +
94
96
  ` Query: "${query}"\n` +
95
97
  ` Wait a moment and retry, or check your Brave API plan limits.`);
98
+ throw new Error(`Rate limit exceeded ${errorMsg}.`);
96
99
  }
97
100
  else {
98
101
  console.warn(`⚠️ Brave Search API returned HTTP ${response.status} (${statusText}) for query: "${query}"`);
102
+ throw new Error(`Brave Search API error: ${errorMsg}`);
99
103
  }
100
- return [];
101
104
  }
102
105
  const data = (await response.json());
103
106
  const webResults = data.web?.results || [];
@@ -120,10 +123,10 @@ async function braveSearch(query, options) {
120
123
  console.warn(`⚠️ Brave Search network error for query "${query}": ${errMsg}\n` +
121
124
  ` Check your internet connection. Framework detection is unaffected.`);
122
125
  }
123
- else {
126
+ else if (!errMsg.includes("HTTP")) {
124
127
  console.warn(`⚠️ Brave Search failed for query "${query}": ${errMsg}`);
125
128
  }
126
- return [];
129
+ throw error;
127
130
  }
128
131
  }
129
132
  /**
@@ -3,6 +3,7 @@
3
3
  * Deterministic — no LLM involvement.
4
4
  */
5
5
  import { Signal, StackInfo, ArchitectureModel, VersionInfo } from "./types";
6
+ /** Confidence scoring rules */
6
7
  /**
7
8
  * Detect stacks from signals.
8
9
  * Groups signals by frameworkId and scores confidence.
@@ -8,37 +8,8 @@ exports.detectStacks = detectStacks;
8
8
  exports.detectArchitecture = detectArchitecture;
9
9
  exports.formatVersionForDisplay = formatVersionForDisplay;
10
10
  const versionResolver_1 = require("./versionResolver");
11
+ const constants_1 = require("../constants");
11
12
  /** Confidence scoring rules */
12
- const CONFIDENCE_SCORES = {
13
- orchestrator: 95,
14
- angular: 85,
15
- react: 80,
16
- nextjs: 85,
17
- vue: 80,
18
- nuxt: 85,
19
- svelte: 80,
20
- sveltekit: 85,
21
- express: 75,
22
- nestjs: 85,
23
- fastify: 75,
24
- django: 85,
25
- flask: 75,
26
- fastapi: 80,
27
- "spring-boot": 90,
28
- spring: 80,
29
- "java-maven": 70,
30
- "java-gradle": 70,
31
- dotnet: 80,
32
- "aspnet-core": 85,
33
- go: 80,
34
- rust: 80,
35
- "actix-web": 85,
36
- axum: 85,
37
- nodejs: 60,
38
- python: 60,
39
- typescript: 65,
40
- docker: 50,
41
- };
42
13
  /**
43
14
  * Detect stacks from signals.
44
15
  * Groups signals by frameworkId and scores confidence.
@@ -46,8 +17,17 @@ const CONFIDENCE_SCORES = {
46
17
  function detectStacks(signals) {
47
18
  const grouped = new Map();
48
19
  for (const signal of signals) {
49
- // Skip tooling/entrypoint signals for stack detection
50
- if (signal.kind === "tooling" || signal.kind === "entrypoint")
20
+ // Skip non-framework signals from stack detection
21
+ if (signal.kind === "tooling" ||
22
+ signal.kind === "entrypoint" ||
23
+ signal.kind === "agent" ||
24
+ signal.kind === "subagent" ||
25
+ signal.kind === "heartbeat" ||
26
+ signal.kind === "cron" ||
27
+ signal.kind === "hook" ||
28
+ signal.kind === "skill" ||
29
+ signal.kind === "plugin" ||
30
+ signal.kind === "integration")
51
31
  continue;
52
32
  if (!grouped.has(signal.frameworkId)) {
53
33
  grouped.set(signal.frameworkId, []);
@@ -57,7 +37,7 @@ function detectStacks(signals) {
57
37
  const stacks = [];
58
38
  for (const [frameworkId, frameworkSignals] of grouped) {
59
39
  const frameworkName = frameworkSignals[0].frameworkName;
60
- const confidence = CONFIDENCE_SCORES[frameworkId] || 50;
40
+ const confidence = constants_1.CONFIDENCE_SCORES[frameworkId] || 50;
61
41
  const version = (0, versionResolver_1.resolveVersion)(frameworkSignals);
62
42
  // Determine rootPath — use the shallowest signal's scope
63
43
  const rootPaths = frameworkSignals.map((s) => s.scope.pathRoot).filter(Boolean);
@@ -46,11 +46,12 @@ const manifestParsers_1 = require("./manifestParsers");
46
46
  const versionResolver_1 = require("./versionResolver");
47
47
  const detector_1 = require("./detector");
48
48
  const webGrounding_1 = require("./webGrounding");
49
+ const sourceScanner_1 = require("./sourceScanner");
49
50
  /**
50
51
  * The ARCHITECTURE CONSTRAINTS block injected into LLM context.
51
52
  * This is authoritative and must not be overridden by the model.
52
53
  */
53
- function buildConstraintsBlock(stacks, resolvedVersions, hasOrchestrator) {
54
+ function buildConstraintsBlock(stacks, resolvedVersions, hasOrchestrator, signals) {
54
55
  const lines = [
55
56
  "=== ARCHITECTURE CONSTRAINTS (AUTHORITATIVE) ===",
56
57
  "1) The detected frameworks/stacks listed below are authoritative because they were derived from repository manifest/config files.",
@@ -78,9 +79,80 @@ function buildConstraintsBlock(stacks, resolvedVersions, hasOrchestrator) {
78
79
  lines.push(` Evidence: ${ev.evidence.reason} [${ev.evidence.file}]`);
79
80
  }
80
81
  }
82
+ // OpenClaw-specific constraint sections (only when detected)
83
+ if (hasOrchestrator) {
84
+ appendOpenClawConstraints(lines, signals);
85
+ }
86
+ // External Integrations (source code scanner)
87
+ const integrations = signals.filter((s) => s.kind === "integration");
88
+ if (integrations.length > 0) {
89
+ lines.push("", "EXTERNAL INTEGRATIONS (Discovered in Source Code):");
90
+ for (const sig of integrations) {
91
+ lines.push(` - ${sig.frameworkName}`);
92
+ lines.push(` Evidence: ${sig.evidence.reason} [${sig.evidence.file}]`);
93
+ }
94
+ }
81
95
  lines.push("=== END ARCHITECTURE CONSTRAINTS ===");
82
96
  return lines.join("\n");
83
97
  }
98
+ /**
99
+ * Append OpenClaw-specific sections to the constraints block.
100
+ * Groups signals by kind (agent, hook, skill, subagent, plugin, heartbeat).
101
+ */
102
+ function appendOpenClawConstraints(lines, signals) {
103
+ // Agent config
104
+ const agentSignals = signals.filter((s) => s.kind === "agent");
105
+ if (agentSignals.length > 0) {
106
+ lines.push("", "OPENCLAW AGENT CONFIG:");
107
+ for (const sig of agentSignals) {
108
+ lines.push(` - ${sig.frameworkName}: ${sig.evidence.excerpt}`);
109
+ }
110
+ }
111
+ // Hooks
112
+ const hookSignals = signals.filter((s) => s.kind === "hook");
113
+ if (hookSignals.length > 0) {
114
+ lines.push("", "OPENCLAW HOOKS:");
115
+ for (const sig of hookSignals) {
116
+ const name = sig.frameworkName.replace("OpenClaw Hook: ", "");
117
+ lines.push(` - /${name}: ${sig.evidence.reason}`);
118
+ }
119
+ }
120
+ // Skills
121
+ const skillSignals = signals.filter((s) => s.kind === "skill");
122
+ if (skillSignals.length > 0) {
123
+ lines.push("", "OPENCLAW SKILLS:");
124
+ for (const sig of skillSignals) {
125
+ lines.push(` - ${sig.frameworkName.replace("OpenClaw Skill: ", "")}: ${sig.evidence.reason}`);
126
+ }
127
+ }
128
+ // Subagents
129
+ const subagentSignals = signals.filter((s) => s.kind === "subagent");
130
+ if (subagentSignals.length > 0) {
131
+ lines.push("", "OPENCLAW SUBAGENTS:");
132
+ for (const sig of subagentSignals) {
133
+ lines.push(` - ${sig.frameworkName.replace("OpenClaw Subagent: ", "")}: ${sig.evidence.reason}`);
134
+ if (sig.evidence.excerpt) {
135
+ lines.push(` ${sig.evidence.excerpt}`);
136
+ }
137
+ }
138
+ }
139
+ // Plugins
140
+ const pluginSignals = signals.filter((s) => s.kind === "plugin");
141
+ if (pluginSignals.length > 0) {
142
+ lines.push("", "OPENCLAW PLUGINS:");
143
+ for (const sig of pluginSignals) {
144
+ lines.push(` - ${sig.frameworkName.replace("OpenClaw Plugin: ", "")}: ${sig.evidence.excerpt}`);
145
+ }
146
+ }
147
+ // Heartbeat
148
+ const heartbeatSignals = signals.filter((s) => s.kind === "heartbeat");
149
+ if (heartbeatSignals.length > 0) {
150
+ lines.push("", "OPENCLAW HEARTBEAT:");
151
+ for (const sig of heartbeatSignals) {
152
+ lines.push(` - ${sig.evidence.excerpt}`);
153
+ }
154
+ }
155
+ }
84
156
  /**
85
157
  * Build the full awareness context string for LLM prompts.
86
158
  */
@@ -170,6 +242,65 @@ function saveAwarenessJson(repoName, result, baseDir = "projects") {
170
242
  ` Check disk space and directory write permissions if you need this output.`);
171
243
  }
172
244
  }
245
+ /**
246
+ * Save grounding.json for web grounding transparency.
247
+ * Always saved — contains references + structured errors.
248
+ */
249
+ function saveGroundingJson(repoName, webReferences, errors, baseDir = "projects") {
250
+ const folderName = repoName.toLowerCase().replace(/\s+/g, "-");
251
+ const targetDir = path.join(baseDir, folderName);
252
+ fs.mkdirSync(targetDir, { recursive: true });
253
+ const filePath = path.join(targetDir, "grounding.json");
254
+ try {
255
+ // Build structured error entries
256
+ const structuredErrors = errors.map((err) => {
257
+ // Parse known error patterns
258
+ if (err.includes("BRAVE_API_KEY not set")) {
259
+ return {
260
+ reason: "BRAVE_API_KEY not set",
261
+ impact: "Web grounding disabled — no documentation references gathered",
262
+ resolution: "Set BRAVE_API_KEY in your .env file. Get a free key at https://brave.com/search/api/",
263
+ };
264
+ }
265
+ if (err.includes("rate limit") || err.includes("HTTP 429")) {
266
+ const queryMatch = err.match(/Query: "([^"]+)"/);
267
+ return {
268
+ query: queryMatch ? queryMatch[1] : undefined,
269
+ reason: "Brave Search API rate limit exceeded (HTTP 429)",
270
+ impact: "Results missing for this query",
271
+ resolution: "Wait a moment and retry, or check your Brave API plan limits",
272
+ };
273
+ }
274
+ if (err.includes("Web search failed")) {
275
+ const queryMatch = err.match(/for "([^"]+)"/);
276
+ const reasonMatch = err.match(/: (.+)$/);
277
+ return {
278
+ query: queryMatch ? queryMatch[1] : undefined,
279
+ reason: reasonMatch ? reasonMatch[1] : err,
280
+ impact: "Results missing for this query",
281
+ };
282
+ }
283
+ return {
284
+ reason: err,
285
+ impact: "Unknown web grounding error",
286
+ };
287
+ });
288
+ const grounding = {
289
+ generatedAt: new Date().toISOString(),
290
+ totalReferences: webReferences.reduce((sum, ref) => sum + ref.results.length, 0),
291
+ frameworksCovered: webReferences.length,
292
+ references: webReferences,
293
+ errors: structuredErrors,
294
+ };
295
+ fs.writeFileSync(filePath, JSON.stringify(grounding, null, 2), "utf-8");
296
+ console.log(`✅ Saved grounding.json to: ${filePath}`);
297
+ }
298
+ catch (error) {
299
+ console.warn(`⚠️ Could not save grounding.json to ${filePath}: ${error.message}\n` +
300
+ ` This is a debug file and does not affect AGENTS.md generation.\n` +
301
+ ` Check disk space and directory write permissions if you need this output.`);
302
+ }
303
+ }
173
304
  /**
174
305
  * Run the full awareness pipeline.
175
306
  * This is the main entry point called from the CLI.
@@ -190,8 +321,16 @@ async function runAwareness(repoPath, repoName) {
190
321
  }
191
322
  // Step 2: Parse all manifests into signals
192
323
  console.log(" → Parsing manifests...");
193
- const signals = (0, manifestParsers_1.parseAllManifests)(manifests);
194
- console.log(` → Generated ${signals.length} signal(s)`);
324
+ const manifestSignals = (0, manifestParsers_1.parseAllManifests)(manifests);
325
+ console.log(` → Generated ${manifestSignals.length} signal(s) from manifests`);
326
+ // Step 2.5: Scan source code for external integrations
327
+ console.log(" → Scanning source code for external integrations...");
328
+ const integrationSignals = (0, sourceScanner_1.scanSourceFiles)(repoPath);
329
+ if (integrationSignals.length > 0) {
330
+ console.log(` → Discovered ${integrationSignals.length} integration(s) in source code`);
331
+ }
332
+ // Merge all signals
333
+ const signals = [...manifestSignals, ...integrationSignals];
195
334
  // Step 3: Resolve versions
196
335
  console.log(" → Resolving versions...");
197
336
  const resolvedVersions = (0, versionResolver_1.resolveAllVersions)(signals);
@@ -210,9 +349,28 @@ async function runAwareness(repoPath, repoName) {
210
349
  }
211
350
  if (hasOrchestrator) {
212
351
  console.log(" 🎯 OpenClaw orchestrator detected");
352
+ // Log OpenClaw-specific discoveries
353
+ const hookCount = signals.filter((s) => s.kind === "hook").length;
354
+ const skillCount = signals.filter((s) => s.kind === "skill").length;
355
+ const subagentCount = signals.filter((s) => s.kind === "subagent").length;
356
+ const pluginCount = signals.filter((s) => s.kind === "plugin").length;
357
+ if (hookCount > 0)
358
+ console.log(` 🪝 ${hookCount} hook(s) detected`);
359
+ if (skillCount > 0)
360
+ console.log(` 🎯 ${skillCount} skill(s) detected`);
361
+ if (subagentCount > 0)
362
+ console.log(` 🤖 ${subagentCount} subagent(s) detected`);
363
+ if (pluginCount > 0)
364
+ console.log(` 🔌 ${pluginCount} plugin(s) detected`);
365
+ const heartbeat = signals.find((s) => s.kind === "heartbeat");
366
+ if (heartbeat) {
367
+ const active = heartbeat.evidence.excerpt?.includes("ACTIVE") &&
368
+ !heartbeat.evidence.excerpt?.includes("INACTIVE");
369
+ console.log(` 💓 Heartbeat: ${active ? "ACTIVE" : "INACTIVE"}`);
370
+ }
213
371
  }
214
- // Step 5: Build constraints block
215
- const constraintsBlock = buildConstraintsBlock(stacks, resolvedVersions, hasOrchestrator);
372
+ // Step 5: Build constraints block (now includes OpenClaw-specific sections)
373
+ const constraintsBlock = buildConstraintsBlock(stacks, resolvedVersions, hasOrchestrator, signals);
216
374
  // Step 6: Web grounding
217
375
  console.log(" → Gathering web references...");
218
376
  const cacheDir = path.join("projects", repoName.toLowerCase().replace(/\s+/g, "-"), "cache", "brave");
@@ -254,6 +412,8 @@ async function runAwareness(repoPath, repoName) {
254
412
  };
255
413
  // Step 8: Save awareness.json
256
414
  saveAwarenessJson(repoName, result);
415
+ // Step 9: Save grounding.json (always, even on errors)
416
+ saveGroundingJson(repoName, webReferences, errors);
257
417
  console.log("✅ Framework Awareness scan complete\n");
258
418
  return result;
259
419
  }
@@ -40,16 +40,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
40
40
  exports.parseGoMod = parseGoMod;
41
41
  const fs = __importStar(require("fs"));
42
42
  const path = __importStar(require("path"));
43
- /** Known Go frameworks/libraries */
44
- const GO_FRAMEWORKS = [
45
- { module: "github.com/gin-gonic/gin", frameworkId: "gin", frameworkName: "Gin" },
46
- { module: "github.com/labstack/echo", frameworkId: "echo", frameworkName: "Echo" },
47
- { module: "github.com/gofiber/fiber", frameworkId: "fiber", frameworkName: "Fiber" },
48
- { module: "github.com/gorilla/mux", frameworkId: "gorilla-mux", frameworkName: "Gorilla Mux" },
49
- { module: "gorm.io/gorm", frameworkId: "gorm", frameworkName: "GORM" },
50
- { module: "github.com/stretchr/testify", frameworkId: "testify", frameworkName: "Testify" },
51
- { module: "google.golang.org/grpc", frameworkId: "grpc-go", frameworkName: "gRPC-Go" },
52
- ];
43
+ const constants_1 = require("../../constants");
53
44
  /**
54
45
  * Parse go.mod
55
46
  */
@@ -117,7 +108,7 @@ function parseGoMod(manifest) {
117
108
  continue;
118
109
  const modulePath = match[1];
119
110
  const version = match[2];
120
- const framework = GO_FRAMEWORKS.find((f) => modulePath.startsWith(f.module));
111
+ const framework = constants_1.GO_FRAMEWORKS.find((f) => modulePath.startsWith(f.module));
121
112
  if (framework) {
122
113
  signals.push({
123
114
  kind: "framework",
@@ -41,25 +41,7 @@ exports.parsePomXml = parsePomXml;
41
41
  exports.parseBuildGradle = parseBuildGradle;
42
42
  const fs = __importStar(require("fs"));
43
43
  const path = __importStar(require("path"));
44
- /** Known Java frameworks */
45
- const JAVA_FRAMEWORKS = [
46
- {
47
- groupArtifact: "org.springframework.boot",
48
- frameworkId: "spring-boot",
49
- frameworkName: "Spring Boot",
50
- },
51
- {
52
- groupArtifact: "org.springframework",
53
- frameworkId: "spring",
54
- frameworkName: "Spring Framework",
55
- },
56
- { groupArtifact: "io.quarkus", frameworkId: "quarkus", frameworkName: "Quarkus" },
57
- { groupArtifact: "io.micronaut", frameworkId: "micronaut", frameworkName: "Micronaut" },
58
- { groupArtifact: "org.hibernate", frameworkId: "hibernate", frameworkName: "Hibernate" },
59
- { groupArtifact: "junit", frameworkId: "junit", frameworkName: "JUnit" },
60
- { groupArtifact: "org.junit", frameworkId: "junit5", frameworkName: "JUnit 5" },
61
- { groupArtifact: "org.apache.kafka", frameworkId: "kafka", frameworkName: "Apache Kafka" },
62
- ];
44
+ const constants_1 = require("../../constants");
63
45
  /**
64
46
  * Parse pom.xml (Maven)
65
47
  */
@@ -108,7 +90,7 @@ function parsePomXml(manifest) {
108
90
  if (parentMatch) {
109
91
  const parentGroup = parentMatch[1];
110
92
  const parentVersion = parentMatch[2];
111
- const framework = JAVA_FRAMEWORKS.find((f) => parentGroup.includes(f.groupArtifact));
93
+ const framework = constants_1.JAVA_FRAMEWORKS.find((f) => parentGroup.includes(f.groupArtifact));
112
94
  if (framework) {
113
95
  const resolvedVersion = parentVersion.startsWith("${")
114
96
  ? properties[parentVersion.slice(2, -1)]
@@ -141,7 +123,7 @@ function parsePomXml(manifest) {
141
123
  const groupId = depMatch[1];
142
124
  const artifactId = depMatch[2];
143
125
  const version = depMatch[3];
144
- const framework = JAVA_FRAMEWORKS.find((f) => groupId.includes(f.groupArtifact));
126
+ const framework = constants_1.JAVA_FRAMEWORKS.find((f) => groupId.includes(f.groupArtifact));
145
127
  if (framework) {
146
128
  let versionInfo;
147
129
  if (version) {
@@ -223,7 +205,7 @@ function parseBuildGradle(manifest) {
223
205
  while ((m = depRegex.exec(content)) !== null) {
224
206
  const groupId = m[1];
225
207
  const version = m[3];
226
- const framework = JAVA_FRAMEWORKS.find((f) => groupId.includes(f.groupArtifact));
208
+ const framework = constants_1.JAVA_FRAMEWORKS.find((f) => groupId.includes(f.groupArtifact));
227
209
  if (framework) {
228
210
  signals.push({
229
211
  kind: "framework",
@@ -1,6 +1,20 @@
1
1
  /**
2
- * OpenClaw manifest parser.
3
- * Parses openclaw.json to extract orchestrator configuration.
2
+ * OpenClaw manifest parser — deep extraction.
3
+ * Parses openclaw.json / .openclaw.json to extract:
4
+ * - Orchestrator signal with version from meta
5
+ * - Agent config (models, workspace, concurrency)
6
+ * - Hooks (internal event scripts with paths + descriptions)
7
+ * - Plugins (enabled extensions)
8
+ * - Gateway config (port, auth, denied commands)
9
+ * - Channels (policies, stream modes)
10
+ * - Tools (web search, fetch)
11
+ * - Commands / messages config
12
+ *
13
+ * Also scans the OpenClaw workspace directory for:
14
+ * - Skills (reusable .md workflows)
15
+ * - Subagents (background agent directories)
16
+ * - Workspace .md files (AGENTS.md, IDENTITY.md, HEARTBEAT.md, etc.)
17
+ * - Hook scripts
4
18
  */
5
19
  import { Signal, ManifestFile } from "../types";
6
20
  export declare function parseOpenclawJson(manifest: ManifestFile): Signal[];