@thelogicatelier/sylva 1.0.11 → 1.0.12
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.
|
@@ -46,8 +46,16 @@ const CONFIDENCE_SCORES = {
|
|
|
46
46
|
function detectStacks(signals) {
|
|
47
47
|
const grouped = new Map();
|
|
48
48
|
for (const signal of signals) {
|
|
49
|
-
// Skip
|
|
50
|
-
if (signal.kind === "tooling" ||
|
|
49
|
+
// Skip non-framework signals from stack detection
|
|
50
|
+
if (signal.kind === "tooling" ||
|
|
51
|
+
signal.kind === "entrypoint" ||
|
|
52
|
+
signal.kind === "agent" ||
|
|
53
|
+
signal.kind === "subagent" ||
|
|
54
|
+
signal.kind === "heartbeat" ||
|
|
55
|
+
signal.kind === "cron" ||
|
|
56
|
+
signal.kind === "hook" ||
|
|
57
|
+
signal.kind === "skill" ||
|
|
58
|
+
signal.kind === "plugin")
|
|
51
59
|
continue;
|
|
52
60
|
if (!grouped.has(signal.frameworkId)) {
|
|
53
61
|
grouped.set(signal.frameworkId, []);
|
package/dist/awareness/index.js
CHANGED
|
@@ -50,7 +50,7 @@ const webGrounding_1 = require("./webGrounding");
|
|
|
50
50
|
* The ARCHITECTURE CONSTRAINTS block injected into LLM context.
|
|
51
51
|
* This is authoritative and must not be overridden by the model.
|
|
52
52
|
*/
|
|
53
|
-
function buildConstraintsBlock(stacks, resolvedVersions, hasOrchestrator) {
|
|
53
|
+
function buildConstraintsBlock(stacks, resolvedVersions, hasOrchestrator, signals) {
|
|
54
54
|
const lines = [
|
|
55
55
|
"=== ARCHITECTURE CONSTRAINTS (AUTHORITATIVE) ===",
|
|
56
56
|
"1) The detected frameworks/stacks listed below are authoritative because they were derived from repository manifest/config files.",
|
|
@@ -78,9 +78,71 @@ function buildConstraintsBlock(stacks, resolvedVersions, hasOrchestrator) {
|
|
|
78
78
|
lines.push(` Evidence: ${ev.evidence.reason} [${ev.evidence.file}]`);
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
|
+
// OpenClaw-specific constraint sections (only when detected)
|
|
82
|
+
if (hasOrchestrator) {
|
|
83
|
+
appendOpenClawConstraints(lines, signals);
|
|
84
|
+
}
|
|
81
85
|
lines.push("=== END ARCHITECTURE CONSTRAINTS ===");
|
|
82
86
|
return lines.join("\n");
|
|
83
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Append OpenClaw-specific sections to the constraints block.
|
|
90
|
+
* Groups signals by kind (agent, hook, skill, subagent, plugin, heartbeat).
|
|
91
|
+
*/
|
|
92
|
+
function appendOpenClawConstraints(lines, signals) {
|
|
93
|
+
// Agent config
|
|
94
|
+
const agentSignals = signals.filter((s) => s.kind === "agent");
|
|
95
|
+
if (agentSignals.length > 0) {
|
|
96
|
+
lines.push("", "OPENCLAW AGENT CONFIG:");
|
|
97
|
+
for (const sig of agentSignals) {
|
|
98
|
+
lines.push(` - ${sig.frameworkName}: ${sig.evidence.excerpt}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Hooks
|
|
102
|
+
const hookSignals = signals.filter((s) => s.kind === "hook");
|
|
103
|
+
if (hookSignals.length > 0) {
|
|
104
|
+
lines.push("", "OPENCLAW HOOKS:");
|
|
105
|
+
for (const sig of hookSignals) {
|
|
106
|
+
const name = sig.frameworkName.replace("OpenClaw Hook: ", "");
|
|
107
|
+
lines.push(` - /${name}: ${sig.evidence.reason}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Skills
|
|
111
|
+
const skillSignals = signals.filter((s) => s.kind === "skill");
|
|
112
|
+
if (skillSignals.length > 0) {
|
|
113
|
+
lines.push("", "OPENCLAW SKILLS:");
|
|
114
|
+
for (const sig of skillSignals) {
|
|
115
|
+
lines.push(` - ${sig.frameworkName.replace("OpenClaw Skill: ", "")}: ${sig.evidence.reason}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Subagents
|
|
119
|
+
const subagentSignals = signals.filter((s) => s.kind === "subagent");
|
|
120
|
+
if (subagentSignals.length > 0) {
|
|
121
|
+
lines.push("", "OPENCLAW SUBAGENTS:");
|
|
122
|
+
for (const sig of subagentSignals) {
|
|
123
|
+
lines.push(` - ${sig.frameworkName.replace("OpenClaw Subagent: ", "")}: ${sig.evidence.reason}`);
|
|
124
|
+
if (sig.evidence.excerpt) {
|
|
125
|
+
lines.push(` ${sig.evidence.excerpt}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Plugins
|
|
130
|
+
const pluginSignals = signals.filter((s) => s.kind === "plugin");
|
|
131
|
+
if (pluginSignals.length > 0) {
|
|
132
|
+
lines.push("", "OPENCLAW PLUGINS:");
|
|
133
|
+
for (const sig of pluginSignals) {
|
|
134
|
+
lines.push(` - ${sig.frameworkName.replace("OpenClaw Plugin: ", "")}: ${sig.evidence.excerpt}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// Heartbeat
|
|
138
|
+
const heartbeatSignals = signals.filter((s) => s.kind === "heartbeat");
|
|
139
|
+
if (heartbeatSignals.length > 0) {
|
|
140
|
+
lines.push("", "OPENCLAW HEARTBEAT:");
|
|
141
|
+
for (const sig of heartbeatSignals) {
|
|
142
|
+
lines.push(` - ${sig.evidence.excerpt}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
84
146
|
/**
|
|
85
147
|
* Build the full awareness context string for LLM prompts.
|
|
86
148
|
*/
|
|
@@ -170,6 +232,65 @@ function saveAwarenessJson(repoName, result, baseDir = "projects") {
|
|
|
170
232
|
` Check disk space and directory write permissions if you need this output.`);
|
|
171
233
|
}
|
|
172
234
|
}
|
|
235
|
+
/**
|
|
236
|
+
* Save grounding.json for web grounding transparency.
|
|
237
|
+
* Always saved — contains references + structured errors.
|
|
238
|
+
*/
|
|
239
|
+
function saveGroundingJson(repoName, webReferences, errors, baseDir = "projects") {
|
|
240
|
+
const folderName = repoName.toLowerCase().replace(/\s+/g, "-");
|
|
241
|
+
const targetDir = path.join(baseDir, folderName);
|
|
242
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
243
|
+
const filePath = path.join(targetDir, "grounding.json");
|
|
244
|
+
try {
|
|
245
|
+
// Build structured error entries
|
|
246
|
+
const structuredErrors = errors.map((err) => {
|
|
247
|
+
// Parse known error patterns
|
|
248
|
+
if (err.includes("BRAVE_API_KEY not set")) {
|
|
249
|
+
return {
|
|
250
|
+
reason: "BRAVE_API_KEY not set",
|
|
251
|
+
impact: "Web grounding disabled — no documentation references gathered",
|
|
252
|
+
resolution: "Set BRAVE_API_KEY in your .env file. Get a free key at https://brave.com/search/api/",
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
if (err.includes("rate limit") || err.includes("HTTP 429")) {
|
|
256
|
+
const queryMatch = err.match(/Query: "([^"]+)"/);
|
|
257
|
+
return {
|
|
258
|
+
query: queryMatch ? queryMatch[1] : undefined,
|
|
259
|
+
reason: "Brave Search API rate limit exceeded (HTTP 429)",
|
|
260
|
+
impact: "Results missing for this query",
|
|
261
|
+
resolution: "Wait a moment and retry, or check your Brave API plan limits",
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
if (err.includes("Web search failed")) {
|
|
265
|
+
const queryMatch = err.match(/for "([^"]+)"/);
|
|
266
|
+
const reasonMatch = err.match(/: (.+)$/);
|
|
267
|
+
return {
|
|
268
|
+
query: queryMatch ? queryMatch[1] : undefined,
|
|
269
|
+
reason: reasonMatch ? reasonMatch[1] : err,
|
|
270
|
+
impact: "Results missing for this query",
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
reason: err,
|
|
275
|
+
impact: "Unknown web grounding error",
|
|
276
|
+
};
|
|
277
|
+
});
|
|
278
|
+
const grounding = {
|
|
279
|
+
generatedAt: new Date().toISOString(),
|
|
280
|
+
totalReferences: webReferences.reduce((sum, ref) => sum + ref.results.length, 0),
|
|
281
|
+
frameworksCovered: webReferences.length,
|
|
282
|
+
references: webReferences,
|
|
283
|
+
errors: structuredErrors,
|
|
284
|
+
};
|
|
285
|
+
fs.writeFileSync(filePath, JSON.stringify(grounding, null, 2), "utf-8");
|
|
286
|
+
console.log(`✅ Saved grounding.json to: ${filePath}`);
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
console.warn(`⚠️ Could not save grounding.json to ${filePath}: ${error.message}\n` +
|
|
290
|
+
` This is a debug file and does not affect AGENTS.md generation.\n` +
|
|
291
|
+
` Check disk space and directory write permissions if you need this output.`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
173
294
|
/**
|
|
174
295
|
* Run the full awareness pipeline.
|
|
175
296
|
* This is the main entry point called from the CLI.
|
|
@@ -210,9 +331,28 @@ async function runAwareness(repoPath, repoName) {
|
|
|
210
331
|
}
|
|
211
332
|
if (hasOrchestrator) {
|
|
212
333
|
console.log(" 🎯 OpenClaw orchestrator detected");
|
|
334
|
+
// Log OpenClaw-specific discoveries
|
|
335
|
+
const hookCount = signals.filter((s) => s.kind === "hook").length;
|
|
336
|
+
const skillCount = signals.filter((s) => s.kind === "skill").length;
|
|
337
|
+
const subagentCount = signals.filter((s) => s.kind === "subagent").length;
|
|
338
|
+
const pluginCount = signals.filter((s) => s.kind === "plugin").length;
|
|
339
|
+
if (hookCount > 0)
|
|
340
|
+
console.log(` 🪝 ${hookCount} hook(s) detected`);
|
|
341
|
+
if (skillCount > 0)
|
|
342
|
+
console.log(` 🎯 ${skillCount} skill(s) detected`);
|
|
343
|
+
if (subagentCount > 0)
|
|
344
|
+
console.log(` 🤖 ${subagentCount} subagent(s) detected`);
|
|
345
|
+
if (pluginCount > 0)
|
|
346
|
+
console.log(` 🔌 ${pluginCount} plugin(s) detected`);
|
|
347
|
+
const heartbeat = signals.find((s) => s.kind === "heartbeat");
|
|
348
|
+
if (heartbeat) {
|
|
349
|
+
const active = heartbeat.evidence.excerpt?.includes("ACTIVE") &&
|
|
350
|
+
!heartbeat.evidence.excerpt?.includes("INACTIVE");
|
|
351
|
+
console.log(` 💓 Heartbeat: ${active ? "ACTIVE" : "INACTIVE"}`);
|
|
352
|
+
}
|
|
213
353
|
}
|
|
214
|
-
// Step 5: Build constraints block
|
|
215
|
-
const constraintsBlock = buildConstraintsBlock(stacks, resolvedVersions, hasOrchestrator);
|
|
354
|
+
// Step 5: Build constraints block (now includes OpenClaw-specific sections)
|
|
355
|
+
const constraintsBlock = buildConstraintsBlock(stacks, resolvedVersions, hasOrchestrator, signals);
|
|
216
356
|
// Step 6: Web grounding
|
|
217
357
|
console.log(" → Gathering web references...");
|
|
218
358
|
const cacheDir = path.join("projects", repoName.toLowerCase().replace(/\s+/g, "-"), "cache", "brave");
|
|
@@ -254,6 +394,8 @@ async function runAwareness(repoPath, repoName) {
|
|
|
254
394
|
};
|
|
255
395
|
// Step 8: Save awareness.json
|
|
256
396
|
saveAwarenessJson(repoName, result);
|
|
397
|
+
// Step 9: Save grounding.json (always, even on errors)
|
|
398
|
+
saveGroundingJson(repoName, webReferences, errors);
|
|
257
399
|
console.log("✅ Framework Awareness scan complete\n");
|
|
258
400
|
return result;
|
|
259
401
|
}
|
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* OpenClaw manifest parser.
|
|
3
|
-
* Parses openclaw.json to extract
|
|
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[];
|
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* OpenClaw manifest parser.
|
|
4
|
-
* Parses openclaw.json to extract
|
|
3
|
+
* OpenClaw manifest parser — deep extraction.
|
|
4
|
+
* Parses openclaw.json / .openclaw.json to extract:
|
|
5
|
+
* - Orchestrator signal with version from meta
|
|
6
|
+
* - Agent config (models, workspace, concurrency)
|
|
7
|
+
* - Hooks (internal event scripts with paths + descriptions)
|
|
8
|
+
* - Plugins (enabled extensions)
|
|
9
|
+
* - Gateway config (port, auth, denied commands)
|
|
10
|
+
* - Channels (policies, stream modes)
|
|
11
|
+
* - Tools (web search, fetch)
|
|
12
|
+
* - Commands / messages config
|
|
13
|
+
*
|
|
14
|
+
* Also scans the OpenClaw workspace directory for:
|
|
15
|
+
* - Skills (reusable .md workflows)
|
|
16
|
+
* - Subagents (background agent directories)
|
|
17
|
+
* - Workspace .md files (AGENTS.md, IDENTITY.md, HEARTBEAT.md, etc.)
|
|
18
|
+
* - Hook scripts
|
|
5
19
|
*/
|
|
6
20
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
21
|
if (k2 === undefined) k2 = k;
|
|
@@ -40,6 +54,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
40
54
|
exports.parseOpenclawJson = parseOpenclawJson;
|
|
41
55
|
const fs = __importStar(require("fs"));
|
|
42
56
|
const path = __importStar(require("path"));
|
|
57
|
+
// ---------------------
|
|
58
|
+
// Deep JSON extraction
|
|
59
|
+
// ---------------------
|
|
43
60
|
function parseOpenclawJson(manifest) {
|
|
44
61
|
const signals = [];
|
|
45
62
|
const content = fs.readFileSync(manifest.absolutePath, "utf-8");
|
|
@@ -54,50 +71,546 @@ function parseOpenclawJson(manifest) {
|
|
|
54
71
|
return signals;
|
|
55
72
|
}
|
|
56
73
|
const rootPath = path.dirname(manifest.relativePath) || ".";
|
|
57
|
-
//
|
|
74
|
+
// --- Extract OpenClaw version from meta ---
|
|
75
|
+
const meta = config.meta;
|
|
76
|
+
const openclawVersion = meta?.lastTouchedVersion
|
|
77
|
+
? {
|
|
78
|
+
value: String(meta.lastTouchedVersion),
|
|
79
|
+
certainty: "exact",
|
|
80
|
+
sourceFile: manifest.relativePath,
|
|
81
|
+
notes: "From meta.lastTouchedVersion in openclaw.json",
|
|
82
|
+
}
|
|
83
|
+
: undefined;
|
|
84
|
+
// --- Primary orchestrator signal ---
|
|
58
85
|
signals.push({
|
|
59
86
|
kind: "orchestrator",
|
|
60
87
|
frameworkId: "openclaw",
|
|
61
88
|
frameworkName: "OpenClaw",
|
|
89
|
+
...(openclawVersion ? { version: openclawVersion } : {}),
|
|
62
90
|
evidence: {
|
|
63
91
|
file: manifest.relativePath,
|
|
64
92
|
reason: "openclaw.json configuration file found",
|
|
65
|
-
excerpt:
|
|
93
|
+
excerpt: openclawVersion
|
|
94
|
+
? `OpenClaw version ${openclawVersion.value}`
|
|
95
|
+
: "OpenClaw orchestrator (version not specified in config)",
|
|
96
|
+
},
|
|
97
|
+
scope: { pathRoot: rootPath },
|
|
98
|
+
});
|
|
99
|
+
// --- Agent config ---
|
|
100
|
+
extractAgentConfig(config, manifest, rootPath, signals);
|
|
101
|
+
// --- Hooks ---
|
|
102
|
+
extractHooks(config, manifest, rootPath, signals);
|
|
103
|
+
// --- Plugins ---
|
|
104
|
+
extractPlugins(config, manifest, rootPath, signals);
|
|
105
|
+
// --- Gateway ---
|
|
106
|
+
extractGateway(config, manifest, rootPath, signals);
|
|
107
|
+
// --- Channels (expanded) ---
|
|
108
|
+
extractChannels(config, manifest, rootPath, signals);
|
|
109
|
+
// --- Tools (expanded) ---
|
|
110
|
+
extractTools(config, manifest, rootPath, signals);
|
|
111
|
+
// --- Commands + messages ---
|
|
112
|
+
extractCommandsAndMessages(config, manifest, rootPath, signals);
|
|
113
|
+
// --- Workspace scanning ---
|
|
114
|
+
scanWorkspace(config, manifest, rootPath, signals);
|
|
115
|
+
return signals;
|
|
116
|
+
}
|
|
117
|
+
// ---------------------
|
|
118
|
+
// Section extractors
|
|
119
|
+
// ---------------------
|
|
120
|
+
function extractAgentConfig(config, manifest, rootPath, signals) {
|
|
121
|
+
const agents = config.agents;
|
|
122
|
+
if (!agents || typeof agents !== "object")
|
|
123
|
+
return;
|
|
124
|
+
const defaults = agents.defaults;
|
|
125
|
+
if (!defaults || typeof defaults !== "object")
|
|
126
|
+
return;
|
|
127
|
+
// Primary model
|
|
128
|
+
const model = defaults.model;
|
|
129
|
+
const primaryModel = model?.primary ? String(model.primary) : undefined;
|
|
130
|
+
// Available models
|
|
131
|
+
const models = defaults.models;
|
|
132
|
+
const modelCatalog = models ? Object.keys(models) : [];
|
|
133
|
+
// Workspace path
|
|
134
|
+
const workspace = defaults.workspace ? String(defaults.workspace) : undefined;
|
|
135
|
+
// Concurrency
|
|
136
|
+
const maxConcurrent = typeof defaults.maxConcurrent === "number" ? defaults.maxConcurrent : undefined;
|
|
137
|
+
const subagentConfig = defaults.subagents;
|
|
138
|
+
const subagentMaxConcurrent = subagentConfig && typeof subagentConfig.maxConcurrent === "number"
|
|
139
|
+
? subagentConfig.maxConcurrent
|
|
140
|
+
: undefined;
|
|
141
|
+
// Compaction
|
|
142
|
+
const compaction = defaults.compaction;
|
|
143
|
+
const compactionMode = compaction?.mode ? String(compaction.mode) : undefined;
|
|
144
|
+
const details = [];
|
|
145
|
+
if (primaryModel)
|
|
146
|
+
details.push(`primary model: ${primaryModel}`);
|
|
147
|
+
if (modelCatalog.length > 0)
|
|
148
|
+
details.push(`${modelCatalog.length} model(s) available`);
|
|
149
|
+
if (workspace)
|
|
150
|
+
details.push(`workspace: ${workspace}`);
|
|
151
|
+
if (maxConcurrent)
|
|
152
|
+
details.push(`maxConcurrent: ${maxConcurrent}`);
|
|
153
|
+
if (subagentMaxConcurrent)
|
|
154
|
+
details.push(`subagent maxConcurrent: ${subagentMaxConcurrent}`);
|
|
155
|
+
if (compactionMode)
|
|
156
|
+
details.push(`compaction: ${compactionMode}`);
|
|
157
|
+
signals.push({
|
|
158
|
+
kind: "agent",
|
|
159
|
+
frameworkId: "openclaw-agent-config",
|
|
160
|
+
frameworkName: "OpenClaw Agent Config",
|
|
161
|
+
evidence: {
|
|
162
|
+
file: manifest.relativePath,
|
|
163
|
+
reason: "Agent configuration in openclaw.json",
|
|
164
|
+
excerpt: details.join("; "),
|
|
66
165
|
},
|
|
67
166
|
scope: { pathRoot: rootPath },
|
|
68
167
|
});
|
|
69
|
-
//
|
|
168
|
+
// Emit individual model entries for the LLM to know available models
|
|
169
|
+
if (modelCatalog.length > 0) {
|
|
170
|
+
signals.push({
|
|
171
|
+
kind: "agent",
|
|
172
|
+
frameworkId: "openclaw-model-catalog",
|
|
173
|
+
frameworkName: "OpenClaw Model Catalog",
|
|
174
|
+
evidence: {
|
|
175
|
+
file: manifest.relativePath,
|
|
176
|
+
reason: "Available LLM models configured in openclaw.json",
|
|
177
|
+
excerpt: modelCatalog.join(", "),
|
|
178
|
+
},
|
|
179
|
+
scope: { pathRoot: rootPath },
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function extractHooks(config, manifest, rootPath, signals) {
|
|
184
|
+
const hooks = config.hooks;
|
|
185
|
+
if (!hooks || typeof hooks !== "object")
|
|
186
|
+
return;
|
|
187
|
+
const internal = hooks.internal;
|
|
188
|
+
if (!internal || typeof internal !== "object")
|
|
189
|
+
return;
|
|
190
|
+
if (internal.enabled === false)
|
|
191
|
+
return;
|
|
192
|
+
const entries = internal.entries;
|
|
193
|
+
if (!entries || typeof entries !== "object")
|
|
194
|
+
return;
|
|
195
|
+
for (const [hookName, hookConfig] of Object.entries(entries)) {
|
|
196
|
+
if (!hookConfig || typeof hookConfig !== "object")
|
|
197
|
+
continue;
|
|
198
|
+
const enabled = hookConfig.enabled !== false; // default true
|
|
199
|
+
const hookPath = hookConfig.path ? String(hookConfig.path) : undefined;
|
|
200
|
+
const description = hookConfig.description ? String(hookConfig.description) : undefined;
|
|
201
|
+
const details = [`enabled: ${enabled}`];
|
|
202
|
+
if (hookPath)
|
|
203
|
+
details.push(`path: ${hookPath}`);
|
|
204
|
+
if (description)
|
|
205
|
+
details.push(`desc: ${description}`);
|
|
206
|
+
signals.push({
|
|
207
|
+
kind: "hook",
|
|
208
|
+
frameworkId: `openclaw-hook-${hookName}`,
|
|
209
|
+
frameworkName: `OpenClaw Hook: ${hookName}`,
|
|
210
|
+
evidence: {
|
|
211
|
+
file: manifest.relativePath,
|
|
212
|
+
reason: description || `Hook '${hookName}' configured in openclaw.json`,
|
|
213
|
+
excerpt: details.join("; "),
|
|
214
|
+
},
|
|
215
|
+
scope: { pathRoot: rootPath },
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function extractPlugins(config, manifest, rootPath, signals) {
|
|
220
|
+
const plugins = config.plugins;
|
|
221
|
+
if (!plugins || typeof plugins !== "object")
|
|
222
|
+
return;
|
|
223
|
+
const entries = plugins.entries;
|
|
224
|
+
if (!entries || typeof entries !== "object")
|
|
225
|
+
return;
|
|
226
|
+
for (const [pluginName, pluginConfig] of Object.entries(entries)) {
|
|
227
|
+
if (!pluginConfig || typeof pluginConfig !== "object")
|
|
228
|
+
continue;
|
|
229
|
+
const enabled = pluginConfig.enabled !== false;
|
|
230
|
+
signals.push({
|
|
231
|
+
kind: "plugin",
|
|
232
|
+
frameworkId: `openclaw-plugin-${pluginName}`,
|
|
233
|
+
frameworkName: `OpenClaw Plugin: ${pluginName}`,
|
|
234
|
+
evidence: {
|
|
235
|
+
file: manifest.relativePath,
|
|
236
|
+
reason: `Plugin '${pluginName}' ${enabled ? "enabled" : "disabled"} in openclaw.json`,
|
|
237
|
+
excerpt: `enabled: ${enabled}`,
|
|
238
|
+
},
|
|
239
|
+
scope: { pathRoot: rootPath },
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function extractGateway(config, manifest, rootPath, signals) {
|
|
244
|
+
const gateway = config.gateway;
|
|
245
|
+
if (!gateway || typeof gateway !== "object")
|
|
246
|
+
return;
|
|
247
|
+
const details = [];
|
|
248
|
+
if (gateway.port)
|
|
249
|
+
details.push(`port: ${gateway.port}`);
|
|
250
|
+
if (gateway.mode)
|
|
251
|
+
details.push(`mode: ${gateway.mode}`);
|
|
252
|
+
if (gateway.bind)
|
|
253
|
+
details.push(`bind: ${gateway.bind}`);
|
|
254
|
+
const auth = gateway.auth;
|
|
255
|
+
if (auth?.mode)
|
|
256
|
+
details.push(`auth: ${auth.mode}`);
|
|
257
|
+
const tailscale = gateway.tailscale;
|
|
258
|
+
if (tailscale?.mode)
|
|
259
|
+
details.push(`tailscale: ${tailscale.mode}`);
|
|
260
|
+
const nodes = gateway.nodes;
|
|
261
|
+
const denyCommands = nodes?.denyCommands;
|
|
262
|
+
if (denyCommands && Array.isArray(denyCommands)) {
|
|
263
|
+
details.push(`denied commands: ${denyCommands.join(", ")}`);
|
|
264
|
+
}
|
|
265
|
+
signals.push({
|
|
266
|
+
kind: "tooling",
|
|
267
|
+
frameworkId: "openclaw-gateway",
|
|
268
|
+
frameworkName: "OpenClaw Gateway",
|
|
269
|
+
evidence: {
|
|
270
|
+
file: manifest.relativePath,
|
|
271
|
+
reason: "Gateway configuration in openclaw.json",
|
|
272
|
+
excerpt: details.join("; "),
|
|
273
|
+
},
|
|
274
|
+
scope: { pathRoot: rootPath },
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
function extractChannels(config, manifest, rootPath, signals) {
|
|
278
|
+
const channels = config.channels;
|
|
279
|
+
if (!channels || typeof channels !== "object")
|
|
280
|
+
return;
|
|
281
|
+
for (const [channelName, channelConfig] of Object.entries(channels)) {
|
|
282
|
+
if (!channelConfig || typeof channelConfig !== "object")
|
|
283
|
+
continue;
|
|
284
|
+
const ch = channelConfig;
|
|
285
|
+
const details = [];
|
|
286
|
+
if (ch.enabled === false)
|
|
287
|
+
details.push("disabled");
|
|
288
|
+
if (ch.dmPolicy)
|
|
289
|
+
details.push(`dmPolicy: ${ch.dmPolicy}`);
|
|
290
|
+
if (ch.groupPolicy)
|
|
291
|
+
details.push(`groupPolicy: ${ch.groupPolicy}`);
|
|
292
|
+
if (ch.streamMode)
|
|
293
|
+
details.push(`streamMode: ${ch.streamMode}`);
|
|
294
|
+
if (ch.selfChatMode !== undefined)
|
|
295
|
+
details.push(`selfChat: ${ch.selfChatMode}`);
|
|
296
|
+
if (ch.debounceMs !== undefined)
|
|
297
|
+
details.push(`debounce: ${ch.debounceMs}ms`);
|
|
298
|
+
if (ch.mediaMaxMb)
|
|
299
|
+
details.push(`mediaMax: ${ch.mediaMaxMb}MB`);
|
|
300
|
+
signals.push({
|
|
301
|
+
kind: "tooling",
|
|
302
|
+
frameworkId: `openclaw-channel-${channelName}`,
|
|
303
|
+
frameworkName: `OpenClaw Channel: ${channelName}`,
|
|
304
|
+
evidence: {
|
|
305
|
+
file: manifest.relativePath,
|
|
306
|
+
reason: `Channel '${channelName}' configured in openclaw.json`,
|
|
307
|
+
excerpt: details.length > 0 ? details.join("; ") : `channel ${channelName} configured`,
|
|
308
|
+
},
|
|
309
|
+
scope: { pathRoot: rootPath },
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
function extractTools(config, manifest, rootPath, signals) {
|
|
70
314
|
const tools = config.tools;
|
|
71
|
-
if (tools
|
|
72
|
-
|
|
315
|
+
if (!tools || typeof tools !== "object")
|
|
316
|
+
return;
|
|
317
|
+
for (const [toolName, toolConfig] of Object.entries(tools)) {
|
|
318
|
+
if (!toolConfig || typeof toolConfig !== "object")
|
|
319
|
+
continue;
|
|
320
|
+
// Build a summary of the tool's sub-capabilities
|
|
321
|
+
const subCapabilities = [];
|
|
322
|
+
for (const [subName, subConfig] of Object.entries(toolConfig)) {
|
|
323
|
+
if (subConfig && typeof subConfig === "object") {
|
|
324
|
+
const sc = subConfig;
|
|
325
|
+
const enabled = sc.enabled !== false;
|
|
326
|
+
subCapabilities.push(`${subName}: ${enabled ? "enabled" : "disabled"}`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
signals.push({
|
|
330
|
+
kind: "tooling",
|
|
331
|
+
frameworkId: `openclaw-tool-${toolName}`,
|
|
332
|
+
frameworkName: `OpenClaw Tool: ${toolName}`,
|
|
333
|
+
evidence: {
|
|
334
|
+
file: manifest.relativePath,
|
|
335
|
+
reason: `Tool '${toolName}' configured in openclaw.json`,
|
|
336
|
+
excerpt: subCapabilities.length > 0 ? subCapabilities.join("; ") : `tool ${toolName} configured`,
|
|
337
|
+
},
|
|
338
|
+
scope: { pathRoot: rootPath },
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
function extractCommandsAndMessages(config, manifest, rootPath, signals) {
|
|
343
|
+
const commands = config.commands;
|
|
344
|
+
if (commands && typeof commands === "object") {
|
|
345
|
+
const details = [];
|
|
346
|
+
if (commands.native)
|
|
347
|
+
details.push(`native: ${commands.native}`);
|
|
348
|
+
if (commands.nativeSkills)
|
|
349
|
+
details.push(`nativeSkills: ${commands.nativeSkills}`);
|
|
350
|
+
if (details.length > 0) {
|
|
73
351
|
signals.push({
|
|
74
352
|
kind: "tooling",
|
|
75
|
-
frameworkId:
|
|
76
|
-
frameworkName:
|
|
353
|
+
frameworkId: "openclaw-commands",
|
|
354
|
+
frameworkName: "OpenClaw Commands",
|
|
77
355
|
evidence: {
|
|
78
356
|
file: manifest.relativePath,
|
|
79
|
-
reason:
|
|
80
|
-
excerpt:
|
|
357
|
+
reason: "Command configuration in openclaw.json",
|
|
358
|
+
excerpt: details.join("; "),
|
|
81
359
|
},
|
|
82
360
|
scope: { pathRoot: rootPath },
|
|
83
361
|
});
|
|
84
362
|
}
|
|
85
363
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
364
|
+
const messages = config.messages;
|
|
365
|
+
if (messages && typeof messages === "object") {
|
|
366
|
+
const details = [];
|
|
367
|
+
if (messages.ackReactionScope)
|
|
368
|
+
details.push(`ackReactionScope: ${messages.ackReactionScope}`);
|
|
369
|
+
if (details.length > 0) {
|
|
90
370
|
signals.push({
|
|
91
371
|
kind: "tooling",
|
|
92
|
-
frameworkId:
|
|
93
|
-
frameworkName:
|
|
372
|
+
frameworkId: "openclaw-messages",
|
|
373
|
+
frameworkName: "OpenClaw Messages",
|
|
94
374
|
evidence: {
|
|
95
375
|
file: manifest.relativePath,
|
|
96
|
-
reason:
|
|
376
|
+
reason: "Message configuration in openclaw.json",
|
|
377
|
+
excerpt: details.join("; "),
|
|
97
378
|
},
|
|
98
379
|
scope: { pathRoot: rootPath },
|
|
99
380
|
});
|
|
100
381
|
}
|
|
101
382
|
}
|
|
102
|
-
|
|
383
|
+
}
|
|
384
|
+
// ---------------------
|
|
385
|
+
// Workspace scanner
|
|
386
|
+
// ---------------------
|
|
387
|
+
function scanWorkspace(config, manifest, rootPath, signals) {
|
|
388
|
+
// Resolve workspace directory:
|
|
389
|
+
// 1. From agents.defaults.workspace in config
|
|
390
|
+
// 2. Fallback to sibling "workspace/" directory relative to the config file
|
|
391
|
+
const agents = config.agents;
|
|
392
|
+
const defaults = agents?.defaults;
|
|
393
|
+
const configuredWorkspace = defaults?.workspace ? String(defaults.workspace) : undefined;
|
|
394
|
+
const configDir = path.dirname(manifest.absolutePath);
|
|
395
|
+
let workspaceDir;
|
|
396
|
+
if (configuredWorkspace) {
|
|
397
|
+
// Could be absolute or relative — try both
|
|
398
|
+
const candidate = path.isAbsolute(configuredWorkspace)
|
|
399
|
+
? configuredWorkspace
|
|
400
|
+
: path.resolve(configDir, configuredWorkspace);
|
|
401
|
+
if (existsAndIsDir(candidate)) {
|
|
402
|
+
workspaceDir = candidate;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
if (!workspaceDir) {
|
|
406
|
+
// Fallback: sibling "workspace/" directory
|
|
407
|
+
const fallback = path.join(configDir, "workspace");
|
|
408
|
+
if (existsAndIsDir(fallback)) {
|
|
409
|
+
workspaceDir = fallback;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
if (!workspaceDir)
|
|
413
|
+
return;
|
|
414
|
+
const workspaceRel = path.relative(path.dirname(manifest.absolutePath), workspaceDir);
|
|
415
|
+
// Scan workspace .md files (identity layer)
|
|
416
|
+
scanWorkspaceMdFiles(workspaceDir, workspaceRel, manifest, rootPath, signals);
|
|
417
|
+
// Scan skills
|
|
418
|
+
scanSkills(workspaceDir, workspaceRel, manifest, rootPath, signals);
|
|
419
|
+
// Scan subagents
|
|
420
|
+
scanSubagents(workspaceDir, workspaceRel, manifest, rootPath, signals);
|
|
421
|
+
}
|
|
422
|
+
function scanWorkspaceMdFiles(workspaceDir, workspaceRel, manifest, rootPath, signals) {
|
|
423
|
+
const WORKSPACE_FILES = {
|
|
424
|
+
"AGENTS.md": "Agent behavioral instructions and skill routing",
|
|
425
|
+
"IDENTITY.md": "Agent identity, personality, and avatar",
|
|
426
|
+
"HEARTBEAT.md": "Periodic awareness checklist (cron-like proactive checks)",
|
|
427
|
+
"MEMORY.md": "Long-term curated memory store",
|
|
428
|
+
"SOUL.md": "Agent personality and core values",
|
|
429
|
+
"USER.md": "User profile and preferences",
|
|
430
|
+
"TOOLS.md": "Tool configuration and capabilities",
|
|
431
|
+
"OPTIMIZATION.md": "Performance and cost optimization rules",
|
|
432
|
+
};
|
|
433
|
+
for (const [filename, description] of Object.entries(WORKSPACE_FILES)) {
|
|
434
|
+
const filePath = path.join(workspaceDir, filename);
|
|
435
|
+
if (!existsAndIsFile(filePath))
|
|
436
|
+
continue;
|
|
437
|
+
// For HEARTBEAT.md, check if it has actual tasks (non-empty, non-comment content)
|
|
438
|
+
if (filename === "HEARTBEAT.md") {
|
|
439
|
+
const heartbeatContent = safeReadFile(filePath);
|
|
440
|
+
const hasActiveTasks = heartbeatContent
|
|
441
|
+
? heartbeatContent.split("\n").some((line) => line.trim() && !line.trim().startsWith("#"))
|
|
442
|
+
: false;
|
|
443
|
+
signals.push({
|
|
444
|
+
kind: "heartbeat",
|
|
445
|
+
frameworkId: "openclaw-heartbeat",
|
|
446
|
+
frameworkName: "OpenClaw Heartbeat",
|
|
447
|
+
evidence: {
|
|
448
|
+
file: path.join(workspaceRel, filename),
|
|
449
|
+
reason: hasActiveTasks
|
|
450
|
+
? "HEARTBEAT.md found with active periodic tasks"
|
|
451
|
+
: "HEARTBEAT.md found but no active tasks configured",
|
|
452
|
+
excerpt: hasActiveTasks ? "Status: ACTIVE" : "Status: INACTIVE (empty/comments only)",
|
|
453
|
+
},
|
|
454
|
+
scope: { pathRoot: rootPath },
|
|
455
|
+
});
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
// For IDENTITY.md, extract the agent name if present
|
|
459
|
+
let excerpt = description;
|
|
460
|
+
if (filename === "IDENTITY.md") {
|
|
461
|
+
const identityContent = safeReadFile(filePath);
|
|
462
|
+
if (identityContent) {
|
|
463
|
+
const nameMatch = identityContent.match(/\*\*Name:\*\*\s*(.+)/);
|
|
464
|
+
if (nameMatch) {
|
|
465
|
+
excerpt = `Agent name: ${nameMatch[1].trim()} — ${description}`;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
signals.push({
|
|
470
|
+
kind: "agent",
|
|
471
|
+
frameworkId: `openclaw-workspace-${filename.replace(".md", "").toLowerCase()}`,
|
|
472
|
+
frameworkName: `OpenClaw Workspace: ${filename}`,
|
|
473
|
+
evidence: {
|
|
474
|
+
file: path.join(workspaceRel, filename),
|
|
475
|
+
reason: `Workspace file ${filename} found`,
|
|
476
|
+
excerpt,
|
|
477
|
+
},
|
|
478
|
+
scope: { pathRoot: rootPath },
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
function scanSkills(workspaceDir, workspaceRel, manifest, rootPath, signals) {
|
|
483
|
+
const skillsDir = path.join(workspaceDir, "skills");
|
|
484
|
+
if (!existsAndIsDir(skillsDir))
|
|
485
|
+
return;
|
|
486
|
+
let entries;
|
|
487
|
+
try {
|
|
488
|
+
entries = fs.readdirSync(skillsDir);
|
|
489
|
+
}
|
|
490
|
+
catch {
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
for (const entry of entries) {
|
|
494
|
+
if (!entry.endsWith(".md"))
|
|
495
|
+
continue;
|
|
496
|
+
if (entry === "SKILL_INDEX.md")
|
|
497
|
+
continue; // Index file, not a skill itself
|
|
498
|
+
const skillPath = path.join(skillsDir, entry);
|
|
499
|
+
if (!existsAndIsFile(skillPath))
|
|
500
|
+
continue;
|
|
501
|
+
// Read first meaningful line for description
|
|
502
|
+
const content = safeReadFile(skillPath);
|
|
503
|
+
let description = `Skill workflow: ${entry.replace(".md", "")}`;
|
|
504
|
+
if (content) {
|
|
505
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
506
|
+
const firstLine = lines[0] || "";
|
|
507
|
+
if (firstLine.startsWith("#")) {
|
|
508
|
+
description = firstLine.replace(/^#+\s*/, "").trim();
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
const skillName = entry
|
|
512
|
+
.replace(".md", "")
|
|
513
|
+
.replace(/-/g, " ")
|
|
514
|
+
.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
515
|
+
signals.push({
|
|
516
|
+
kind: "skill",
|
|
517
|
+
frameworkId: `openclaw-skill-${entry.replace(".md", "")}`,
|
|
518
|
+
frameworkName: `OpenClaw Skill: ${skillName}`,
|
|
519
|
+
evidence: {
|
|
520
|
+
file: path.join(workspaceRel, "skills", entry),
|
|
521
|
+
reason: description,
|
|
522
|
+
excerpt: `Skill file: ${entry}`,
|
|
523
|
+
},
|
|
524
|
+
scope: { pathRoot: rootPath },
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
function scanSubagents(workspaceDir, workspaceRel, manifest, rootPath, signals) {
|
|
529
|
+
const subagentsDir = path.join(workspaceDir, "subagents");
|
|
530
|
+
if (!existsAndIsDir(subagentsDir))
|
|
531
|
+
return;
|
|
532
|
+
let entries;
|
|
533
|
+
try {
|
|
534
|
+
entries = fs.readdirSync(subagentsDir);
|
|
535
|
+
}
|
|
536
|
+
catch {
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
for (const entry of entries) {
|
|
540
|
+
const entryPath = path.join(subagentsDir, entry);
|
|
541
|
+
if (!existsAndIsDir(entryPath))
|
|
542
|
+
continue;
|
|
543
|
+
// List files in the subagent directory
|
|
544
|
+
let subFiles;
|
|
545
|
+
try {
|
|
546
|
+
subFiles = fs.readdirSync(entryPath).filter((f) => {
|
|
547
|
+
const fp = path.join(entryPath, f);
|
|
548
|
+
try {
|
|
549
|
+
return fs.statSync(fp).isFile() && !f.startsWith(".");
|
|
550
|
+
}
|
|
551
|
+
catch {
|
|
552
|
+
return false;
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
catch {
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
// Filter out __pycache__ etc
|
|
560
|
+
const meaningfulFiles = subFiles.filter((f) => !f.endsWith(".pyc") && f !== "__pycache__");
|
|
561
|
+
// Try to read protocol.md for description
|
|
562
|
+
let description = `Subagent: ${entry}`;
|
|
563
|
+
const protocolPath = path.join(entryPath, "protocol.md");
|
|
564
|
+
if (existsAndIsFile(protocolPath)) {
|
|
565
|
+
const content = safeReadFile(protocolPath);
|
|
566
|
+
if (content) {
|
|
567
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
568
|
+
const firstLine = lines[0] || "";
|
|
569
|
+
if (firstLine.startsWith("#")) {
|
|
570
|
+
description = firstLine.replace(/^#+\s*/, "").trim();
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
const subagentName = entry.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
575
|
+
signals.push({
|
|
576
|
+
kind: "subagent",
|
|
577
|
+
frameworkId: `openclaw-subagent-${entry}`,
|
|
578
|
+
frameworkName: `OpenClaw Subagent: ${subagentName}`,
|
|
579
|
+
evidence: {
|
|
580
|
+
file: path.join(workspaceRel, "subagents", entry),
|
|
581
|
+
reason: description,
|
|
582
|
+
excerpt: meaningfulFiles.length > 0
|
|
583
|
+
? `Files: ${meaningfulFiles.join(", ")}`
|
|
584
|
+
: "Empty subagent directory",
|
|
585
|
+
},
|
|
586
|
+
scope: { pathRoot: rootPath },
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
// ---------------------
|
|
591
|
+
// Helpers
|
|
592
|
+
// ---------------------
|
|
593
|
+
function existsAndIsDir(p) {
|
|
594
|
+
try {
|
|
595
|
+
return fs.statSync(p).isDirectory();
|
|
596
|
+
}
|
|
597
|
+
catch {
|
|
598
|
+
return false;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
function existsAndIsFile(p) {
|
|
602
|
+
try {
|
|
603
|
+
return fs.statSync(p).isFile();
|
|
604
|
+
}
|
|
605
|
+
catch {
|
|
606
|
+
return false;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
function safeReadFile(p) {
|
|
610
|
+
try {
|
|
611
|
+
return fs.readFileSync(p, "utf-8");
|
|
612
|
+
}
|
|
613
|
+
catch {
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
103
616
|
}
|
|
@@ -13,7 +13,7 @@ export interface VersionInfo {
|
|
|
13
13
|
notes?: string;
|
|
14
14
|
}
|
|
15
15
|
/** Signal kinds emitted by manifest parsers */
|
|
16
|
-
export type SignalKind = "framework" | "version" | "orchestrator" | "entrypoint" | "tooling";
|
|
16
|
+
export type SignalKind = "framework" | "version" | "orchestrator" | "entrypoint" | "tooling" | "agent" | "subagent" | "heartbeat" | "cron" | "hook" | "skill" | "plugin";
|
|
17
17
|
/** Evidence for a detected signal */
|
|
18
18
|
export interface SignalEvidence {
|
|
19
19
|
file: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thelogicatelier/sylva",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.12",
|
|
4
4
|
"description": "Auto-generate AGENTS.md for your repository using Ax-LLM. Analyze the structural backbone, data flow, and day-to-day coding conventions natively.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|