evolclaw 3.0.0 → 3.1.1
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/README.md +1 -1
- package/bin/ec.js +29 -0
- package/dist/agents/baseagent-normalize.js +19 -0
- package/dist/agents/claude-runner.js +47 -12
- package/dist/agents/codex-runner.js +2 -0
- package/dist/agents/gemini-runner.js +9 -9
- package/dist/agents/kit-renderer.js +281 -0
- package/dist/aun/aid/identity.js +28 -0
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/aid/lifecycle-log.js +33 -0
- package/dist/aun/msg/group.js +3 -1
- package/dist/aun/msg/p2p.js +42 -1
- package/dist/channels/aun.js +427 -146
- package/dist/channels/dingtalk.js +3 -1
- package/dist/channels/feishu.js +128 -7
- package/dist/channels/qqbot.js +3 -1
- package/dist/channels/wechat.js +4 -1
- package/dist/channels/wecom.js +3 -1
- package/dist/cli/bench.js +1219 -0
- package/dist/cli/index.js +418 -40
- package/dist/cli/init.js +3 -4
- package/dist/cli/link-rules.js +245 -0
- package/dist/cli/net-check.js +640 -0
- package/dist/cli/watch-msg.js +666 -0
- package/dist/config-store.js +82 -5
- package/dist/core/channel-loader.js +23 -10
- package/dist/core/command-handler.js +127 -99
- package/dist/core/evolagent.js +5 -10
- package/dist/core/message/im-renderer.js +93 -48
- package/dist/core/message/items-formatter.js +11 -4
- package/dist/core/message/message-bridge.js +11 -2
- package/dist/core/message/message-log.js +8 -1
- package/dist/core/message/message-processor.js +194 -127
- package/dist/core/message/message-queue.js +10 -3
- package/dist/core/permission.js +95 -3
- package/dist/core/relation/peer-identity.js +161 -0
- package/dist/core/session/session-manager.js +103 -65
- package/dist/core/trigger/manager.js +16 -0
- package/dist/core/trigger/parser.js +110 -0
- package/dist/core/trigger/scheduler.js +7 -1
- package/dist/data/error-dict.json +118 -0
- package/dist/eck/baseagent-caps.js +18 -0
- package/dist/eck/detect.js +47 -0
- package/dist/eck/init.js +77 -0
- package/dist/eck/rules-loader.js +28 -0
- package/dist/index.js +186 -19
- package/dist/net-check.js +640 -0
- package/dist/paths.js +31 -40
- package/dist/utils/aid-lifecycle-log.js +33 -0
- package/dist/utils/atomic-write.js +10 -0
- package/dist/utils/cross-platform.js +17 -8
- package/dist/utils/error-utils.js +27 -15
- package/dist/utils/instance-registry.js +6 -5
- package/dist/utils/log-writer.js +2 -1
- package/dist/utils/logger.js +10 -0
- package/dist/utils/npm-ops.js +35 -3
- package/dist/utils/process-introspect.js +16 -38
- package/dist/utils/stats.js +216 -2
- package/dist/watch-msg.js +26 -11
- package/evolclaw-install-aun.md +14 -2
- package/kits/docs/GUIDE.md +20 -0
- package/kits/docs/INDEX.md +52 -0
- package/kits/docs/aun/CHEATSHEET.md +17 -0
- package/kits/docs/aun/SYNC_PROTOCOL.md +15 -0
- package/kits/docs/channels/feishu.md +27 -0
- package/kits/docs/eck_templates/GUIDE.template.md +22 -0
- package/kits/docs/eck_templates/INDEX.template.md +28 -0
- package/kits/docs/eck_templates/path-registry.template.md +33 -0
- package/kits/docs/eck_templates/runtime.template.md +19 -0
- package/kits/docs/evolclaw/MSG_GROUP.md +30 -0
- package/kits/docs/evolclaw/MSG_PRIVATE.md +72 -0
- package/kits/docs/identity/AID_PROFILE_SPEC.md +27 -0
- package/kits/docs/identity/PATH_OPS.md +16 -0
- package/kits/docs/identity/ROLE_DETAIL.md +20 -0
- package/kits/docs/path-registry.md +43 -0
- package/kits/eck_manifest.json +95 -0
- package/kits/rules/01-overview.md +120 -0
- package/kits/rules/02-navigation.md +75 -0
- package/kits/rules/03-identity.md +34 -0
- package/kits/rules/04-relation.md +49 -0
- package/kits/rules/05-venue.md +45 -0
- package/kits/rules/06-channel.md +73 -0
- package/kits/templates/system-fragments/baseagent.md +2 -0
- package/kits/templates/system-fragments/channel.md +10 -0
- package/kits/templates/system-fragments/identity.md +12 -0
- package/kits/templates/system-fragments/relation.md +9 -0
- package/kits/templates/system-fragments/runtime.md +19 -0
- package/kits/templates/system-fragments/venue.md +5 -0
- package/package.json +7 -5
- package/dist/agents/templates.js +0 -122
- package/dist/data/prompts.md +0 -137
- package/kits/aun/meta.md +0 -25
- package/kits/aun/role.md +0 -25
- package/kits/templates/group.md +0 -20
- package/kits/templates/private.md +0 -9
- package/kits/templates/system-fragments/personal-context.md +0 -3
- package/kits/templates/system-fragments/self-intro.md +0 -5
- package/kits/templates/system-fragments/speaker-intro.md +0 -5
- package/kits/templates/system-fragments/venue-intro.md +0 -5
- /package/kits/{channels → docs/channels}/aun.md +0 -0
- /package/kits/{evolclaw/commands.md → docs/evolclaw/AGENT_CMD.md} +0 -0
- /package/kits/{evolclaw → docs/evolclaw}/self-summary.md +0 -0
- /package/kits/{evolclaw → docs/evolclaw}/tools.md +0 -0
- /package/kits/{evolclaw → docs/identity}/identity-tools.md +0 -0
|
@@ -43,6 +43,116 @@ export function validateCronExpr(expr) {
|
|
|
43
43
|
return false;
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
+
export function parseTriggerUpdate(args) {
|
|
47
|
+
// First token is the trigger name/id, rest is flags
|
|
48
|
+
const trimmed = args.trim();
|
|
49
|
+
if (!trimmed) {
|
|
50
|
+
return { ok: false, error: '用法:/trigger update <名称|ID> [--参数...]' };
|
|
51
|
+
}
|
|
52
|
+
// Extract nameOrId: first non-flag token (could be quoted)
|
|
53
|
+
let nameOrId;
|
|
54
|
+
let rest;
|
|
55
|
+
if (trimmed.startsWith('"') || trimmed.startsWith("'")) {
|
|
56
|
+
const quote = trimmed[0];
|
|
57
|
+
const end = trimmed.indexOf(quote, 1);
|
|
58
|
+
if (end === -1) {
|
|
59
|
+
return { ok: false, error: '名称引号未闭合' };
|
|
60
|
+
}
|
|
61
|
+
nameOrId = trimmed.slice(1, end);
|
|
62
|
+
rest = trimmed.slice(end + 1).trim();
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
const spaceIdx = trimmed.indexOf(' ');
|
|
66
|
+
if (spaceIdx === -1) {
|
|
67
|
+
return { ok: false, error: '至少需要指定一个修改参数(如 --prompt、--delay 等)' };
|
|
68
|
+
}
|
|
69
|
+
nameOrId = trimmed.slice(0, spaceIdx);
|
|
70
|
+
rest = trimmed.slice(spaceIdx + 1).trim();
|
|
71
|
+
}
|
|
72
|
+
if (!rest) {
|
|
73
|
+
return { ok: false, error: '至少需要指定一个修改参数(如 --prompt、--delay 等)' };
|
|
74
|
+
}
|
|
75
|
+
const flags = parseFlags(rest);
|
|
76
|
+
if (flags.size === 0) {
|
|
77
|
+
return { ok: false, error: '至少需要指定一个修改参数(如 --prompt、--delay 等)' };
|
|
78
|
+
}
|
|
79
|
+
const result = {};
|
|
80
|
+
// Parse schedule if provided (only one allowed)
|
|
81
|
+
const hasDelay = flags.has('delay');
|
|
82
|
+
const hasAt = flags.has('at');
|
|
83
|
+
const hasCron = flags.has('cron');
|
|
84
|
+
const timeCount = [hasDelay, hasAt, hasCron].filter(Boolean).length;
|
|
85
|
+
if (timeCount > 1) {
|
|
86
|
+
return { ok: false, error: '--delay、--at、--cron 互斥,只能指定一个' };
|
|
87
|
+
}
|
|
88
|
+
if (hasDelay) {
|
|
89
|
+
const raw = flags.get('delay');
|
|
90
|
+
const ms = parseDuration(raw);
|
|
91
|
+
if (ms === null)
|
|
92
|
+
return { ok: false, error: `无法解析 --delay "${raw}",支持格式:30m、2h、1d、2h30m` };
|
|
93
|
+
result.scheduleType = 'delay';
|
|
94
|
+
result.scheduleValue = String(ms);
|
|
95
|
+
}
|
|
96
|
+
else if (hasAt) {
|
|
97
|
+
const raw = flags.get('at');
|
|
98
|
+
const ts = parseIsoDate(raw);
|
|
99
|
+
if (ts === null)
|
|
100
|
+
return { ok: false, error: `无法解析 --at "${raw}",请使用 ISO 格式,如 2026-05-15T09:00` };
|
|
101
|
+
if (ts <= Date.now())
|
|
102
|
+
return { ok: false, error: `--at 时间已过期:${raw}` };
|
|
103
|
+
result.scheduleType = 'at';
|
|
104
|
+
result.scheduleValue = new Date(ts).toISOString();
|
|
105
|
+
}
|
|
106
|
+
else if (hasCron) {
|
|
107
|
+
const raw = flags.get('cron');
|
|
108
|
+
if (!validateCronExpr(raw))
|
|
109
|
+
return { ok: false, error: `无效的 cron 表达式:"${raw}"` };
|
|
110
|
+
result.scheduleType = 'cron';
|
|
111
|
+
result.scheduleValue = raw;
|
|
112
|
+
}
|
|
113
|
+
// Parse optional fields
|
|
114
|
+
if (flags.has('prompt')) {
|
|
115
|
+
const prompt = flags.get('prompt');
|
|
116
|
+
if (!prompt || prompt === true)
|
|
117
|
+
return { ok: false, error: '--prompt 不能为空' };
|
|
118
|
+
if (typeof prompt === 'string' && prompt.length > 4096)
|
|
119
|
+
return { ok: false, error: '--prompt 超过 4096 字符限制' };
|
|
120
|
+
result.prompt = prompt;
|
|
121
|
+
}
|
|
122
|
+
if (flags.has('name')) {
|
|
123
|
+
const name = flags.get('name');
|
|
124
|
+
if (!name || name === true)
|
|
125
|
+
return { ok: false, error: '--name 不能为空' };
|
|
126
|
+
result.name = name;
|
|
127
|
+
}
|
|
128
|
+
if (flags.has('session')) {
|
|
129
|
+
const sv = flags.get('session');
|
|
130
|
+
if (sv !== 'latest' && sv !== 'silent')
|
|
131
|
+
return { ok: false, error: '--session 只接受 latest 或 silent' };
|
|
132
|
+
result.targetSessionStrategy = sv;
|
|
133
|
+
}
|
|
134
|
+
if (flags.has('agent')) {
|
|
135
|
+
const agent = flags.get('agent');
|
|
136
|
+
if (!agent || agent === true)
|
|
137
|
+
return { ok: false, error: '--agent 不能为空' };
|
|
138
|
+
result.agentId = agent;
|
|
139
|
+
}
|
|
140
|
+
const hasChannel = flags.has('channel');
|
|
141
|
+
const hasChannelId = flags.has('channelid');
|
|
142
|
+
if (hasChannel !== hasChannelId) {
|
|
143
|
+
return { ok: false, error: '--channel 与 --channelid 必须同时指定或同时省略' };
|
|
144
|
+
}
|
|
145
|
+
if (hasChannel) {
|
|
146
|
+
result.targetChannel = flags.get('channel');
|
|
147
|
+
result.targetChannelId = flags.get('channelid');
|
|
148
|
+
}
|
|
149
|
+
if (flags.has('thread')) {
|
|
150
|
+
if (flags.has('session'))
|
|
151
|
+
return { ok: false, error: '--thread 与 --session 互斥' };
|
|
152
|
+
result.targetThreadId = flags.get('thread');
|
|
153
|
+
}
|
|
154
|
+
return { ok: true, nameOrId, value: result };
|
|
155
|
+
}
|
|
46
156
|
export function parseTriggerSet(args) {
|
|
47
157
|
const flags = parseFlags(args);
|
|
48
158
|
const hasDelay = flags.has('delay');
|
|
@@ -137,6 +137,12 @@ export class TriggerScheduler {
|
|
|
137
137
|
this.inflightCron.delete(id);
|
|
138
138
|
this.resetTimer();
|
|
139
139
|
}
|
|
140
|
+
update(trigger) {
|
|
141
|
+
this.heap.remove(trigger.id);
|
|
142
|
+
this.heap.push(trigger);
|
|
143
|
+
this.resetTimer();
|
|
144
|
+
this.eventBus.publish({ type: 'trigger:updated', triggerId: trigger.id, name: trigger.name, peerId: trigger.createdByPeerId });
|
|
145
|
+
}
|
|
140
146
|
stop() {
|
|
141
147
|
if (this.timer) {
|
|
142
148
|
clearTimeout(this.timer);
|
|
@@ -153,7 +159,7 @@ export class TriggerScheduler {
|
|
|
153
159
|
if (!top)
|
|
154
160
|
return;
|
|
155
161
|
const delay = Math.max(0, top.nextFireAt - Date.now());
|
|
156
|
-
this.timer = setTimeout(() => this.onFire(), delay);
|
|
162
|
+
this.timer = setTimeout(() => this.onFire(), delay).unref();
|
|
157
163
|
}
|
|
158
164
|
onFire() {
|
|
159
165
|
this.timer = null;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
{
|
|
2
|
+
"rules": [
|
|
3
|
+
{
|
|
4
|
+
"id": "context-too-long-cn",
|
|
5
|
+
"match": "上下文过长",
|
|
6
|
+
"action": "stop",
|
|
7
|
+
"type": "context_too_long",
|
|
8
|
+
"message": "⚠️ 上下文过长,请手动输入 /compact 压缩上下文"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"id": "context-too-long-en",
|
|
12
|
+
"match": "context too long",
|
|
13
|
+
"action": "stop",
|
|
14
|
+
"type": "context_too_long",
|
|
15
|
+
"message": "⚠️ 上下文过长,请手动输入 /compact 压缩上下文"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": "prompt-too-long",
|
|
19
|
+
"match": "prompt is too long",
|
|
20
|
+
"action": "stop",
|
|
21
|
+
"type": "context_too_long",
|
|
22
|
+
"message": "⚠️ 输入过长,请精简提问或使用 /compact 压缩上下文"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"id": "invalid-api-key",
|
|
26
|
+
"match": "invalid api key",
|
|
27
|
+
"action": "stop",
|
|
28
|
+
"type": "auth_error",
|
|
29
|
+
"message": "❌ API Key 无效,请检查密钥配置。使用 /status 查看当前配置"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": "key-not-found",
|
|
33
|
+
"match": "key_not_found",
|
|
34
|
+
"action": "stop",
|
|
35
|
+
"type": "auth_error",
|
|
36
|
+
"message": "❌ API Key 未找到,请检查密钥配置"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"id": "credit-query-timeout",
|
|
40
|
+
"match": "积分查询超时",
|
|
41
|
+
"action": "retry",
|
|
42
|
+
"type": "api_error",
|
|
43
|
+
"message": "⚠️ 积分查询超时,正在重试..."
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": "api-error-400",
|
|
47
|
+
"match": "api error: 400",
|
|
48
|
+
"action": "stop",
|
|
49
|
+
"type": "api_error",
|
|
50
|
+
"message": "❌ 请求格式错误,请检查输入内容"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"id": "api-error-403",
|
|
54
|
+
"match": "api error: 403",
|
|
55
|
+
"action": "retry",
|
|
56
|
+
"type": "api_error",
|
|
57
|
+
"message": "⚠️ API 访问受限,正在重试..."
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"id": "api-error-429",
|
|
61
|
+
"match": "api error: 429",
|
|
62
|
+
"action": "retry",
|
|
63
|
+
"type": "api_error",
|
|
64
|
+
"message": "⚠️ 请求过于频繁,正在重试..."
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"id": "api-error-500",
|
|
68
|
+
"match": "api error: 500",
|
|
69
|
+
"action": "retry",
|
|
70
|
+
"type": "api_error",
|
|
71
|
+
"message": "❌ API 服务暂时不可用,正在重试..."
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"id": "api-error-502",
|
|
75
|
+
"match": "api error: 502",
|
|
76
|
+
"action": "retry",
|
|
77
|
+
"type": "api_error"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"id": "api-error-503",
|
|
81
|
+
"match": "api error: 503",
|
|
82
|
+
"action": "retry",
|
|
83
|
+
"type": "api_error"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"id": "api-error-504",
|
|
87
|
+
"match": "api error: 504",
|
|
88
|
+
"action": "retry",
|
|
89
|
+
"type": "api_error"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"id": "not-valid-json",
|
|
93
|
+
"match": "is not valid json",
|
|
94
|
+
"action": "retry",
|
|
95
|
+
"type": "api_error",
|
|
96
|
+
"message": "⚠️ API 返回异常响应,正在重试..."
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"id": "feishu-permission",
|
|
100
|
+
"match": "im:resource",
|
|
101
|
+
"action": "stop",
|
|
102
|
+
"type": "unknown",
|
|
103
|
+
"message": "❌ 权限不足,请联系管理员配置应用权限"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"id": "stream-error",
|
|
107
|
+
"match": "stream",
|
|
108
|
+
"action": "retry",
|
|
109
|
+
"type": "stream_error"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"id": "request-aborted",
|
|
113
|
+
"match": "request was aborted",
|
|
114
|
+
"action": "ignore",
|
|
115
|
+
"type": "stream_error"
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const BASEAGENT_CAPS = {
|
|
2
|
+
'claude-code': {
|
|
3
|
+
autoLoadsRules: true,
|
|
4
|
+
supportsSystemPrompt: true,
|
|
5
|
+
},
|
|
6
|
+
'claude': {
|
|
7
|
+
autoLoadsRules: true,
|
|
8
|
+
supportsSystemPrompt: true,
|
|
9
|
+
},
|
|
10
|
+
'codex': {
|
|
11
|
+
autoLoadsRules: false,
|
|
12
|
+
supportsSystemPrompt: true,
|
|
13
|
+
},
|
|
14
|
+
'gemini': {
|
|
15
|
+
autoLoadsRules: false,
|
|
16
|
+
supportsSystemPrompt: true,
|
|
17
|
+
},
|
|
18
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { BASEAGENT_CAPS } from './baseagent-caps.js';
|
|
4
|
+
const MAX_DEPTH = 5;
|
|
5
|
+
export function resolveEckInjection(agentConfig, projectPath, kitsRulesPath) {
|
|
6
|
+
const caps = BASEAGENT_CAPS[agentConfig.baseAgent]
|
|
7
|
+
?? { autoLoadsRules: false, supportsSystemPrompt: true };
|
|
8
|
+
if (!caps.autoLoadsRules) {
|
|
9
|
+
return { shouldInject: true, reason: 'baseagent-no-autoload' };
|
|
10
|
+
}
|
|
11
|
+
const symlinkActive = detectEckSymlink(projectPath, kitsRulesPath);
|
|
12
|
+
if (symlinkActive) {
|
|
13
|
+
return { shouldInject: false, reason: 'symlink-active' };
|
|
14
|
+
}
|
|
15
|
+
return { shouldInject: true, reason: 'symlink-not-found' };
|
|
16
|
+
}
|
|
17
|
+
export function detectEckSymlink(projectPath, kitsRulesPath) {
|
|
18
|
+
let dir = projectPath;
|
|
19
|
+
let depth = 0;
|
|
20
|
+
while (depth < MAX_DEPTH) {
|
|
21
|
+
const eckDir = path.join(dir, '.claude', 'rules', 'eck');
|
|
22
|
+
if (fs.existsSync(eckDir)) {
|
|
23
|
+
try {
|
|
24
|
+
const realPath = fs.realpathSync(eckDir);
|
|
25
|
+
const kitsRulesReal = fs.realpathSync(kitsRulesPath);
|
|
26
|
+
if (pathEquals(realPath, kitsRulesReal)) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// detection failure → conservatively assume not loaded
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const parent = path.dirname(dir);
|
|
35
|
+
if (parent === dir)
|
|
36
|
+
break;
|
|
37
|
+
dir = parent;
|
|
38
|
+
depth++;
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
function pathEquals(a, b) {
|
|
43
|
+
if (process.platform === 'win32') {
|
|
44
|
+
return path.resolve(a).toLowerCase() === path.resolve(b).toLowerCase();
|
|
45
|
+
}
|
|
46
|
+
return path.resolve(a) === path.resolve(b);
|
|
47
|
+
}
|
package/dist/eck/init.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { resolvePaths, kitsDocsDir, agentDir, getPackageRoot } from '../paths.js';
|
|
4
|
+
import { atomicWriteText } from '../utils/atomic-write.js';
|
|
5
|
+
import { logger } from '../utils/logger.js';
|
|
6
|
+
export function initEck() {
|
|
7
|
+
const p = resolvePaths();
|
|
8
|
+
const eckDir = p.eckDir;
|
|
9
|
+
fs.mkdirSync(eckDir, { recursive: true });
|
|
10
|
+
initEckRuntime(eckDir);
|
|
11
|
+
initEckPathRegistry(eckDir);
|
|
12
|
+
}
|
|
13
|
+
export function initAgentIndex(aid) {
|
|
14
|
+
const indexDir = path.join(agentDir(aid), 'index');
|
|
15
|
+
fs.mkdirSync(indexDir, { recursive: true });
|
|
16
|
+
const indexFile = path.join(indexDir, 'INDEX.md');
|
|
17
|
+
const guideFile = path.join(indexDir, 'GUIDE.md');
|
|
18
|
+
if (!fs.existsSync(indexFile)) {
|
|
19
|
+
const template = loadTemplate('INDEX.template.md');
|
|
20
|
+
if (template) {
|
|
21
|
+
atomicWriteText(indexFile, renderTemplate(template, { AID: aid }));
|
|
22
|
+
logger.info(`[eck] Created ${indexFile}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (!fs.existsSync(guideFile)) {
|
|
26
|
+
const template = loadTemplate('GUIDE.template.md');
|
|
27
|
+
if (template) {
|
|
28
|
+
atomicWriteText(guideFile, renderTemplate(template, { AID: aid }));
|
|
29
|
+
logger.info(`[eck] Created ${guideFile}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function initEckRuntime(eckDir) {
|
|
34
|
+
const runtimeFile = path.join(eckDir, 'runtime.md');
|
|
35
|
+
if (fs.existsSync(runtimeFile))
|
|
36
|
+
return;
|
|
37
|
+
const template = loadTemplate('runtime.template.md');
|
|
38
|
+
if (!template)
|
|
39
|
+
return;
|
|
40
|
+
const vars = {
|
|
41
|
+
EVOLCLAW_HOME: resolvePaths().root,
|
|
42
|
+
PACKAGE_ROOT: getPackageRoot(),
|
|
43
|
+
};
|
|
44
|
+
atomicWriteText(runtimeFile, renderTemplate(template, vars));
|
|
45
|
+
logger.info(`[eck] Created ${runtimeFile}`);
|
|
46
|
+
}
|
|
47
|
+
function initEckPathRegistry(eckDir) {
|
|
48
|
+
const registryFile = path.join(eckDir, 'path-registry.md');
|
|
49
|
+
if (fs.existsSync(registryFile))
|
|
50
|
+
return;
|
|
51
|
+
const template = loadTemplate('path-registry.template.md');
|
|
52
|
+
if (!template)
|
|
53
|
+
return;
|
|
54
|
+
const vars = {
|
|
55
|
+
EVOLCLAW_HOME: resolvePaths().root,
|
|
56
|
+
PACKAGE_ROOT: getPackageRoot(),
|
|
57
|
+
};
|
|
58
|
+
atomicWriteText(registryFile, renderTemplate(template, vars));
|
|
59
|
+
logger.info(`[eck] Created ${registryFile}`);
|
|
60
|
+
}
|
|
61
|
+
function loadTemplate(filename) {
|
|
62
|
+
const templatePath = path.join(kitsDocsDir(), 'eck_templates', filename);
|
|
63
|
+
try {
|
|
64
|
+
return fs.readFileSync(templatePath, 'utf-8');
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
logger.warn(`[eck] Template not found: ${templatePath}`);
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function renderTemplate(template, vars) {
|
|
72
|
+
let result = template;
|
|
73
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
74
|
+
result = result.replaceAll(`{{${key}}}`, value);
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { kitsRulesDir } from '../paths.js';
|
|
4
|
+
let _cachedRules = null;
|
|
5
|
+
export function loadRulesForInjection() {
|
|
6
|
+
if (_cachedRules !== null)
|
|
7
|
+
return _cachedRules;
|
|
8
|
+
const rulesDir = kitsRulesDir();
|
|
9
|
+
if (!fs.existsSync(rulesDir)) {
|
|
10
|
+
_cachedRules = '';
|
|
11
|
+
return '';
|
|
12
|
+
}
|
|
13
|
+
const files = fs.readdirSync(rulesDir)
|
|
14
|
+
.filter(f => f.endsWith('.md'))
|
|
15
|
+
.sort();
|
|
16
|
+
const parts = [];
|
|
17
|
+
for (const file of files) {
|
|
18
|
+
try {
|
|
19
|
+
parts.push(fs.readFileSync(path.join(rulesDir, file), 'utf-8'));
|
|
20
|
+
}
|
|
21
|
+
catch { /* skip unreadable files */ }
|
|
22
|
+
}
|
|
23
|
+
_cachedRules = parts.join('\n\n');
|
|
24
|
+
return _cachedRules;
|
|
25
|
+
}
|
|
26
|
+
export function invalidateRulesCache() {
|
|
27
|
+
_cachedRules = null;
|
|
28
|
+
}
|