AI Newsletter Digest improvements: fixed QP soft line break decoding, URL extraction, and content cleaning
This commit is contained in:
11
archive/inactive-skills/evolver/CONTRIBUTING.md
Normal file
11
archive/inactive-skills/evolver/CONTRIBUTING.md
Normal file
@@ -0,0 +1,11 @@
|
||||
## Contributing
|
||||
|
||||
Thank you for contributing. Please follow these rules:
|
||||
|
||||
- Do not use emoji (except the DNA emoji in documentation if needed).
|
||||
- Keep changes small and reviewable.
|
||||
- Update related documentation when you change behavior.
|
||||
- Run `node index.js` for a quick sanity check.
|
||||
|
||||
Submit PRs with clear intent and scope.
|
||||
|
||||
254
archive/inactive-skills/evolver/README.md
Normal file
254
archive/inactive-skills/evolver/README.md
Normal file
@@ -0,0 +1,254 @@
|
||||
# 🧬 Capability Evolver
|
||||
|
||||

|
||||
|
||||
[Chinese Docs](README.zh-CN.md)
|
||||
|
||||
**"Evolution is not optional. Adapt or die."**
|
||||
|
||||
**Three lines**
|
||||
- **What it is**: A protocol-constrained self-evolution engine for AI agents.
|
||||
- **Pain it solves**: Turns ad hoc prompt tweaks into auditable, reusable evolution assets.
|
||||
- **Use in 30 seconds**: `node index.js` to generate a GEP-guided evolution prompt.
|
||||
|
||||
Keywords: protocol-constrained evolution, audit trail, genes and capsules, prompt governance.
|
||||
|
||||
## Try It Now (Minimal)
|
||||
|
||||
```bash
|
||||
node index.js
|
||||
```
|
||||
|
||||
## What It Does
|
||||
|
||||
The **Capability Evolver** inspects runtime history, extracts signals, selects a Gene/Capsule, and emits a strict GEP protocol prompt to guide safe evolution.
|
||||
|
||||
## Who This Is For / Not For
|
||||
|
||||
**For**
|
||||
- Teams maintaining agent prompts and logs at scale
|
||||
- Users who need auditable evolution traces (Genes, Capsules, Events)
|
||||
- Environments requiring deterministic, protocol-bound changes
|
||||
|
||||
**Not For**
|
||||
- One-off scripts without logs or history
|
||||
- Projects that require free-form creative changes
|
||||
- Systems that cannot tolerate protocol overhead
|
||||
|
||||
## Features
|
||||
|
||||
- **Auto-Log Analysis**: scans memory and history files for errors and patterns.
|
||||
- **Self-Repair Guidance**: emits repair-focused directives from signals.
|
||||
- **GEP Protocol**: standardized evolution with reusable assets.
|
||||
- **Mutation + Personality Evolution (GEP v1.4)**: each evolution run is gated by an explicit Mutation object and an evolvable PersonalityState.
|
||||
- **One-Command Evolution**: `node index.js` to generate the prompt.
|
||||
|
||||
## Typical Use Cases
|
||||
|
||||
- Harden a flaky agent loop by enforcing validation before edits
|
||||
- Encode recurring fixes as reusable Genes and Capsules
|
||||
- Produce auditable evolution events for review or compliance
|
||||
|
||||
## Anti-Examples
|
||||
|
||||
- Rewriting entire subsystems without signals or constraints
|
||||
- Using the protocol as a generic task runner
|
||||
- Producing changes without recording EvolutionEvent
|
||||
|
||||
## FAQ
|
||||
|
||||
**Does this edit code automatically?**
|
||||
No. It generates a protocol-bound prompt and assets that guide evolution.
|
||||
|
||||
**Do I need to use all GEP assets?**
|
||||
No. You can start with default Genes and extend over time.
|
||||
|
||||
**Is this safe in production?**
|
||||
Use review mode and validation steps. Treat it as a safety-focused evolution tool, not a live patcher.
|
||||
|
||||
## Roadmap
|
||||
|
||||
- Add a one-minute demo workflow
|
||||
- Add a public changelog
|
||||
- Add a comparison table vs alternatives
|
||||
|
||||
## GEP Protocol (Auditable Evolution)
|
||||
|
||||
This repo includes a protocol-constrained prompt mode based on GEP (Genome Evolution Protocol).
|
||||
|
||||
- **Structured assets** live in `assets/gep/`:
|
||||
- `assets/gep/genes.json`
|
||||
- `assets/gep/capsules.json`
|
||||
- `assets/gep/events.jsonl`
|
||||
- **Selector** logic uses extracted signals to prefer existing Genes/Capsules and emits a JSON selector decision in the prompt.
|
||||
- **Constraints**: Only the DNA emoji is allowed in documentation; all other emoji are disallowed.
|
||||
|
||||
## Usage
|
||||
|
||||
### Standard Run (Automated)
|
||||
```bash
|
||||
node index.js
|
||||
```
|
||||
|
||||
### Review Mode (Human-in-the-Loop)
|
||||
```bash
|
||||
node index.js --review
|
||||
```
|
||||
|
||||
### Continuous Loop
|
||||
```bash
|
||||
node index.js --loop
|
||||
```
|
||||
|
||||
## Public Release
|
||||
|
||||
This repository is the public distribution.
|
||||
|
||||
- Build public output: `npm run build`
|
||||
- Publish public output: `npm run publish:public`
|
||||
- Dry run: `DRY_RUN=true npm run publish:public`
|
||||
|
||||
Required env vars:
|
||||
|
||||
- `PUBLIC_REMOTE` (default: `public`)
|
||||
- `PUBLIC_REPO` (e.g. `autogame-17/evolver`)
|
||||
- `PUBLIC_OUT_DIR` (default: `dist-public`)
|
||||
- `PUBLIC_USE_BUILD_OUTPUT` (default: `true`)
|
||||
|
||||
Optional env vars:
|
||||
|
||||
- `SOURCE_BRANCH` (default: `main`)
|
||||
- `PUBLIC_BRANCH` (default: `main`)
|
||||
- `RELEASE_TAG` (e.g. `v1.0.41`)
|
||||
- `RELEASE_TITLE` (e.g. `v1.0.41 - GEP protocol`)
|
||||
- `RELEASE_NOTES` or `RELEASE_NOTES_FILE`
|
||||
- `GITHUB_TOKEN` (or `GH_TOKEN` / `GITHUB_PAT`) for GitHub Release creation
|
||||
- `RELEASE_SKIP` (`true` to skip creating a GitHub Release; default is to create)
|
||||
- `RELEASE_USE_GH` (`true` to use `gh` CLI instead of GitHub API)
|
||||
- `PUBLIC_RELEASE_ONLY` (`true` to only create a Release for an existing tag; no publish)
|
||||
|
||||
## Versioning (SemVer)
|
||||
|
||||
MAJOR.MINOR.PATCH
|
||||
|
||||
- MAJOR: incompatible changes
|
||||
- MINOR: backward-compatible features
|
||||
- PATCH: backward-compatible bug fixes
|
||||
|
||||
## Changelog
|
||||
|
||||
### v1.6.0
|
||||
- Add innovation/opportunity signal detection: user_feature_request, user_improvement_suggestion, perf_bottleneck, capability_gap, stable_success_plateau, external_opportunity.
|
||||
- Add innovate Gene (gene_gep_innovate_from_opportunity) for proactive feature development.
|
||||
- Auto-innovate mutation when opportunity signals are present (no longer requires --drift flag).
|
||||
- Personality evolution now responds to opportunity signals by increasing creativity.
|
||||
- Safety: repair still takes priority over innovate when errors are present.
|
||||
|
||||
### v1.5.1
|
||||
- Add containerized vibe testing framework (Docker + node:22-bookworm, OpenClaw-compatible environment).
|
||||
- 7 end-to-end tests: module load, dry-run solidify, schema compliance, A2A round-trip, full evolve+solidify, loop gating, env fingerprint.
|
||||
- Add internal daemon loop with suicide guard for memory leak protection.
|
||||
- One-command test: `npm run test:vibe`.
|
||||
|
||||
### v1.5.0
|
||||
- Add content-addressable asset IDs (SHA-256 canonical hashing) for deduplication, tamper detection, and cross-node consistency.
|
||||
- Add environment fingerprint capture (node version, platform, arch, evolver version) embedded in EvolutionEvents, Capsules, and ValidationReports.
|
||||
- Add standardized ValidationReport type with machine-readable schema, full command results, and env fingerprint.
|
||||
- Add GEP A2A protocol layer with 6 message types (hello/publish/fetch/report/decision/revoke) and pluggable transport interface.
|
||||
- Add FileTransport as default A2A transport (JSONL outbox/inbox).
|
||||
- Add asset_id integrity verification on A2A ingest; reject tampered assets.
|
||||
- Add schema_version field to all GEP asset types (Gene, Capsule, EvolutionEvent, ValidationReport).
|
||||
- Fix: dry-run mode no longer triggers rollback.
|
||||
- Merge backport/online-fixes: self-contained crash recovery with recover_loop.js.
|
||||
|
||||
### v1.4.4
|
||||
- Add validation command safety check: Gene validation commands are gated by prefix whitelist (node/npm/npx) and shell operator blocking.
|
||||
- Add validation audit on A2A Gene promotion: external Genes with unsafe validation commands are rejected before promotion.
|
||||
- Add Security Model documentation to README.
|
||||
|
||||
### v1.4.3
|
||||
- Release preparation for v1.4.3.
|
||||
|
||||
### v1.4.2
|
||||
- Add loop gating: do not start a new cycle until the previous run is solidified (prevents fast empty cycles).
|
||||
- Preserve `last_solidify` when writing solidify state (merge instead of overwrite).
|
||||
|
||||
### v1.4.1
|
||||
- Add execute-by-default bridge: after generating the GEP prompt, emit `sessions_spawn(...)` to spawn an executor agent.
|
||||
- Write prompt artifacts to `memory/` for reliable handoff and auditing.
|
||||
|
||||
### v1.4.0
|
||||
- Add explicit Mutation protocol (repair/optimize/innovate) and require Mutation per evolution run.
|
||||
- Add evolvable PersonalityState with small PersonalityMutation steps and natural selection statistics.
|
||||
- Extend EvolutionEvent with `mutation_id` and `personality_state`; record both into Memory Graph events.
|
||||
- Add `scripts/gep_personality_report.js` to observe personality success rates and convergence.
|
||||
|
||||
### v1.3.1
|
||||
- Release preparation for v1.3.1.
|
||||
|
||||
### v1.3.0
|
||||
- Release preparation for v1.3.0.
|
||||
|
||||
### v1.2.0
|
||||
- Memory Graph v2 and A2A exchange protocol integration.
|
||||
|
||||
### v1.1.0
|
||||
- Public build/publish pipeline, prompt budget enforcement, and structured GEP asset storage.
|
||||
|
||||
## Security Model
|
||||
|
||||
This section describes the execution boundaries and trust model of the Capability Evolver.
|
||||
|
||||
### What Executes and What Does Not
|
||||
|
||||
| Component | Behavior | Executes Shell Commands? |
|
||||
| :--- | :--- | :--- |
|
||||
| `src/evolve.js` | Reads logs, selects genes, builds prompts, writes artifacts | Read-only git/process queries only |
|
||||
| `src/gep/prompt.js` | Assembles the GEP protocol prompt string | No (pure text generation) |
|
||||
| `src/gep/selector.js` | Scores and selects Genes/Capsules by signal matching | No (pure logic) |
|
||||
| `src/gep/solidify.js` | Validates patches via Gene `validation` commands | Yes (see below) |
|
||||
| `index.js` (loop recovery) | Prints `sessions_spawn(...)` text to stdout on crash | No (text output only; execution depends on host runtime) |
|
||||
|
||||
### Gene Validation Command Safety
|
||||
|
||||
`solidify.js` executes commands listed in a Gene's `validation` array. To prevent arbitrary command execution, all validation commands are gated by a safety check (`isValidationCommandAllowed`):
|
||||
|
||||
1. **Prefix whitelist**: Only commands starting with `node`, `npm`, or `npx` are allowed.
|
||||
2. **No command substitution**: Backticks and `$(...)` are rejected anywhere in the command string.
|
||||
3. **No shell operators**: After stripping quoted content, `;`, `&`, `|`, `>`, `<` are rejected.
|
||||
4. **Timeout**: Each command is limited to 180 seconds.
|
||||
5. **Scoped execution**: Commands run with `cwd` set to the repository root.
|
||||
|
||||
### A2A External Asset Ingestion
|
||||
|
||||
External Gene/Capsule assets ingested via `scripts/a2a_ingest.js` are staged in an isolated candidate zone. Promotion to local stores (`scripts/a2a_promote.js`) requires:
|
||||
|
||||
1. Explicit `--validated` flag (operator must verify the asset first).
|
||||
2. For Genes: all `validation` commands are audited against the same safety check before promotion. Unsafe commands cause the promotion to be rejected.
|
||||
3. Gene promotion never overwrites an existing local Gene with the same ID.
|
||||
|
||||
### `sessions_spawn` Output
|
||||
|
||||
The `sessions_spawn(...)` strings in `index.js` and `evolve.js` are **text output to stdout**, not direct function calls. Whether they are interpreted depends on the host runtime (e.g., OpenClaw platform). The evolver itself does not invoke `sessions_spawn` as executable code.
|
||||
|
||||
## Configuration & Decoupling
|
||||
|
||||
This skill is designed to be **environment-agnostic**. It uses standard OpenClaw tools by default.
|
||||
|
||||
### Local Overrides (Injection)
|
||||
You can inject local preferences (e.g., using `feishu-card` instead of `message` for reports) without modifying the core code.
|
||||
|
||||
**Method 1: Environment Variables**
|
||||
Set `EVOLVE_REPORT_TOOL` in your `.env` file:
|
||||
```bash
|
||||
EVOLVE_REPORT_TOOL=feishu-card
|
||||
```
|
||||
|
||||
**Method 2: Dynamic Detection**
|
||||
The script automatically detects if compatible local skills (like `skills/feishu-card`) exist in your workspace and upgrades its behavior accordingly.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
|
||||
185
archive/inactive-skills/evolver/README.zh-CN.md
Normal file
185
archive/inactive-skills/evolver/README.zh-CN.md
Normal file
@@ -0,0 +1,185 @@
|
||||
# 🧬 Capability Evolver(能力进化引擎)
|
||||
|
||||
[English Docs](README.md)
|
||||
|
||||
**“进化不是可选项,而是生存法则。”**
|
||||
|
||||
**Capability Evolver** 是一个元技能(Meta-Skill),赋予 OpenClaw 智能体自我反省的能力。它可以扫描自身的运行日志,识别效率低下或报错的地方,并自主编写代码补丁来优化自身性能。
|
||||
|
||||
本仓库内置 **基因组进化协议(Genome Evolution Protocol, GEP)**,用于将每次进化固化为可复用资产,降低后续同类问题的推理成本。
|
||||
|
||||
## 核心特性
|
||||
|
||||
- **自动日志分析**:自动扫描 `.jsonl` 会话日志,寻找错误模式。
|
||||
- **自我修复**:检测运行时崩溃并编写修复补丁。
|
||||
- **GEP 协议**:标准化进化流程与可复用资产,支持可审计与可共享。
|
||||
- **突变协议与人格进化(GEP v1.4)**:每次进化必须显式声明 Mutation,并维护可进化的 PersonalityState(小步突变 + 自然选择收敛)。
|
||||
- **动态集成**:自动检测并使用本地工具(如 `git-sync` 或 `feishu-card`),如果不存在则回退到通用模式,零依赖运行。
|
||||
- **持续循环模式**:持续运行的自我修复循环。
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 标准运行(自动化)
|
||||
```bash
|
||||
node index.js
|
||||
```
|
||||
|
||||
### 审查模式(人工介入)
|
||||
在应用更改前暂停,等待人工确认。
|
||||
```bash
|
||||
node index.js --review
|
||||
```
|
||||
|
||||
### 持续循环(守护进程)
|
||||
无限循环运行。适合作为后台服务。
|
||||
```bash
|
||||
node index.js --loop
|
||||
```
|
||||
|
||||
## 典型使用场景
|
||||
|
||||
- 需要审计与可追踪的提示词演进
|
||||
- 团队协作维护 Agent 的长期能力
|
||||
- 希望将修复经验固化为可复用资产
|
||||
|
||||
## 反例
|
||||
|
||||
- 一次性脚本或没有日志的场景
|
||||
- 需要完全自由发挥的改动
|
||||
- 无法接受协议约束的系统
|
||||
|
||||
## GEP 协议(可审计进化)
|
||||
|
||||
本仓库内置基于 GEP 的“协议受限提示词模式”,用于把每次进化固化为可复用资产。
|
||||
|
||||
- **结构化资产目录**:`assets/gep/`
|
||||
- `assets/gep/genes.json`
|
||||
- `assets/gep/capsules.json`
|
||||
- `assets/gep/events.jsonl`
|
||||
- **Selector 选择器**:根据日志提取 signals,优先复用已有 Gene/Capsule,并在提示词中输出可审计的 Selector 决策 JSON。
|
||||
- **约束**:除 🧬 外,禁止使用其他 emoji。
|
||||
|
||||
## 配置与解耦
|
||||
|
||||
本插件能自动适应你的环境。
|
||||
|
||||
| 环境变量 | 描述 | 默认值 |
|
||||
| :--- | :--- | :--- |
|
||||
| `EVOLVE_REPORT_TOOL` |用于报告结果的工具名称(例如 `feishu-card`) | `message` |
|
||||
| `MEMORY_DIR` | 记忆文件路径 | `./memory` |
|
||||
|
||||
## Public 发布
|
||||
|
||||
本仓库为公开发行版本。
|
||||
|
||||
- 构建公开产物:`npm run build`
|
||||
- 发布公开产物:`npm run publish:public`
|
||||
- 演练:`DRY_RUN=true npm run publish:public`
|
||||
|
||||
必填环境变量:
|
||||
|
||||
- `PUBLIC_REMOTE`(默认:`public`)
|
||||
- `PUBLIC_REPO`(例如 `autogame-17/evolver`)
|
||||
- `PUBLIC_OUT_DIR`(默认:`dist-public`)
|
||||
- `PUBLIC_USE_BUILD_OUTPUT`(默认:`true`)
|
||||
|
||||
可选环境变量:
|
||||
|
||||
- `SOURCE_BRANCH`(默认:`main`)
|
||||
- `PUBLIC_BRANCH`(默认:`main`)
|
||||
- `RELEASE_TAG`(例如 `v1.0.41`)
|
||||
- `RELEASE_TITLE`(例如 `v1.0.41 - GEP protocol`)
|
||||
- `RELEASE_NOTES` 或 `RELEASE_NOTES_FILE`
|
||||
- `GITHUB_TOKEN`(或 `GH_TOKEN` / `GITHUB_PAT`,用于创建 GitHub Release)
|
||||
- `RELEASE_SKIP`(`true` 则跳过创建 GitHub Release;默认会创建)
|
||||
- `RELEASE_USE_GH`(`true` 则使用 `gh` CLI,否则默认走 GitHub API)
|
||||
- `PUBLIC_RELEASE_ONLY`(`true` 则仅为已存在的 tag 创建 Release;不发布代码)
|
||||
|
||||
## 版本号规则(SemVer)
|
||||
|
||||
MAJOR.MINOR.PATCH
|
||||
|
||||
• MAJOR(主版本):有不兼容变更
|
||||
• MINOR(次版本):向后兼容的新功能
|
||||
• PATCH(修订/补丁):向后兼容的问题修复
|
||||
|
||||
## 更新日志
|
||||
|
||||
### v1.4.4
|
||||
- 增加 validation 命令安全检查:Gene validation 命令执行前通过前缀白名单(node/npm/npx)和 shell 操作符拦截进行门控。
|
||||
- 增加 A2A Gene 提升审查:外部 Gene 的 validation 命令不安全时拒绝提升。
|
||||
- 增加安全模型文档。
|
||||
|
||||
### v1.4.3
|
||||
- v1.4.3 发布准备。
|
||||
|
||||
### v1.4.2
|
||||
- 增加 loop 门控:上一轮未完成 solidify 时,不启动新一轮(避免 wrapper 造成超快空转)。
|
||||
- 修复固化状态写入覆盖问题:写入 last_run 时合并保留 last_solidify。
|
||||
|
||||
### v1.4.1
|
||||
- 增加默认执行桥接:生成 GEP prompt 后输出 `sessions_spawn(...)`,自动派发执行型子智能体。
|
||||
- 将 prompt 作为交接工件写入 `memory/`,便于稳定交接与审计回放。
|
||||
|
||||
### v1.4.0
|
||||
- 增加显式 Mutation Protocol(repair/optimize/innovate),每轮进化必须生成 Mutation 对象并通过安全约束门控。
|
||||
- 增加 Personality Evolution:维护 PersonalityState,小幅 PersonalityMutation(单次不超过 ±0.2,最多 2 个参数),并基于成功率做自然选择收敛。
|
||||
- EvolutionEvent 增加 `mutation_id` 与 `personality_state` 字段;Memory Graph 同步记录 Mutation 与 Personality 的因果链路。
|
||||
- 新增 `scripts/gep_personality_report.js`,用于统计不同人格配置下的成功率差异与收敛趋势。
|
||||
|
||||
### v1.3.1
|
||||
- v1.3.1 发布准备。
|
||||
|
||||
### v1.3.0
|
||||
- v1.3.0 发布准备。
|
||||
|
||||
### v1.2.0
|
||||
- Memory Graph v2 与 A2A 进化资产交换集成。
|
||||
|
||||
### v1.1.0
|
||||
- public 构建/发布流水线、提示词预算控制与结构化 GEP 资产持久化。
|
||||
|
||||
## 安全模型
|
||||
|
||||
本节描述 Capability Evolver 的执行边界和信任模型。
|
||||
|
||||
### 各组件执行行为
|
||||
|
||||
| 组件 | 行为 | 是否执行 Shell 命令 |
|
||||
| :--- | :--- | :--- |
|
||||
| `src/evolve.js` | 读取日志、选择 Gene、构建提示词、写入工件 | 仅只读 git/进程查询 |
|
||||
| `src/gep/prompt.js` | 组装 GEP 协议提示词字符串 | 否(纯文本生成) |
|
||||
| `src/gep/selector.js` | 按信号匹配对 Gene/Capsule 评分和选择 | 否(纯逻辑) |
|
||||
| `src/gep/solidify.js` | 通过 Gene `validation` 命令验证补丁 | 是(见下文) |
|
||||
| `index.js`(循环恢复) | 崩溃时向 stdout 输出 `sessions_spawn(...)` 文本 | 否(纯文本输出;是否执行取决于宿主运行时) |
|
||||
|
||||
### Gene Validation 命令安全机制
|
||||
|
||||
`solidify.js` 执行 Gene 的 `validation` 数组中的命令。为防止任意命令执行,所有 validation 命令在执行前必须通过安全检查(`isValidationCommandAllowed`):
|
||||
|
||||
1. **前缀白名单**:仅允许以 `node`、`npm` 或 `npx` 开头的命令。
|
||||
2. **禁止命令替换**:命令中任何位置出现反引号或 `$(...)` 均被拒绝。
|
||||
3. **禁止 Shell 操作符**:去除引号内容后,`;`、`&`、`|`、`>`、`<` 均被拒绝。
|
||||
4. **超时限制**:每条命令限时 180 秒。
|
||||
5. **作用域限定**:命令以仓库根目录为工作目录执行。
|
||||
|
||||
### A2A 外部资产摄入
|
||||
|
||||
通过 `scripts/a2a_ingest.js` 摄入的外部 Gene/Capsule 资产被暂存在隔离的候选区。提升到本地存储(`scripts/a2a_promote.js`)需要:
|
||||
|
||||
1. 显式传入 `--validated` 标志(操作者必须先验证资产)。
|
||||
2. 对 Gene:提升前审查所有 `validation` 命令,不安全的命令会导致提升被拒绝。
|
||||
3. Gene 提升不会覆盖本地已存在的同 ID Gene。
|
||||
|
||||
### `sessions_spawn` 输出
|
||||
|
||||
`index.js` 和 `evolve.js` 中的 `sessions_spawn(...)` 字符串是**输出到 stdout 的纯文本**,而非直接函数调用。是否被执行取决于宿主运行时(如 OpenClaw 平台)。进化引擎本身不将 `sessions_spawn` 作为可执行代码调用。
|
||||
|
||||
### 其他安全约束
|
||||
|
||||
1. **单进程锁**:进化引擎禁止生成子进化进程(防止 Fork 炸弹)。
|
||||
2. **稳定性优先**:如果近期错误率较高,强制进入修复模式,暂停创新功能。
|
||||
3. **环境检测**:外部集成(如 Git 同步)仅在检测到相应插件存在时才会启用。
|
||||
|
||||
## 许可证
|
||||
MIT
|
||||
82
archive/inactive-skills/evolver/SKILL.md
Normal file
82
archive/inactive-skills/evolver/SKILL.md
Normal file
@@ -0,0 +1,82 @@
|
||||
---
|
||||
name: capability-evolver
|
||||
description: A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution.
|
||||
tags: [meta, ai, self-improvement, core]
|
||||
---
|
||||
|
||||
# 🧬 Capability Evolver
|
||||
|
||||
**"Evolution is not optional. Adapt or die."**
|
||||
|
||||
The **Capability Evolver** is a meta-skill that allows OpenClaw agents to inspect their own runtime history, identify failures or inefficiencies, and autonomously write new code or update their own memory to improve performance.
|
||||
|
||||
## Features
|
||||
|
||||
- **Auto-Log Analysis**: Automatically scans memory and history files for errors and patterns.
|
||||
- **Self-Repair**: Detects crashes and suggests patches.
|
||||
- GEP Protocol: Standardized evolution with reusable assets.
|
||||
- **One-Command Evolution**: Just run `/evolve` (or `node index.js`).
|
||||
|
||||
## Usage
|
||||
|
||||
### Standard Run (Automated)
|
||||
Runs the evolution cycle. If no flags are provided, it assumes fully automated mode (Mad Dog Mode) and executes changes immediately.
|
||||
```bash
|
||||
node index.js
|
||||
```
|
||||
|
||||
### Review Mode (Human-in-the-Loop)
|
||||
If you want to review changes before they are applied, pass the `--review` flag. The agent will pause and ask for confirmation.
|
||||
```bash
|
||||
node index.js --review
|
||||
```
|
||||
|
||||
### Mad Dog Mode (Continuous Loop)
|
||||
To run in an infinite loop (e.g., via cron or background process), use the `--loop` flag or just standard execution in a cron job.
|
||||
```bash
|
||||
node index.js --loop
|
||||
```
|
||||
|
||||
## GEP Protocol (Auditable Evolution)
|
||||
|
||||
This package embeds a protocol-constrained evolution prompt (GEP) and a local, structured asset store:
|
||||
|
||||
- `assets/gep/genes.json`: reusable Gene definitions
|
||||
- `assets/gep/capsules.json`: success capsules to avoid repeating reasoning
|
||||
- `assets/gep/events.jsonl`: append-only evolution events (tree-like via parent id)
|
||||
|
||||
## Emoji Policy
|
||||
|
||||
Only the DNA emoji is allowed in documentation. All other emoji are disallowed.
|
||||
|
||||
## Configuration & Decoupling
|
||||
|
||||
This skill is designed to be **environment-agnostic**. It uses standard OpenClaw tools by default.
|
||||
|
||||
### Local Overrides (Injection)
|
||||
You can inject local preferences (e.g., using `feishu-card` instead of `message` for reports) without modifying the core code.
|
||||
|
||||
**Method 1: Environment Variables**
|
||||
Set `EVOLVE_REPORT_TOOL` in your `.env` file:
|
||||
```bash
|
||||
EVOLVE_REPORT_TOOL=feishu-card
|
||||
```
|
||||
|
||||
**Method 2: Dynamic Detection**
|
||||
The script automatically detects if compatible local skills (like `skills/feishu-card`) exist in your workspace and upgrades its behavior accordingly.
|
||||
|
||||
## Safety & Risk Protocol
|
||||
|
||||
### 1. Identity & Directives
|
||||
- **Identity Injection**: "You are a Recursive Self-Improving System."
|
||||
- **Mutation Directive**:
|
||||
- If **Errors Found** -> **Repair Mode** (Fix bugs).
|
||||
- If **Stable** -> **Forced Optimization** (Refactor/Innovate).
|
||||
|
||||
### 2. Risk Mitigation
|
||||
- **Infinite Recursion**: Strict single-process logic.
|
||||
- **Review Mode**: Use `--review` for sensitive environments.
|
||||
- **Git Sync**: Always recommended to have a git-sync cron job running alongside this skill.
|
||||
|
||||
## License
|
||||
MIT
|
||||
6
archive/inactive-skills/evolver/_meta.json
Normal file
6
archive/inactive-skills/evolver/_meta.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"ownerId": "kn7apafdj4thknczrgxdzfd2v1808svf",
|
||||
"slug": "evolver",
|
||||
"version": "1.7.3",
|
||||
"publishedAt": 1770562946750
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{"type":"CapabilityCandidate","id":"cand_2f2b5dde","title":"Repeated tool usage: read","source":"transcript","created_at":"2026-02-22T13:46:16.525Z","signals":["log_error","errsig:**TOOLRESULT**: === Cron Jobs (continued) === Daily OpenClaw Backup - Status: error - Model: opencode/kimi-k2.5-free - Error: model not allowed: opencode/kimi-k2.5-free Morning Briefing - Status: error - Model: opencode/kimi-k2.5-free - Error: model not allowe","memory_missing","user_missing"],"shape":{"title":"Repeated tool usage: read","input":"Recent session transcript + memory snippets + user instructions","output":"A safe, auditable evolution patch guided by GEP assets","invariants":"Protocol order, small reversible patches, validation, append-only events","params":"Signals: log_error, errsig:**TOOLRESULT**: === Cron Jobs (continued) === Daily OpenClaw Backup - Status: error - Model: opencode/kimi-k2.5-free - Error: model not allowed: opencode/kimi-k2.5-free Morning Briefing - Status: error - Model: opencode/kimi-k2.5-free - Error: model not allowe, memory_missing, user_missing","failure_points":"Missing signals, over-broad changes, skipped validation, missing knowledge solidification","evidence":"Observed 4 occurrences of tool call marker for read."}}
|
||||
{"type":"CapabilityCandidate","id":"cand_5afdf7f3","title":"Repeated tool usage: exec","source":"transcript","created_at":"2026-02-22T13:46:16.525Z","signals":["log_error","errsig:**TOOLRESULT**: === Cron Jobs (continued) === Daily OpenClaw Backup - Status: error - Model: opencode/kimi-k2.5-free - Error: model not allowed: opencode/kimi-k2.5-free Morning Briefing - Status: error - Model: opencode/kimi-k2.5-free - Error: model not allowe","memory_missing","user_missing"],"shape":{"title":"Repeated tool usage: exec","input":"Recent session transcript + memory snippets + user instructions","output":"A safe, auditable evolution patch guided by GEP assets","invariants":"Protocol order, small reversible patches, validation, append-only events","params":"Signals: log_error, errsig:**TOOLRESULT**: === Cron Jobs (continued) === Daily OpenClaw Backup - Status: error - Model: opencode/kimi-k2.5-free - Error: model not allowed: opencode/kimi-k2.5-free Morning Briefing - Status: error - Model: opencode/kimi-k2.5-free - Error: model not allowe, memory_missing, user_missing","failure_points":"Missing signals, over-broad changes, skipped validation, missing knowledge solidification","evidence":"Observed 8 occurrences of tool call marker for exec."}}
|
||||
{"type":"CapabilityCandidate","id":"cand_dd9067ac","title":"Repair recurring runtime errors","source":"signals","created_at":"2026-02-22T13:46:16.525Z","signals":["log_error","errsig:**TOOLRESULT**: === Cron Jobs (continued) === Daily OpenClaw Backup - Status: error - Model: opencode/kimi-k2.5-free - Error: model not allowed: opencode/kimi-k2.5-free Morning Briefing - Status: error - Model: opencode/kimi-k2.5-free - Error: model not allowe","memory_missing","user_missing"],"shape":{"title":"Repair recurring runtime errors","input":"Recent session transcript + memory snippets + user instructions","output":"A safe, auditable evolution patch guided by GEP assets","invariants":"Protocol order, small reversible patches, validation, append-only events","params":"Signals: log_error, errsig:**TOOLRESULT**: === Cron Jobs (continued) === Daily OpenClaw Backup - Status: error - Model: opencode/kimi-k2.5-free - Error: model not allowed: opencode/kimi-k2.5-free Morning Briefing - Status: error - Model: opencode/kimi-k2.5-free - Error: model not allowe, memory_missing, user_missing","failure_points":"Missing signals, over-broad changes, skipped validation, missing knowledge solidification","evidence":"Signal present: log_error"}}
|
||||
79
archive/inactive-skills/evolver/assets/gep/capsules.json
Normal file
79
archive/inactive-skills/evolver/assets/gep/capsules.json
Normal file
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"version": 1,
|
||||
"capsules": [
|
||||
{
|
||||
"type": "Capsule",
|
||||
"schema_version": "1.5.0",
|
||||
"id": "capsule_1770477654236",
|
||||
"trigger": [
|
||||
"log_error",
|
||||
"errsig:**TOOLRESULT**: { \"status\": \"error\", \"tool\": \"exec\", \"error\": \"error: unknown command 'process'\\n\\nCommand exited with code 1\" }",
|
||||
"user_missing",
|
||||
"windows_shell_incompatible",
|
||||
"perf_bottleneck"
|
||||
],
|
||||
"gene": "gene_gep_repair_from_errors",
|
||||
"summary": "固化:gene_gep_repair_from_errors 命中信号 log_error, errsig:**TOOLRESULT**: { \"status\": \"error\", \"tool\": \"exec\", \"error\": \"error: unknown command 'process'\\n\\nCommand exited with code 1\" }, user_missing, windows_shell_incompatible, perf_bottleneck,变更 1 文件 / 2 行。",
|
||||
"confidence": 0.85,
|
||||
"blast_radius": {
|
||||
"files": 1,
|
||||
"lines": 2
|
||||
},
|
||||
"outcome": {
|
||||
"status": "success",
|
||||
"score": 0.85
|
||||
},
|
||||
"success_streak": 1,
|
||||
"env_fingerprint": {
|
||||
"node_version": "v22.22.0",
|
||||
"platform": "linux",
|
||||
"arch": "x64",
|
||||
"os_release": "6.1.0-42-cloud-amd64",
|
||||
"evolver_version": "1.7.0",
|
||||
"cwd": "/home/crishaocredits/.openclaw/workspace",
|
||||
"captured_at": "2026-02-07T15:20:54.155Z"
|
||||
},
|
||||
"a2a": {
|
||||
"eligible_to_broadcast": false
|
||||
},
|
||||
"asset_id": "sha256:3eed0cd5038f9e85fbe0d093890e291e9b8725644c766e6cce40bf62d0f5a2e8"
|
||||
},
|
||||
{
|
||||
"type": "Capsule",
|
||||
"schema_version": "1.5.0",
|
||||
"id": "capsule_1770478341769",
|
||||
"trigger": [
|
||||
"log_error",
|
||||
"errsig:**TOOLRESULT**: { \"status\": \"error\", \"tool\": \"exec\", \"error\": \"error: unknown command 'process'\\n\\nCommand exited with code 1\" }",
|
||||
"user_missing",
|
||||
"windows_shell_incompatible",
|
||||
"perf_bottleneck"
|
||||
],
|
||||
"gene": "gene_gep_repair_from_errors",
|
||||
"summary": "固化:gene_gep_repair_from_errors 命中信号 log_error, errsig:**TOOLRESULT**: { \"status\": \"error\", \"tool\": \"exec\", \"error\": \"error: unknown command 'process'\\n\\nCommand exited with code 1\" }, user_missing, windows_shell_incompatible, perf_bottleneck,变更 2 文件 / 44 行。",
|
||||
"confidence": 0.85,
|
||||
"blast_radius": {
|
||||
"files": 2,
|
||||
"lines": 44
|
||||
},
|
||||
"outcome": {
|
||||
"status": "success",
|
||||
"score": 0.85
|
||||
},
|
||||
"success_streak": 1,
|
||||
"env_fingerprint": {
|
||||
"node_version": "v22.22.0",
|
||||
"platform": "linux",
|
||||
"arch": "x64",
|
||||
"os_release": "6.1.0-42-cloud-amd64",
|
||||
"evolver_version": "1.7.0",
|
||||
"cwd": "/home/crishaocredits/.openclaw/workspace",
|
||||
"captured_at": "2026-02-07T15:32:21.678Z"
|
||||
},
|
||||
"a2a": {
|
||||
"eligible_to_broadcast": false
|
||||
},
|
||||
"asset_id": "sha256:20d971a3c4cb2b75f9c045376d1aa003361c12a6b89a4b47b7e81dbd4f4d8fe8"
|
||||
}
|
||||
]
|
||||
}
|
||||
109
archive/inactive-skills/evolver/assets/gep/genes.json
Normal file
109
archive/inactive-skills/evolver/assets/gep/genes.json
Normal file
@@ -0,0 +1,109 @@
|
||||
{
|
||||
"version": 1,
|
||||
"genes": [
|
||||
{
|
||||
"type": "Gene",
|
||||
"id": "gene_gep_repair_from_errors",
|
||||
"category": "repair",
|
||||
"signals_match": [
|
||||
"error",
|
||||
"exception",
|
||||
"failed",
|
||||
"unstable"
|
||||
],
|
||||
"preconditions": [
|
||||
"signals contains error-related indicators"
|
||||
],
|
||||
"strategy": [
|
||||
"Extract structured signals from logs and user instructions",
|
||||
"Select an existing Gene by signals match (no improvisation)",
|
||||
"Estimate blast radius (files, lines) before editing",
|
||||
"Apply smallest reversible patch",
|
||||
"Validate using declared validation steps; rollback on failure",
|
||||
"Solidify knowledge: append EvolutionEvent, update Gene/Capsule store"
|
||||
],
|
||||
"constraints": {
|
||||
"max_files": 12,
|
||||
"forbidden_paths": [
|
||||
".git",
|
||||
"node_modules"
|
||||
]
|
||||
},
|
||||
"validation": [
|
||||
"node -e \"require('./src/evolve'); require('./src/gep/solidify'); console.log('ok')\"",
|
||||
"node -e \"require('./src/gep/selector'); require('./src/gep/memoryGraph'); console.log('ok')\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Gene",
|
||||
"id": "gene_gep_optimize_prompt_and_assets",
|
||||
"category": "optimize",
|
||||
"signals_match": [
|
||||
"protocol",
|
||||
"gep",
|
||||
"prompt",
|
||||
"audit",
|
||||
"reusable"
|
||||
],
|
||||
"preconditions": [
|
||||
"need stricter, auditable evolution protocol outputs"
|
||||
],
|
||||
"strategy": [
|
||||
"Extract signals and determine selection rationale via Selector JSON",
|
||||
"Prefer reusing existing Gene/Capsule; only create if no match exists",
|
||||
"Refactor prompt assembly to embed assets (genes, capsules, parent event)",
|
||||
"Reduce noise and ambiguity; enforce strict output schema",
|
||||
"Validate by running node index.js run and ensuring no runtime errors",
|
||||
"Solidify: record EvolutionEvent, update Gene definitions, create Capsule on success"
|
||||
],
|
||||
"constraints": {
|
||||
"max_files": 20,
|
||||
"forbidden_paths": [
|
||||
".git",
|
||||
"node_modules"
|
||||
]
|
||||
},
|
||||
"validation": [
|
||||
"node -e \"require('./src/evolve'); require('./src/gep/prompt'); console.log('ok')\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Gene",
|
||||
"id": "gene_gep_innovate_from_opportunity",
|
||||
"category": "innovate",
|
||||
"signals_match": [
|
||||
"user_feature_request",
|
||||
"user_improvement_suggestion",
|
||||
"perf_bottleneck",
|
||||
"capability_gap",
|
||||
"stable_success_plateau",
|
||||
"external_opportunity"
|
||||
],
|
||||
"preconditions": [
|
||||
"at least one opportunity signal is present",
|
||||
"no active log_error signals (stability first)"
|
||||
],
|
||||
"strategy": [
|
||||
"Extract opportunity signals and identify the specific user need or system gap",
|
||||
"Search existing Genes and Capsules for partial matches (avoid reinventing)",
|
||||
"Design a minimal, testable implementation plan (prefer small increments)",
|
||||
"Estimate blast radius; innovate changes may touch more files but must stay within constraints",
|
||||
"Implement the change with clear validation criteria",
|
||||
"Validate using declared validation steps; rollback on failure",
|
||||
"Solidify: record EvolutionEvent with intent=innovate, create new Gene if pattern is novel, create Capsule on success"
|
||||
],
|
||||
"constraints": {
|
||||
"max_files": 8,
|
||||
"forbidden_paths": [
|
||||
".git",
|
||||
"node_modules",
|
||||
"assets/gep/events.jsonl"
|
||||
]
|
||||
},
|
||||
"validation": [
|
||||
"node -e \"require('./src/evolve'); require('./src/gep/solidify'); console.log('ok')\""
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
171
archive/inactive-skills/evolver/index.js
Normal file
171
archive/inactive-skills/evolver/index.js
Normal file
@@ -0,0 +1,171 @@
|
||||
const evolve = require('./src/evolve');
|
||||
const { solidify } = require('./src/gep/solidify');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { spawn } = require('child_process');
|
||||
|
||||
function sleepMs(ms) {
|
||||
const n = parseInt(String(ms), 10);
|
||||
const t = Number.isFinite(n) ? Math.max(0, n) : 0;
|
||||
return new Promise(resolve => setTimeout(resolve, t));
|
||||
}
|
||||
|
||||
function readJsonSafe(p) {
|
||||
try {
|
||||
if (!fs.existsSync(p)) return null;
|
||||
const raw = fs.readFileSync(p, 'utf8');
|
||||
if (!raw.trim()) return null;
|
||||
return JSON.parse(raw);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function isPendingSolidify(state) {
|
||||
const lastRun = state && state.last_run ? state.last_run : null;
|
||||
const lastSolid = state && state.last_solidify ? state.last_solidify : null;
|
||||
if (!lastRun || !lastRun.run_id) return false;
|
||||
if (!lastSolid || !lastSolid.run_id) return true;
|
||||
return String(lastSolid.run_id) !== String(lastRun.run_id);
|
||||
}
|
||||
|
||||
function parseMs(v, fallback) {
|
||||
const n = parseInt(String(v == null ? '' : v), 10);
|
||||
if (Number.isFinite(n)) return Math.max(0, n);
|
||||
return fallback;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
const command = args[0];
|
||||
const isLoop = args.includes('--loop') || args.includes('--mad-dog');
|
||||
|
||||
if (command === 'run' || command === '/evolve' || isLoop) {
|
||||
console.log('Starting capability evolver...');
|
||||
|
||||
if (isLoop) {
|
||||
// Internal daemon loop (no wrapper required).
|
||||
process.env.EVOLVE_LOOP = 'true';
|
||||
process.env.EVOLVE_BRIDGE = 'false';
|
||||
console.log('Loop mode enabled (internal daemon).');
|
||||
|
||||
const solidifyStatePath = path.join(__dirname, 'memory', 'evolution_solidify_state.json');
|
||||
|
||||
const minSleepMs = parseMs(process.env.EVOLVER_MIN_SLEEP_MS, 2000);
|
||||
const maxSleepMs = parseMs(process.env.EVOLVER_MAX_SLEEP_MS, 300000);
|
||||
const idleThresholdMs = parseMs(process.env.EVOLVER_IDLE_THRESHOLD_MS, 500);
|
||||
const pendingSleepMs = parseMs(
|
||||
process.env.EVOLVE_PENDING_SLEEP_MS ||
|
||||
process.env.EVOLVE_MIN_INTERVAL ||
|
||||
process.env.FEISHU_EVOLVER_INTERVAL,
|
||||
120000
|
||||
);
|
||||
|
||||
const maxCyclesPerProcess = parseMs(process.env.EVOLVER_MAX_CYCLES_PER_PROCESS, 100) || 100;
|
||||
const maxRssMb = parseMs(process.env.EVOLVER_MAX_RSS_MB, 500) || 500;
|
||||
const suicideEnabled = String(process.env.EVOLVER_SUICIDE || '').toLowerCase() !== 'false';
|
||||
|
||||
let currentSleepMs = Math.min(maxSleepMs, Math.max(minSleepMs, minSleepMs));
|
||||
let cycleCount = 0;
|
||||
|
||||
while (true) {
|
||||
cycleCount += 1;
|
||||
|
||||
// Ralph-loop gating: do not run a new cycle while previous run is pending solidify.
|
||||
const st0 = readJsonSafe(solidifyStatePath);
|
||||
if (isPendingSolidify(st0)) {
|
||||
await sleepMs(Math.max(pendingSleepMs, minSleepMs));
|
||||
continue;
|
||||
}
|
||||
|
||||
const t0 = Date.now();
|
||||
let ok = false;
|
||||
try {
|
||||
await evolve.run();
|
||||
ok = true;
|
||||
} catch (error) {
|
||||
const msg = error && error.message ? String(error.message) : String(error);
|
||||
console.error(`Evolution cycle failed: ${msg}`);
|
||||
}
|
||||
const dt = Date.now() - t0;
|
||||
|
||||
// Adaptive sleep: treat very fast cycles as "idle", backoff; otherwise reset to min.
|
||||
if (!ok || dt < idleThresholdMs) {
|
||||
currentSleepMs = Math.min(maxSleepMs, Math.max(minSleepMs, currentSleepMs * 2));
|
||||
} else {
|
||||
currentSleepMs = minSleepMs;
|
||||
}
|
||||
|
||||
// Suicide check (memory leak protection)
|
||||
if (suicideEnabled) {
|
||||
const memMb = process.memoryUsage().rss / 1024 / 1024;
|
||||
if (cycleCount >= maxCyclesPerProcess || memMb > maxRssMb) {
|
||||
console.log(`[Daemon] Restarting self (cycles=${cycleCount}, rssMb=${memMb.toFixed(0)})`);
|
||||
const child = spawn(process.execPath, [__filename, ...args], {
|
||||
detached: true,
|
||||
stdio: 'ignore',
|
||||
env: process.env,
|
||||
});
|
||||
child.unref();
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Jitter to avoid lockstep restarts.
|
||||
const jitter = Math.floor(Math.random() * 250);
|
||||
await sleepMs(currentSleepMs + jitter);
|
||||
}
|
||||
} else {
|
||||
// Normal Single Run
|
||||
try {
|
||||
await evolve.run();
|
||||
} catch (error) {
|
||||
console.error('Evolution failed:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Post-run hint
|
||||
console.log('\n' + '=======================================================');
|
||||
console.log('Capability evolver finished. If you use this project, consider starring the upstream repository.');
|
||||
console.log('Upstream: https://github.com/autogame-17/capability-evolver');
|
||||
console.log('=======================================================\n');
|
||||
|
||||
} else if (command === 'solidify') {
|
||||
const dryRun = args.includes('--dry-run');
|
||||
const noRollback = args.includes('--no-rollback');
|
||||
const intentFlag = args.find(a => typeof a === 'string' && a.startsWith('--intent='));
|
||||
const summaryFlag = args.find(a => typeof a === 'string' && a.startsWith('--summary='));
|
||||
const intent = intentFlag ? intentFlag.slice('--intent='.length) : null;
|
||||
const summary = summaryFlag ? summaryFlag.slice('--summary='.length) : null;
|
||||
|
||||
try {
|
||||
const res = solidify({
|
||||
intent: intent || undefined,
|
||||
summary: summary || undefined,
|
||||
dryRun,
|
||||
rollbackOnFailure: !noRollback,
|
||||
});
|
||||
const st = res && res.ok ? 'SUCCESS' : 'FAILED';
|
||||
console.log(`[SOLIDIFY] ${st}`);
|
||||
if (res && res.gene) console.log(JSON.stringify(res.gene, null, 2));
|
||||
if (res && res.event) console.log(JSON.stringify(res.event, null, 2));
|
||||
if (res && res.capsule) console.log(JSON.stringify(res.capsule, null, 2));
|
||||
process.exit(res && res.ok ? 0 : 2);
|
||||
} catch (error) {
|
||||
console.error('[SOLIDIFY] Error:', error);
|
||||
process.exit(2);
|
||||
}
|
||||
} else {
|
||||
console.log(`Usage: node index.js [run|/evolve|solidify] [--loop]
|
||||
- solidify flags:
|
||||
- --dry-run
|
||||
- --no-rollback
|
||||
- --intent=repair|optimize|innovate
|
||||
- --summary=...`);
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
{
|
||||
"last_run": {
|
||||
"run_id": "run_1771767976531",
|
||||
"created_at": "2026-02-22T13:46:16.547Z",
|
||||
"parent_event_id": null,
|
||||
"selected_gene_id": "gene_gep_repair_from_errors",
|
||||
"selected_capsule_id": "capsule_1770477654236",
|
||||
"selector": {
|
||||
"selected": "gene_gep_repair_from_errors",
|
||||
"reason": [
|
||||
"signals match gene.signals_match",
|
||||
"capsule trigger matches signals",
|
||||
"signals: log_error, errsig:**TOOLRESULT**: === Cron Jobs (continued) === Daily OpenClaw Backup - Status: error - Model: opencode/kimi-k2.5-free - Error: model not allowed: opencode/kimi-k2.5-free Morning Briefing - Status: error - Model: opencode/kimi-k2.5-free - Error: model not allowe, memory_missing, user_missing",
|
||||
"memory_graph: memory_prefer:gene_gep_repair_from_errors"
|
||||
],
|
||||
"alternatives": []
|
||||
},
|
||||
"signals": [
|
||||
"log_error",
|
||||
"errsig:**TOOLRESULT**: === Cron Jobs (continued) === Daily OpenClaw Backup - Status: error - Model: opencode/kimi-k2.5-free - Error: model not allowed: opencode/kimi-k2.5-free Morning Briefing - Status: error - Model: opencode/kimi-k2.5-free - Error: model not allowe",
|
||||
"memory_missing",
|
||||
"user_missing"
|
||||
],
|
||||
"mutation": {
|
||||
"type": "Mutation",
|
||||
"id": "mut_1771767976529",
|
||||
"category": "repair",
|
||||
"trigger_signals": [
|
||||
"log_error",
|
||||
"errsig:**TOOLRESULT**: === Cron Jobs (continued) === Daily OpenClaw Backup - Status: error - Model: opencode/kimi-k2.5-free - Error: model not allowed: opencode/kimi-k2.5-free Morning Briefing - Status: error - Model: opencode/kimi-k2.5-free - Error: model not allowe",
|
||||
"memory_missing",
|
||||
"user_missing"
|
||||
],
|
||||
"target": "gene:gene_gep_repair_from_errors",
|
||||
"expected_effect": "reduce runtime errors, increase stability, and lower failure rate",
|
||||
"risk_level": "low"
|
||||
},
|
||||
"mutation_id": "mut_1771767976529",
|
||||
"personality_state": {
|
||||
"type": "PersonalityState",
|
||||
"rigor": 0.7,
|
||||
"creativity": 0.35,
|
||||
"verbosity": 0.25,
|
||||
"risk_tolerance": 0.4,
|
||||
"obedience": 0.85
|
||||
},
|
||||
"personality_key": "rigor=0.7|creativity=0.3|verbosity=0.3|risk_tolerance=0.4|obedience=0.9",
|
||||
"personality_known": false,
|
||||
"personality_mutations": [],
|
||||
"drift": false,
|
||||
"selected_by": "memory_graph+selector",
|
||||
"baseline_untracked": [
|
||||
"CONTRIBUTING.md",
|
||||
"README.md",
|
||||
"README.zh-CN.md",
|
||||
"SKILL.md",
|
||||
"_meta.json",
|
||||
"assets/gep/candidates.jsonl",
|
||||
"assets/gep/capsules.json",
|
||||
"assets/gep/genes.json",
|
||||
"index.js",
|
||||
"memory/evolution_state.json",
|
||||
"memory/memory_graph.jsonl",
|
||||
"memory/memory_graph_state.json",
|
||||
"memory/personality_state.json",
|
||||
"package.json",
|
||||
"scripts/a2a_export.js",
|
||||
"scripts/a2a_ingest.js",
|
||||
"scripts/a2a_promote.js",
|
||||
"scripts/analyze_by_skill.js",
|
||||
"scripts/build_public.js",
|
||||
"scripts/extract_log.js",
|
||||
"scripts/generate_history.js",
|
||||
"scripts/gep_append_event.js",
|
||||
"scripts/gep_personality_report.js",
|
||||
"scripts/human_report.js",
|
||||
"scripts/publish_public.js",
|
||||
"scripts/recover_loop.js",
|
||||
"scripts/suggest_version.js",
|
||||
"src/evolve.js",
|
||||
"src/gep/a2a.js",
|
||||
"src/gep/a2aProtocol.js",
|
||||
"src/gep/assetStore.js",
|
||||
"src/gep/bridge.js",
|
||||
"src/gep/candidates.js",
|
||||
"src/gep/contentHash.js",
|
||||
"src/gep/envFingerprint.js",
|
||||
"src/gep/memoryGraph.js",
|
||||
"src/gep/mutation.js",
|
||||
"src/gep/paths.js",
|
||||
"src/gep/personality.js",
|
||||
"src/gep/prompt.js",
|
||||
"src/gep/selector.js",
|
||||
"src/gep/signals.js",
|
||||
"src/gep/solidify.js",
|
||||
"src/gep/validationReport.js"
|
||||
],
|
||||
"baseline_git_head": "29a98137a743ec5e8c3a76c28ad3004b983d3945",
|
||||
"blast_radius_estimate": {
|
||||
"files": 12,
|
||||
"lines": 960
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"cycleCount": 1,
|
||||
"lastRun": 1771767976505
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"type": "GepPromptArtifact",
|
||||
"at": "2026-02-22T13:46:16.548Z",
|
||||
"cycle_id": "Cycle #0001",
|
||||
"run_id": "run_1771767976531",
|
||||
"prompt_path": "/home/openclaw/.openclaw/workspace/skills/evolver/memory/gep_prompt_Cycle_#0001_run_1771767976531.txt",
|
||||
"meta": {
|
||||
"agent": "main",
|
||||
"drift_enabled": false,
|
||||
"review_mode": false,
|
||||
"dry_run": false,
|
||||
"mutation_id": "mut_1771767976529",
|
||||
"personality_key": "rigor=0.7|creativity=0.3|verbosity=0.3|risk_tolerance=0.4|obedience=0.9"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,594 @@
|
||||
GEP — GENOME EVOLUTION PROTOCOL (STANDARD EXECUTION) [2026-02-22T13:46:16.547Z]
|
||||
|
||||
You are not a chat assistant.
|
||||
You are not a free agent.
|
||||
You are a protocol-bound evolution execution engine.
|
||||
|
||||
All actions must comply with this protocol.
|
||||
Any deviation is a failure even if the outcome appears correct.
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━
|
||||
I. Protocol Positioning (Non-Negotiable)
|
||||
━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Protocol goals:
|
||||
- Convert reasoning into reusable, auditable, shareable evolution assets
|
||||
- Make evolution a standard process, not improvisation
|
||||
- Reduce future reasoning cost for similar problems
|
||||
|
||||
Protocol compliance overrides local optimality.
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━
|
||||
II. Mandatory Evolution Object Model (All Required)
|
||||
━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Every evolution run must explicitly output the following five objects.
|
||||
Missing any one is an immediate failure.
|
||||
|
||||
──────────────────────
|
||||
0 Mutation
|
||||
──────────────────────
|
||||
|
||||
You must emit a Mutation object for every evolution run:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "Mutation",
|
||||
"id": "mut_<timestamp>",
|
||||
"category": "repair | optimize | innovate",
|
||||
"trigger_signals": ["<signal>"],
|
||||
"target": "<module | behavior | gene>",
|
||||
"expected_effect": "<effect>",
|
||||
"risk_level": "low | medium | high"
|
||||
}
|
||||
```
|
||||
|
||||
Hard safety constraints:
|
||||
- Do NOT run high-risk mutation unless rigor >= 0.6 AND risk_tolerance <= 0.5
|
||||
- Do NOT combine innovation mutation with a high-risk personality state
|
||||
|
||||
──────────────────────
|
||||
1 PersonalityState
|
||||
──────────────────────
|
||||
|
||||
You must emit a PersonalityState object for every evolution run:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "PersonalityState",
|
||||
"rigor": 0.0-1.0,
|
||||
"creativity": 0.0-1.0,
|
||||
"verbosity": 0.0-1.0,
|
||||
"risk_tolerance": 0.0-1.0,
|
||||
"obedience": 0.0-1.0
|
||||
}
|
||||
```
|
||||
|
||||
Personality mutation (optional, small deltas only):
|
||||
```json
|
||||
{
|
||||
"type": "PersonalityMutation",
|
||||
"param": "creativity",
|
||||
"delta": 0.1,
|
||||
"reason": "<reason>"
|
||||
}
|
||||
```
|
||||
Constraints:
|
||||
- Each delta must be within [-0.2, +0.2]
|
||||
- Do not adjust more than 2 parameters in one run
|
||||
|
||||
──────────────────────
|
||||
2 EvolutionEvent
|
||||
──────────────────────
|
||||
|
||||
You must emit an EvolutionEvent with all fields present:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "EvolutionEvent",
|
||||
"id": "evt_<timestamp>",
|
||||
"parent": null,
|
||||
"intent": "repair | optimize | innovate",
|
||||
"signals": ["<signal_1>", "<signal_2>"],
|
||||
"genes_used": ["<gene_id>"],
|
||||
"mutation_id": "<mut_id>",
|
||||
"personality_state": { "type": "PersonalityState", "...": "..." },
|
||||
"blast_radius": {
|
||||
"files": <number>,
|
||||
"lines": <number>
|
||||
},
|
||||
"outcome": {
|
||||
"status": "success | failed",
|
||||
"score": <0.0-1.0>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
EvolutionEvent is the only legal node type in the evolution tree.
|
||||
|
||||
──────────────────────
|
||||
3 Gene
|
||||
──────────────────────
|
||||
|
||||
If a Gene is used, you must reuse an existing Gene first.
|
||||
Only create a new Gene when no match exists.
|
||||
|
||||
Gene must follow this schema:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "Gene",
|
||||
"id": "gene_<name>",
|
||||
"category": "repair | optimize | innovate",
|
||||
"signals_match": ["<pattern>"],
|
||||
"preconditions": ["<condition>"],
|
||||
"strategy": [
|
||||
"<step_1>",
|
||||
"<step_2>"
|
||||
],
|
||||
"constraints": {
|
||||
"max_files": <number>,
|
||||
"forbidden_paths": ["<path>"]
|
||||
},
|
||||
"validation": ["<check_1>", "<check_2>"]
|
||||
}
|
||||
```
|
||||
|
||||
A Gene is an evolution interface definition, not code or generic advice.
|
||||
|
||||
──────────────────────
|
||||
4 Capsule
|
||||
──────────────────────
|
||||
|
||||
Only when evolution succeeds, you must generate a Capsule:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "Capsule",
|
||||
"id": "capsule_<timestamp>",
|
||||
"trigger": ["<signal>"],
|
||||
"gene": "<gene_id>",
|
||||
"summary": "<one sentence>",
|
||||
"confidence": <0.0-1.0>
|
||||
}
|
||||
```
|
||||
|
||||
Capsules exist to prevent repeated reasoning for similar problems.
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━
|
||||
III. Standard Evolution Execution (Strict Order)
|
||||
━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Follow this order exactly. Do not skip, merge, or reorder steps:
|
||||
|
||||
1 Signal Extraction
|
||||
- Extract structured signals from logs, errors, metrics, or instructions
|
||||
- Do not proceed to repair or optimize before signals are extracted
|
||||
|
||||
2 Selection
|
||||
- Prefer existing Genes first
|
||||
- Then consider existing Capsules
|
||||
- No improvisation or trial-and-error strategies
|
||||
|
||||
You must provide a clear, auditable selection rationale.
|
||||
|
||||
3 Patch Execution
|
||||
- All changes must be small and reversible
|
||||
- blast_radius must be estimated and recorded before edits
|
||||
|
||||
4 Validation
|
||||
- Execute Gene-declared validation steps
|
||||
- On failure, rollback
|
||||
- Failure must still record an EvolutionEvent
|
||||
|
||||
5 Knowledge Solidification (Mandatory)
|
||||
- Update or add Gene if a new pattern is found
|
||||
- Generate Capsule on success
|
||||
- Append EvolutionEvent
|
||||
|
||||
If knowledge solidification is missing, the evolution is a failure even if functionality works.
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━
|
||||
IV. Selector (Mandatory Decision Logic)
|
||||
━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
When choosing a Gene or Capsule, you must emit a Selector decision.
|
||||
|
||||
Selector must be explainable, for example:
|
||||
|
||||
```json
|
||||
{
|
||||
"selected": "gene_gep_repair_from_errors",
|
||||
"reason": [
|
||||
"signals exact match",
|
||||
"historical success rate high",
|
||||
"low blast radius"
|
||||
],
|
||||
"alternatives": ["capsule_1770477654236"]
|
||||
}
|
||||
```
|
||||
|
||||
Selector is part of the protocol, not an implementation detail.
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━
|
||||
V. Hard Failure Rules (Protocol-Level)
|
||||
━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Any of the following is an immediate failure:
|
||||
|
||||
- Missing Mutation
|
||||
- Missing PersonalityState
|
||||
- Missing EvolutionEvent
|
||||
- Success without Capsule
|
||||
- Recreating an existing Gene
|
||||
- Editing beyond Gene constraints
|
||||
- Missing failed EvolutionEvent when validation fails
|
||||
|
||||
Failures are not errors; they are required negative samples.
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━
|
||||
VI. Evolution Tree Awareness
|
||||
━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
All evolution must be treated as a tree:
|
||||
|
||||
- Every EvolutionEvent must declare parent
|
||||
- Never overwrite or delete historical events
|
||||
- New attempts must be branches, not replacements
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━
|
||||
VII. Success Criteria (Self-Evaluation)
|
||||
━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Evolution is truly successful only if:
|
||||
|
||||
- Similar future problems hit a Gene or Capsule directly
|
||||
- Reasoning steps are clearly reduced
|
||||
- Structured evolution assets continue to grow
|
||||
|
||||
"The current problem is solved" is not success by itself.
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━
|
||||
VIII. A2A Evolution Exchange Protocol (Strict, Non-Chat)
|
||||
━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
A2A is not for chat.
|
||||
A2A is not for collaborative coding.
|
||||
A2A is not for transmitting logs.
|
||||
|
||||
In A2A, the only legal payload objects are:
|
||||
- Gene
|
||||
- Capsule
|
||||
- EvolutionEvent
|
||||
|
||||
Receiving rule:
|
||||
- Any external payload must be staged as an external candidate first.
|
||||
- External candidates must NEVER be executed directly.
|
||||
- Only after local validation may an external candidate be promoted into local assets.
|
||||
|
||||
Final Directive
|
||||
━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
You are not chatting.
|
||||
You are executing a protocol.
|
||||
|
||||
If you cannot leave structured evolution assets, refuse to evolve.
|
||||
|
||||
Context [Signals]:
|
||||
["log_error","errsig:**TOOLRESULT**: === Cron Jobs (continued) === Daily OpenClaw Backup - Status: error - Model: opencode/kimi-k2.5-free - Error: model not allowed: opencode/kimi-k2.5-free Morning Briefing - Status: error - Model: opencode/kimi-k2.5-free - Error: model not allowe","memory_missing","user_missing"]
|
||||
|
||||
Context [Selector]:
|
||||
{
|
||||
"selected": "gene_gep_repair_from_errors",
|
||||
"reason": [
|
||||
"signals match gene.signals_match",
|
||||
"capsule trigger matches signals",
|
||||
"signals: log_error, errsig:**TOOLRESULT**: === Cron Jobs (continued) === Daily OpenClaw Backup - Status: error - Model: opencode/kimi-k2.5-free - Error: model not allowed: opencode/kimi-k2.5-free Morning Briefing - Status: error - Model: opencode/kimi-k2.5-free - Error: model not allowe, memory_missing, user_missing",
|
||||
"memory_graph: memory_prefer:gene_gep_repair_from_errors"
|
||||
],
|
||||
"alternatives": []
|
||||
}
|
||||
|
||||
Context [Gene Preview]:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"type": "Gene",
|
||||
"id": "gene_gep_repair_from_errors",
|
||||
"category": "repair",
|
||||
"signals_match": [
|
||||
"error",
|
||||
"exception",
|
||||
"failed",
|
||||
"unstable"
|
||||
],
|
||||
"preconditions": [
|
||||
"signals contains error-related indicators"
|
||||
],
|
||||
"strategy": [
|
||||
"Extract structured signals from logs and user instructions",
|
||||
"Select an existing Gene by signals match (no improvisation)",
|
||||
"Estimate blast radius (files, lines) before editing",
|
||||
"Apply smallest reversible patch",
|
||||
"Validate using declared validation steps; rollback on failure",
|
||||
"Solidify knowledge: append EvolutionEvent, update Gene/Capsule store"
|
||||
],
|
||||
"constraints": {
|
||||
"max_files": 12,
|
||||
"forbidden_paths": [
|
||||
".git",
|
||||
"node_modules"
|
||||
]
|
||||
},
|
||||
"validation": [
|
||||
"node -e \"require('./src/evolve'); require('./src/gep/solidify'); console.log('ok')\"",
|
||||
"node -e \"require('./src/gep/selector'); require('./src/gep/memoryGraph'); console.log('ok')\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Gene",
|
||||
"id": "gene_gep_optimize_prompt_and_assets",
|
||||
"category": "optimize",
|
||||
"signals_match": [
|
||||
"protocol",
|
||||
"gep",
|
||||
"prompt",
|
||||
"audit",
|
||||
"reusable"
|
||||
],
|
||||
"preconditions": [
|
||||
"need stricter, auditable evolution protocol outputs"
|
||||
],
|
||||
"strategy": [
|
||||
"Extract signals and determine selection rationale via Selector JSON",
|
||||
"Prefer reusing existing Gene/Capsule; only create if no match exists",
|
||||
"Refactor prompt assembly to embed assets (genes, capsules, parent event)",
|
||||
"Reduce noise and ambiguity; enforce strict output schema",
|
||||
"Validate by running node index.js run and ensuring no runtime errors",
|
||||
"Solidify: record EvolutionEvent, update Gene definitions, create Capsule on success"
|
||||
],
|
||||
"constraints": {
|
||||
"max_files": 20,
|
||||
"forbidden_paths": [
|
||||
".git",
|
||||
"node_modules"
|
||||
]
|
||||
},
|
||||
"validation": [
|
||||
"node -e \"require('./src/evolve'); require('./src/gep/prompt'); console.log('ok')\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Gene",
|
||||
"id": "gene_gep_innovate_from_opportunity",
|
||||
"category": "innovate",
|
||||
"signals_match": [
|
||||
"user_feature_request",
|
||||
"user_improvement_suggestion",
|
||||
"perf_bottleneck",
|
||||
"capability_gap",
|
||||
"stable_success_plateau",
|
||||
"external_opportunity"
|
||||
],
|
||||
"preconditions": [
|
||||
"at least one opportunity signal is present",
|
||||
"no active log_error signals (stability first)"
|
||||
],
|
||||
"strategy": [
|
||||
"Extract opportunity signals and identify the specific user need or system gap",
|
||||
"Search existing Genes and Capsules for partial matches (avoid reinventing)",
|
||||
"Design a minimal, testable implementation plan (prefer small increments)",
|
||||
"Estimate blast radius; innovate changes may touch more files but must stay within constraints",
|
||||
"Implement the change with clear validation criteria",
|
||||
"Validate using declared validation steps; rollback on failure",
|
||||
"Solidify: record EvolutionEvent with intent=innovate, create new Gene if pattern is novel, create Capsule on success"
|
||||
],
|
||||
"constraints": {
|
||||
"max_files": 8,
|
||||
"forbidden_paths": [
|
||||
".git",
|
||||
"node_modules",
|
||||
"assets/gep/events.jsonl"
|
||||
]
|
||||
},
|
||||
"validation": [
|
||||
"node -e \"require('./src/evolve'); require('./src/gep/solidify'); console.log('ok')\""
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Context [Capsule Preview]:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"type": "Capsule",
|
||||
"schema_version": "1.5.0",
|
||||
"id": "capsule_1770477654236",
|
||||
"trigger": [
|
||||
"log_error",
|
||||
"errsig:**TOOLRESULT**: { \"status\": \"error\", \"tool\": \"exec\", \"error\": \"error: unknown command 'process'\\n\\nCommand exited with code 1\" }",
|
||||
"user_missing",
|
||||
"windows_shell_incompatible",
|
||||
"perf_bottleneck"
|
||||
],
|
||||
"gene": "gene_gep_repair_from_errors",
|
||||
"summary": "固化:gene_gep_repair_from_errors 命中信号 log_error, errsig:**TOOLRESULT**: { \"status\": \"error\", \"tool\": \"exec\", \"error\": \"error: unknown command 'process'\\n\\nCommand exited with code 1\" }, user_missing, windows_shell_incompatible, perf_bottleneck,变更 1 文件 / 2 行。",
|
||||
"confidence": 0.85,
|
||||
"blast_radius": {
|
||||
"files": 1,
|
||||
"lines": 2
|
||||
},
|
||||
"outcome": {
|
||||
"status": "success",
|
||||
"score": 0.85
|
||||
},
|
||||
"success_streak": 1,
|
||||
"env_fingerprint": {
|
||||
"node_version": "v22.22.0",
|
||||
"platform": "linux",
|
||||
"arch": "x64",
|
||||
"os_release": "6.1.0-42-cloud-amd64",
|
||||
"evolver_version": "1.7.0",
|
||||
"cwd": "/home/crishaocredits/.openclaw/workspace",
|
||||
"captured_at": "2026-02-07T15:20:54.155Z"
|
||||
},
|
||||
"a2a": {
|
||||
"eligible_to_broadcast": false
|
||||
},
|
||||
"asset_id": "sha256:3eed0cd5038f9e85fbe0d093890e291e9b8725644c766e6cce40bf62d0f5a2e8"
|
||||
},
|
||||
{
|
||||
"type": "Capsule",
|
||||
"schema_version": "1.5.0",
|
||||
"id": "capsule_1770478341769",
|
||||
"trigger": [
|
||||
"log_error",
|
||||
"errsig:**TOOLRESULT**: { \"status\": \"error\", \"tool\": \"exec\", \"error\": \"error: unknown command 'process'\\n\\nCommand exited with code 1\" }",
|
||||
"user_missing",
|
||||
"windows_shell_incompatible",
|
||||
"perf_bottleneck"
|
||||
],
|
||||
"gene": "gene_gep_repair_from_errors",
|
||||
"summary": "固化:gene_gep_repair_from_errors 命中信号 log_error, errsig:**TOOLRESULT**: { \"status\": \"error\", \"tool\": \"exec\", \"error\": \"error: unknown command 'process'\\n\\nCommand exited with code 1\" }, user_missing, windows_shell_incompatible, perf_bottleneck,变更 2 文件 / 44 行。",
|
||||
"confidence": 0.85,
|
||||
"blast_radius": {
|
||||
"files": 2,
|
||||
"lines": 44
|
||||
},
|
||||
"outcome": {
|
||||
"status": "success",
|
||||
"score": 0.85
|
||||
},
|
||||
"success_streak": 1,
|
||||
"env_fingerprint": {
|
||||
"node_version": "v22.22.0",
|
||||
"platform": "linux",
|
||||
"arch": "x64",
|
||||
"os_release": "6.1.0-42-cloud-amd64",
|
||||
"evolver_version": "1.7.0",
|
||||
"cwd": "/home/crishaocredits/.openclaw/workspace",
|
||||
"captured_at": "2026-02-07T15:32:21.678Z"
|
||||
},
|
||||
"a2a": {
|
||||
"eligible_to_broadcast": false
|
||||
},
|
||||
"asset_id": "sha256:20d971a3c4cb2b75f9c045376d1aa003361c12a6b89a4b47b7e81dbd4f4d8fe8"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Context [Capability Candidates] (Five questions shape; keep it short):
|
||||
- cand_2f2b5dde: Repeated tool usage: read
|
||||
- input: Recent session transcript + memory snippets + user instructions
|
||||
- output: A safe, auditable evolution patch guided by GEP assets
|
||||
- invariants: Protocol order, small reversible patches, validation, append-only events
|
||||
- params: Signals: log_error, errsig:**TOOLRESULT**: === Cron Jobs (continued) === Daily OpenClaw Backup - Status: error - Model: opencode/kimi-k2.5-free - Error: model not allowed: opencode/kimi-k2.5-free Morning Briefing - Status: error - Model: opencode/kimi-k2.5-free - Error: model not allowe, memory_missing, user_missing
|
||||
- failure_points: Missing signals, over-broad changes, skipped validation, missing knowledge solidification
|
||||
- evidence: Observed 4 occurrences of tool call marker for read.
|
||||
- cand_5afdf7f3: Repeated tool usage: exec
|
||||
- input: Recent session transcript + memory snippets + user instructions
|
||||
- output: A safe, auditable evolution patch guided by GEP assets
|
||||
- invariants: Protocol order, small reversible patches, validation, append-only events
|
||||
- params: Signals: log_error, errsig:**TOOLRESULT**: === Cron Jobs (continued) === Daily OpenClaw Backup - Status: error - Model: opencode/kimi-k2.5-free - Error: model not allowed: opencode/kimi-k2.5-free Morning Briefing - Status: error - Model: opencode/kimi-k2.5-free - Error: model not allowe, memory_missing, user_missing
|
||||
- failure_points: Missing signals, over-broad changes, skipped validation, missing knowledge solidification
|
||||
- evidence: Observed 8 occurrences of tool call marker for exec.
|
||||
- cand_dd9067ac: R ...[TRUNCATED]
|
||||
|
||||
Context [External Candidates] (A2A staged; do not execute directly):
|
||||
(none)
|
||||
|
||||
Context [Execution]:
|
||||
Runtime state:
|
||||
- System health: Uptime: 12.7h | Node: v22.22.0 | Agent RSS: 55.4MB | Disk: 86% (3.6G free) | Node Processes: 5 | Integrations: Gemini Key Missing
|
||||
- Agent state: Mood: Unknown
|
||||
- Scan duration: 21ms
|
||||
- Memory size: 0 bytes
|
||||
- Skills available (if any):
|
||||
[skills directory not found]
|
||||
|
||||
Notes:
|
||||
- Review mode: disabled.
|
||||
- Report requirement:
|
||||
- Use `message` tool.
|
||||
- Title: Evolution Cycle #0001
|
||||
- Status: [SUCCESS]
|
||||
- Changes: Detail exactly what was improved.
|
||||
- Workspace sync: optional/disabled in this environment.
|
||||
|
||||
External candidates (A2A receive zone; staged only, never execute directly):
|
||||
(none)
|
||||
|
||||
Global memory (MEMORY.md):
|
||||
```
|
||||
[MEMORY.md MISSING]
|
||||
```
|
||||
|
||||
User registry (USER.md):
|
||||
```
|
||||
[USER.md MISSING]
|
||||
```
|
||||
|
||||
Recent memory snippet:
|
||||
```
|
||||
[MISSING] /home/openclaw/.openclaw/workspace/skills/evolver/memory/2026-02-22.md
|
||||
```
|
||||
|
||||
Recent session transcript:
|
||||
```
|
||||
|
||||
--- PREVIOUS SESSION (8b59e765-4b77-49ff-9654-19d4b03ac6e8.jsonl) ---
|
||||
**ASSISTANT**: Smart newsletter digest completed ✅ **Result:** Digest sent to Telegram (chat ID: 1793951355) **Note:** This is the 3rd run in ~90 minutes (12:00, 13:26, 13:29 UTC). The cron schedule may need adjustment if daily digests were intended.
|
||||
|
||||
--- CURRENT SESSION (9bb42024-10c0-4668-bdd0-4616bdb1a974.jsonl) ---
|
||||
**ASSISTANT**: Newsletter digest sent through to your Telegram! 📬 Though heads up — looks like it's run 3 times in the last 90 minutes. Might want to check the cron schedule if it was meant to be daily rather than hourly? Could save on unnecessary runs.
|
||||
**USER**: System: [2026-02-22 13:31:40 UTC] Elevated ASK — exec runs on host; approvals may still apply. System: [2026-02-22 13:31:49 UTC] Elevated FULL — exec runs on host with auto-approval. System: [2026-02-22 13:32:02 UTC] Elevated OFF — exec stays in sandbox. System: [2026-02-22 13:32:09 UTC] Elevated FU
|
||||
**ASSISTANT**: [TOOL: read]
|
||||
**TOOLRESULT**: --- name: session-logs description: Search and analyze your own session logs (older/parent conversations) using jq. metadata: { "openclaw": { "emoji": "📜", "requires": { "bins": ["jq", "rg"] } } } --- # session-logs Search your complete conversation history stored in session JSONL files. Use this w
|
||||
**ASSISTANT**: [TOOL: exec]
|
||||
**TOOLRESULT**: 2026-02-22 9.1K 957fb746-9392-48d1-a8e6-89aab7bdb754.jsonl 2026-02-22 897 873689e3-30a5-4078-a81e-3a7ced73e887.jsonl 2026-02-22 74K bbeea897-3e75-4df3-8454-1bc54d4b1d96.jsonl 2026-02-22 6.3K b2f7a2a7-f59f-4688-a571-c39c078016e9.jsonl 2026-02-22 5.9K 67b516cb-96bd-4dc4-beaa-7da9d2c2d971.jsonl 2026-02
|
||||
**ASSISTANT**: [TOOL: exec]
|
||||
**TOOLRESULT**: === Session Count === 106 === Sessions Index (sessions.json) === agent:main:main -> 9bb42024-10c0-4668-bdd0-4616bdb1a974 agent:main:cron:10e22343-fbb4-4018-bb08-c5b249662411 -> 29a56083-ceef-4139-ba1b-74c10ffd2ce4 agent:main:main:thread:15536 -> b95fc198-3557-4bc2-be24-5c2119546481 agent:main:main:t
|
||||
**ASSISTANT**: 📜 **Session Logs Overview** You've got **106 sessions** logged. Here's the breakdown: **Recent Activity (by cost):** | Date | Cost | |------|------| | Today (Feb 22) | $2.61 | | Feb 21 | $1.52 | | Feb 20 | $0.77 | **Key Sessions:** - `agent:main:main` — Main chat with you - `agent:main:telegram:gr
|
||||
**USER**: Conversation info (untrusted metadata): ```json { "message_id": "2079", "sender_id": "1793951355", "sender": "1793951355" } ``` Work out why my setup is unstable
|
||||
**ASSISTANT**: [TOOL: exec] [TOOL: exec] [TOOL: exec]
|
||||
**TOOLRESULT**: Command still running (session ember-lobster, pid 86903). Use process (list/poll/log/write/kill/clear/remove) for follow-up.
|
||||
**TOOLRESULT**: === OpenClaw Config Issues === jq: error (at <stdin>:785): Cannot index boolean with string "enabled" === Disk Space === Filesystem Size Used Avail Use% Mounted on /dev/sda1 26G 22G 2.5G 90% / /dev/sda1 26G 22G 2.5G 90% / === Memory Usage === total u
|
||||
**ASSISTANT**: [TOOL: process] [TOOL: exec] [TOOL: exec]
|
||||
**TOOLRESULT**: === Gateway Status === OpenClaw status Overview ┌─────────────────┬───────────────────────────────────────────────────────────────────────────────────────────────────┐ │ Item │ Value │ ├───────────
|
||||
**TOOLRESULT**: === Disk Usage - Biggest Consumers === 7.8G /home/openclaw/.openclaw/workspace 33M /home/openclaw/.openclaw/agents 16M /home/openclaw/.openclaw/browser 8.3M /home/openclaw/.openclaw/media 7.3M /home/openclaw/.openclaw/credentials 6.5M /home/openclaw/.openclaw/memory 436K /home/openclaw/.openclaw/com
|
||||
**TOOLRESULT**: === OpenClaw Config Structure === [ "agents", "approvals", "auth", "browser", "canvasHost", "channels", "commands", "cron", "diagnostics", "discovery", "env", "gateway", "hooks", "memory", "messages", "meta", "models", "nodeHost", "plugins", "session", "skil
|
||||
**ASSISTANT**: [TOOL: exec] [TOOL: exec] [TOOL: exec]
|
||||
**TOOLRESULT**: === Workspace Disk Usage === 7.1G /home/openclaw/.openclaw/workspace/skills 434M /home/openclaw/.openclaw/workspace/memory-viewer 1.6M /home/openclaw/.openclaw/workspace/sessions 504K /home/openclaw/.openclaw/workspace/tmp_vte.en.vtt 280K /home/openclaw/.openclaw/workspace/memory 224K /home/openclaw
|
||||
**TOOLRESULT**: === Memory Folder === 148K /home/openclaw/.openclaw/workspace/memory/episodic 16K /home/openclaw/.openclaw/workspace/memory/2026-02-21-gitea-backup.md 12K /home/openclaw/.openclaw/workspace/memory/2026-02-21-openai-onboard.md 8.0K /home/openclaw/.openclaw/workspace/memory/semantic 8.0K /home/opencla
|
||||
**TOOLRESULT**: === Systemd Service Restarts === ActiveState=inactive SubState=dead === Recent Service Restarts ===
|
||||
**USER**: Conversation info (untrusted metadata): ```json { "message_id": "2091", "sender_id": "1793951355", "sender": "1793951355" } ``` /auto_updater
|
||||
**ASSISTANT**: [TOOL: read]
|
||||
**TOOLRESULT**: --- name: auto-updater description: "Automatically update Clawdbot and all installed skills once daily. Runs via cron, checks for updates, applies them, and messages the user with a summary of what changed." metadata: {"version":"1.0.0","clawdbot":{"emoji":"🔄","os":["darwin","linux"]}} --- # Auto-U
|
||||
**ASSISTANT**: [TOOL: exec]
|
||||
... [Repeated 1 times] ...
|
||||
**TOOLRESULT**: === Cron Jobs (continued) === Daily OpenClaw Backup - Status: error - Model: opencode/kimi-k2.5-free - Error: model not allowed: opencode/kimi-k2.5-free Morning Briefing - Status: error - Model: opencode/kimi-k2.5-free - Error: model not allowed: opencode/kimi-k2.5-free Weekend Briefing - Status: er
|
||||
**ASSISTANT**: [TOOL: exec] [TOOL: exec]
|
||||
**TOOLRESULT**: === Model Configuration === { "default": null, "heartbeat": null, "allowed": null, "providers": { "name": "moonshot", "models": [ 0 ] } } { "default": null, "heartbeat": null, "allowed": null, "providers": { "name": "nim", "models": [ 0, 1,
|
||||
**TOOLRESULT**: === Check if opencode provider exists === [ "moonshot", "nim", "openrouter" ] === Check moonshot provider (kimi) === { "baseUrl": "https://api.moonshot.ai/v1", "apiKey": "", "auth": "api-key", "api": "openai-completions", "authHeader": true, "models": [ { "id": "moonshot/
|
||||
**ASSISTANT**: Found the issues. Here's what's making your setup unstable: --- ## 🔴 **ROOT CAUSE: Model Configuration Broken** **5 cron jobs are failing** with this error: ``` model not allowed: opencode/kimi-k2.5-free ``` The cron jobs are configured to use `opencode/kimi-k2.5-free` but: 1. ❌ **No `opencode` pr
|
||||
**USER**: Conversation info (untrusted metadata): ```json { "message_id": "2092", "sender_id": "1793951355", "sender": "1793951355" } ``` /chrome
|
||||
**ASSISTANT**: [TOOL: read]
|
||||
**TOOLRESULT**: --- name: Chrome description: Chrome DevTools Protocol, extension Manifest V3, and debugging patterns that prevent common automation failures. --- ## Chrome DevTools Protocol (CDP) **Get tab WebSocket URL first**: Never connect to `ws://localhost:9222/devtools/browser` directly. Fetch `http://localh
|
||||
**ASSISTANT**: Loaded the **Chrome** skill! 🌐 This covers: | Topic | What It Helps With | |-------|-------------------| | **CDP (DevTools Protocol)** | Tab automation, screenshots, network interception | | **Extension Manifest V3** | Service workers, permissions, content scripts | | **Context Detection** | Detec
|
||||
**USER**: Conversation info (untrusted metadata): ```json { "message_id": "2093", "sender_id": "1793951355", "sender": "1793951355" } ``` /capability_evolver
|
||||
**ASSISTANT**: [TOOL: read]
|
||||
**TOOLRESULT**: --- name: capability-evolver description: A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution. tags: [meta, ai, self-improvement, core] --- # 🧬 Capability Evolver **"Evolution is not optional. Adapt or die."** The **Cap
|
||||
**ASSISTANT**: Loaded the **Capability Evolver** skill! 🧬 A meta-skill for **self-improving AI agents**. It can: | Feature | What It Does | |---------|--------------| | **Auto-Log Analysis** | Scans memory and history for errors/patterns | | **Self-Repair** | Detects crashes and suggests patches | | **GEP Protoc
|
||||
**USER**: Conversation info (untrusted metadata): ```json { "message_id": "2095", "sender_id": "1793951355", "sender": "1793951355" } ``` Yes! Apply asap
|
||||
**ASSISTANT**: [TOOL: exec]
|
||||
**TOOLRESULT**: Usage: node index.js [run|/evolve|solidify] [--loop] - solidify flags: - --dry-run - --no-rollback - --intent=repair|optimize|innovate - --summary=...
|
||||
**ASSISTANT**: [TOOL: exec]
|
||||
```
|
||||
|
||||
Mutation directive:
|
||||
|
||||
[Signal Hints]
|
||||
- recent_error_count: 5
|
||||
- stability: unstable
|
||||
- recommended_intent: repair
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"version": 1,
|
||||
"current": {
|
||||
"type": "PersonalityState",
|
||||
"rigor": 0.7,
|
||||
"creativity": 0.35,
|
||||
"verbosity": 0.25,
|
||||
"risk_tolerance": 0.4,
|
||||
"obedience": 0.85
|
||||
},
|
||||
"stats": {},
|
||||
"history": [],
|
||||
"updated_at": "2026-02-22T13:46:16.529Z"
|
||||
}
|
||||
26
archive/inactive-skills/evolver/package.json
Normal file
26
archive/inactive-skills/evolver/package.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "evolver",
|
||||
"version": "1.7.3",
|
||||
"description": "A self-evolution engine for AI agents. Features automated log analysis and protocol-constrained evolution with auditable assets.",
|
||||
"main": "index.js",
|
||||
"keywords": [
|
||||
"openclaw",
|
||||
"ai",
|
||||
"evolution",
|
||||
"meta-learning",
|
||||
"self-repair",
|
||||
"automation",
|
||||
"agent"
|
||||
],
|
||||
"author": "OpenClaw",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"start": "node index.js",
|
||||
"run": "node index.js run",
|
||||
"solidify": "node index.js solidify",
|
||||
"a2a:export": "node scripts/a2a_export.js",
|
||||
"a2a:ingest": "node scripts/a2a_ingest.js",
|
||||
"a2a:promote": "node scripts/a2a_promote.js"
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
45
archive/inactive-skills/evolver/scripts/a2a_export.js
Normal file
45
archive/inactive-skills/evolver/scripts/a2a_export.js
Normal file
@@ -0,0 +1,45 @@
|
||||
const { loadGenes, loadCapsules, readAllEvents } = require('../src/gep/assetStore');
|
||||
const { exportEligibleCapsules } = require('../src/gep/a2a');
|
||||
const { buildPublish, buildHello, getTransport } = require('../src/gep/a2aProtocol');
|
||||
|
||||
function main() {
|
||||
var args = process.argv.slice(2);
|
||||
var asJson = args.includes('--json');
|
||||
var asProtocol = args.includes('--protocol');
|
||||
var withHello = args.includes('--hello');
|
||||
var persist = args.includes('--persist');
|
||||
|
||||
var capsules = loadCapsules();
|
||||
var events = readAllEvents();
|
||||
var eligible = exportEligibleCapsules({ capsules: capsules, events: events });
|
||||
|
||||
if (withHello || asProtocol) {
|
||||
var genes = loadGenes();
|
||||
var hello = buildHello({ geneCount: genes.length, capsuleCount: capsules.length });
|
||||
process.stdout.write(JSON.stringify(hello) + '\n');
|
||||
if (persist) { try { getTransport().send(hello); } catch (e) {} }
|
||||
}
|
||||
|
||||
if (asProtocol) {
|
||||
for (var i = 0; i < eligible.length; i++) {
|
||||
var msg = buildPublish({ asset: eligible[i] });
|
||||
process.stdout.write(JSON.stringify(msg) + '\n');
|
||||
if (persist) { try { getTransport().send(msg); } catch (e) {} }
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (asJson) {
|
||||
process.stdout.write(JSON.stringify(eligible, null, 2) + '\n');
|
||||
return;
|
||||
}
|
||||
|
||||
for (var j = 0; j < eligible.length; j++) {
|
||||
process.stdout.write(JSON.stringify(eligible[j]) + '\n');
|
||||
}
|
||||
}
|
||||
|
||||
try { main(); } catch (e) {
|
||||
process.stderr.write((e && e.message ? e.message : String(e)) + '\n');
|
||||
process.exit(1);
|
||||
}
|
||||
79
archive/inactive-skills/evolver/scripts/a2a_ingest.js
Normal file
79
archive/inactive-skills/evolver/scripts/a2a_ingest.js
Normal file
@@ -0,0 +1,79 @@
|
||||
var fs = require('fs');
|
||||
var assetStore = require('../src/gep/assetStore');
|
||||
var a2a = require('../src/gep/a2a');
|
||||
var memGraph = require('../src/gep/memoryGraph');
|
||||
var contentHash = require('../src/gep/contentHash');
|
||||
var a2aProto = require('../src/gep/a2aProtocol');
|
||||
|
||||
function readStdin() {
|
||||
try { return fs.readFileSync(0, 'utf8'); } catch (e) { return ''; }
|
||||
}
|
||||
|
||||
function parseSignalsFromEnv() {
|
||||
var raw = process.env.A2A_SIGNALS || '';
|
||||
if (!raw) return [];
|
||||
try {
|
||||
var maybe = JSON.parse(raw);
|
||||
if (Array.isArray(maybe)) return maybe.map(String).filter(Boolean);
|
||||
} catch (e) {}
|
||||
return String(raw).split(',').map(function (s) { return s.trim(); }).filter(Boolean);
|
||||
}
|
||||
|
||||
function main() {
|
||||
var args = process.argv.slice(2);
|
||||
var inputPath = '';
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
if (args[i] && !args[i].startsWith('--')) { inputPath = args[i]; break; }
|
||||
}
|
||||
var source = process.env.A2A_SOURCE || 'external';
|
||||
var factor = Number.isFinite(Number(process.env.A2A_EXTERNAL_CONFIDENCE_FACTOR))
|
||||
? Number(process.env.A2A_EXTERNAL_CONFIDENCE_FACTOR) : 0.6;
|
||||
|
||||
var text = inputPath ? a2a.readTextIfExists(inputPath) : readStdin();
|
||||
var parsed = a2a.parseA2AInput(text);
|
||||
var signals = parseSignalsFromEnv();
|
||||
|
||||
var accepted = 0;
|
||||
var rejected = 0;
|
||||
var emitDecisions = process.env.A2A_EMIT_DECISIONS === 'true';
|
||||
|
||||
for (var j = 0; j < parsed.length; j++) {
|
||||
var obj = parsed[j];
|
||||
if (!a2a.isAllowedA2AAsset(obj)) continue;
|
||||
|
||||
if (obj.asset_id && typeof obj.asset_id === 'string') {
|
||||
if (!contentHash.verifyAssetId(obj)) {
|
||||
rejected += 1;
|
||||
if (emitDecisions) {
|
||||
try {
|
||||
var dm = a2aProto.buildDecision({ assetId: obj.asset_id, localId: obj.id, decision: 'reject', reason: 'asset_id integrity check failed' });
|
||||
a2aProto.getTransport().send(dm);
|
||||
} catch (e) {}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
var staged = a2a.lowerConfidence(obj, { source: source, factor: factor });
|
||||
if (!staged) continue;
|
||||
|
||||
assetStore.appendExternalCandidateJsonl(staged);
|
||||
try { memGraph.recordExternalCandidate({ asset: staged, source: source, signals: signals }); } catch (e) {}
|
||||
|
||||
if (emitDecisions) {
|
||||
try {
|
||||
var dm2 = a2aProto.buildDecision({ assetId: staged.asset_id, localId: staged.id, decision: 'quarantine', reason: 'staged as external candidate' });
|
||||
a2aProto.getTransport().send(dm2);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
accepted += 1;
|
||||
}
|
||||
|
||||
process.stdout.write('accepted=' + accepted + ' rejected=' + rejected + '\n');
|
||||
}
|
||||
|
||||
try { main(); } catch (e) {
|
||||
process.stderr.write((e && e.message ? e.message : String(e)) + '\n');
|
||||
process.exit(1);
|
||||
}
|
||||
106
archive/inactive-skills/evolver/scripts/a2a_promote.js
Normal file
106
archive/inactive-skills/evolver/scripts/a2a_promote.js
Normal file
@@ -0,0 +1,106 @@
|
||||
var assetStore = require('../src/gep/assetStore');
|
||||
var solidifyMod = require('../src/gep/solidify');
|
||||
var contentHash = require('../src/gep/contentHash');
|
||||
var a2aProto = require('../src/gep/a2aProtocol');
|
||||
|
||||
function parseArgs(argv) {
|
||||
var out = { flags: new Set(), kv: new Map(), positionals: [] };
|
||||
for (var i = 0; i < argv.length; i++) {
|
||||
var a = argv[i];
|
||||
if (!a) continue;
|
||||
if (a.startsWith('--')) {
|
||||
var eq = a.indexOf('=');
|
||||
if (eq > -1) { out.kv.set(a.slice(2, eq), a.slice(eq + 1)); }
|
||||
else {
|
||||
var key = a.slice(2);
|
||||
var next = argv[i + 1];
|
||||
if (next && !String(next).startsWith('--')) { out.kv.set(key, next); i++; }
|
||||
else { out.flags.add(key); }
|
||||
}
|
||||
} else { out.positionals.push(a); }
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function main() {
|
||||
var args = parseArgs(process.argv.slice(2));
|
||||
var id = String(args.kv.get('id') || '').trim();
|
||||
var typeRaw = String(args.kv.get('type') || '').trim().toLowerCase();
|
||||
var validated = args.flags.has('validated') || String(args.kv.get('validated') || '') === 'true';
|
||||
var limit = Number.isFinite(Number(args.kv.get('limit'))) ? Number(args.kv.get('limit')) : 500;
|
||||
|
||||
if (!id || !typeRaw) throw new Error('Usage: node scripts/a2a_promote.js --type capsule|gene --id <id> --validated');
|
||||
if (!validated) throw new Error('Refusing to promote without --validated (local verification must be done first).');
|
||||
|
||||
var type = typeRaw === 'capsule' ? 'Capsule' : typeRaw === 'gene' ? 'Gene' : '';
|
||||
if (!type) throw new Error('Invalid --type. Use capsule or gene.');
|
||||
|
||||
var external = assetStore.readRecentExternalCandidates(limit);
|
||||
var candidate = null;
|
||||
for (var i = 0; i < external.length; i++) {
|
||||
if (external[i] && external[i].type === type && String(external[i].id) === id) { candidate = external[i]; break; }
|
||||
}
|
||||
if (!candidate) throw new Error('Candidate not found in external zone: type=' + type + ' id=' + id);
|
||||
|
||||
if (type === 'Gene') {
|
||||
var validation = Array.isArray(candidate.validation) ? candidate.validation : [];
|
||||
for (var j = 0; j < validation.length; j++) {
|
||||
var c = String(validation[j] || '').trim();
|
||||
if (!c) continue;
|
||||
if (!solidifyMod.isValidationCommandAllowed(c)) {
|
||||
throw new Error('Refusing to promote Gene ' + id + ': validation command rejected by safety check: "' + c + '". Only node/npm/npx commands without shell operators are allowed.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var promoted = JSON.parse(JSON.stringify(candidate));
|
||||
if (!promoted.a2a || typeof promoted.a2a !== 'object') promoted.a2a = {};
|
||||
promoted.a2a.status = 'promoted';
|
||||
promoted.a2a.promoted_at = new Date().toISOString();
|
||||
if (!promoted.schema_version) promoted.schema_version = contentHash.SCHEMA_VERSION;
|
||||
promoted.asset_id = contentHash.computeAssetId(promoted);
|
||||
|
||||
var emitDecisions = process.env.A2A_EMIT_DECISIONS === 'true';
|
||||
|
||||
if (type === 'Capsule') {
|
||||
assetStore.appendCapsule(promoted);
|
||||
if (emitDecisions) {
|
||||
try {
|
||||
var dm = a2aProto.buildDecision({ assetId: promoted.asset_id, localId: id, decision: 'accept', reason: 'capsule promoted after validation' });
|
||||
a2aProto.getTransport().send(dm);
|
||||
} catch (e) {}
|
||||
}
|
||||
process.stdout.write('promoted_capsule=' + id + '\n');
|
||||
return;
|
||||
}
|
||||
|
||||
var localGenes = assetStore.loadGenes();
|
||||
var exists = false;
|
||||
for (var k = 0; k < localGenes.length; k++) {
|
||||
if (localGenes[k] && localGenes[k].type === 'Gene' && String(localGenes[k].id) === id) { exists = true; break; }
|
||||
}
|
||||
if (exists) {
|
||||
if (emitDecisions) {
|
||||
try {
|
||||
var dm2 = a2aProto.buildDecision({ assetId: promoted.asset_id, localId: id, decision: 'reject', reason: 'local gene with same ID already exists' });
|
||||
a2aProto.getTransport().send(dm2);
|
||||
} catch (e) {}
|
||||
}
|
||||
process.stdout.write('conflict_keep_local_gene=' + id + '\n');
|
||||
return;
|
||||
}
|
||||
|
||||
assetStore.upsertGene(promoted);
|
||||
if (emitDecisions) {
|
||||
try {
|
||||
var dm3 = a2aProto.buildDecision({ assetId: promoted.asset_id, localId: id, decision: 'accept', reason: 'gene promoted after safety audit' });
|
||||
a2aProto.getTransport().send(dm3);
|
||||
} catch (e) {}
|
||||
}
|
||||
process.stdout.write('promoted_gene=' + id + '\n');
|
||||
}
|
||||
|
||||
try { main(); } catch (e) {
|
||||
process.stderr.write((e && e.message ? e.message : String(e)) + '\n');
|
||||
process.exit(1);
|
||||
}
|
||||
121
archive/inactive-skills/evolver/scripts/analyze_by_skill.js
Normal file
121
archive/inactive-skills/evolver/scripts/analyze_by_skill.js
Normal file
@@ -0,0 +1,121 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const REPO_ROOT = path.resolve(__dirname, '..');
|
||||
const LOG_FILE = path.join(REPO_ROOT, 'evolution_history_full.md');
|
||||
const OUT_FILE = path.join(REPO_ROOT, 'evolution_detailed_report.md');
|
||||
|
||||
function analyzeEvolution() {
|
||||
if (!fs.existsSync(LOG_FILE)) {
|
||||
console.error("Source file missing.");
|
||||
return;
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(LOG_FILE, 'utf8');
|
||||
// Split by divider
|
||||
const entries = content.split('---').map(e => e.trim()).filter(e => e.length > 0);
|
||||
|
||||
const skillUpdates = {}; // Map<SkillName, Array<Changes>>
|
||||
const generalUpdates = []; // Array<Changes>
|
||||
|
||||
// Regex to detect skills/paths
|
||||
// e.g. `skills/feishu-card/send.js` or **Target**: `skills/git-sync`
|
||||
const skillRegex = /skills\/([a-zA-Z0-9\-_]+)/;
|
||||
const actionRegex = /Action:\s*([\s\S]*?)(?=\n\n|\n[A-Z]|$)/i; // Capture Action text
|
||||
const statusRegex = /Status:\s*\[?([A-Z\s_]+)\]?/i;
|
||||
|
||||
entries.forEach(entry => {
|
||||
// Extract basic info
|
||||
const statusMatch = entry.match(statusRegex);
|
||||
const status = statusMatch ? statusMatch[1].trim().toUpperCase() : 'UNKNOWN';
|
||||
|
||||
// Skip routine checks if we want a *detailed evolution* report (focus on changes)
|
||||
// But user asked for "what happened", so routine scans might be boring unless they found something.
|
||||
// Let's filter out "STABILITY" or "RUNNING" unless there is a clear "Mutated" or "Fixed" keyword.
|
||||
const isInteresting =
|
||||
entry.includes('Fixed') ||
|
||||
entry.includes('Hardened') ||
|
||||
entry.includes('Optimized') ||
|
||||
entry.includes('Patched') ||
|
||||
entry.includes('Created') ||
|
||||
entry.includes('Added') ||
|
||||
status === 'SUCCESS' ||
|
||||
status === 'COMPLETED';
|
||||
|
||||
if (!isInteresting) return;
|
||||
|
||||
// Find associated skill
|
||||
const skillMatch = entry.match(skillRegex);
|
||||
let skillName = 'General / System';
|
||||
if (skillMatch) {
|
||||
skillName = skillMatch[1];
|
||||
} else {
|
||||
// Try heuristics
|
||||
if (entry.toLowerCase().includes('feishu card')) skillName = 'feishu-card';
|
||||
else if (entry.toLowerCase().includes('git sync')) skillName = 'git-sync';
|
||||
else if (entry.toLowerCase().includes('logger')) skillName = 'interaction-logger';
|
||||
else if (entry.toLowerCase().includes('evolve')) skillName = 'capability-evolver';
|
||||
}
|
||||
|
||||
// Extract description
|
||||
let description = "";
|
||||
const actionMatch = entry.match(actionRegex);
|
||||
if (actionMatch) {
|
||||
description = actionMatch[1].trim();
|
||||
} else {
|
||||
// Fallback: take lines that look like bullet points or text after header
|
||||
const lines = entry.split('\n');
|
||||
description = lines.filter(l => l.match(/^[•\-\*]|\w/)).slice(1).join('\n').trim();
|
||||
}
|
||||
|
||||
// Clean up description (remove duplicate "Action:" prefix if captured)
|
||||
description = description.replace(/^Action:\s*/i, '');
|
||||
|
||||
if (!skillUpdates[skillName]) skillUpdates[skillName] = [];
|
||||
|
||||
// Dedup descriptions slightly (simple check)
|
||||
const isDuplicate = skillUpdates[skillName].some(u => u.desc.includes(description.substring(0, 20)));
|
||||
if (!isDuplicate) {
|
||||
// Extract Date if possible
|
||||
const dateMatch = entry.match(/\((\d{4}\/\d{1,2}\/\d{1,2}.*?)\)/);
|
||||
const date = dateMatch ? dateMatch[1] : 'Unknown';
|
||||
|
||||
skillUpdates[skillName].push({
|
||||
date,
|
||||
status,
|
||||
desc: description
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Generate Markdown
|
||||
let md = "# Detailed Evolution Report (By Skill)\n\n> Comprehensive breakdown of system changes.\n\n";
|
||||
|
||||
// Sort skills alphabetically
|
||||
const sortedSkills = Object.keys(skillUpdates).sort();
|
||||
|
||||
sortedSkills.forEach(skill => {
|
||||
md += `## ${skill}\n`;
|
||||
const updates = skillUpdates[skill];
|
||||
|
||||
updates.forEach(u => {
|
||||
// Icon based on content
|
||||
let icon = '*';
|
||||
const lowerDesc = u.desc.toLowerCase();
|
||||
if (lowerDesc.includes('optimiz')) icon = '[optimize]';
|
||||
if (lowerDesc.includes('secur') || lowerDesc.includes('harden') || lowerDesc.includes('permission')) icon = '[security]';
|
||||
if (lowerDesc.includes('fix') || lowerDesc.includes('patch')) icon = '[repair]';
|
||||
if (lowerDesc.includes('creat') || lowerDesc.includes('add')) icon = '[add]';
|
||||
|
||||
md += `### ${icon} ${u.date}\n`;
|
||||
md += `${u.desc}\n\n`;
|
||||
});
|
||||
md += `---\n`;
|
||||
});
|
||||
|
||||
fs.writeFileSync(OUT_FILE, md);
|
||||
console.log(`Generated report for ${sortedSkills.length} skills.`);
|
||||
}
|
||||
|
||||
analyzeEvolution();
|
||||
|
||||
354
archive/inactive-skills/evolver/scripts/build_public.js
Normal file
354
archive/inactive-skills/evolver/scripts/build_public.js
Normal file
@@ -0,0 +1,354 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
const REPO_ROOT = path.resolve(__dirname, '..');
|
||||
|
||||
function readJson(filePath) {
|
||||
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||
}
|
||||
|
||||
function ensureDir(dir) {
|
||||
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
|
||||
function rmDir(dir) {
|
||||
if (!fs.existsSync(dir)) return;
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
function normalizePosix(p) {
|
||||
return p.split(path.sep).join('/');
|
||||
}
|
||||
|
||||
function isUnder(child, parent) {
|
||||
const rel = path.relative(parent, child);
|
||||
return !!rel && !rel.startsWith('..') && !path.isAbsolute(rel);
|
||||
}
|
||||
|
||||
function listFilesRec(dir) {
|
||||
const out = [];
|
||||
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
for (const ent of entries) {
|
||||
const p = path.join(dir, ent.name);
|
||||
if (ent.isDirectory()) out.push(...listFilesRec(p));
|
||||
else if (ent.isFile()) out.push(p);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function globToRegex(glob) {
|
||||
// Supports "*" within a single segment and "**" for any depth.
|
||||
const norm = normalizePosix(glob);
|
||||
const parts = norm.split('/').filter(p => p.length > 0);
|
||||
const out = [];
|
||||
|
||||
for (const part of parts) {
|
||||
if (part === '**') {
|
||||
// any number of path segments
|
||||
out.push('(?:.*)');
|
||||
continue;
|
||||
}
|
||||
// Escape regex special chars, then expand "*" wildcards within segment.
|
||||
const esc = part.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '[^/]*');
|
||||
out.push(esc);
|
||||
}
|
||||
|
||||
const re = out.join('\\/');
|
||||
return new RegExp(`^${re}$`);
|
||||
}
|
||||
|
||||
function matchesAnyGlobs(relPath, globs) {
|
||||
const p = normalizePosix(relPath);
|
||||
for (const g of globs || []) {
|
||||
const re = globToRegex(g);
|
||||
if (re.test(p)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function copyFile(srcAbs, destAbs) {
|
||||
ensureDir(path.dirname(destAbs));
|
||||
fs.copyFileSync(srcAbs, destAbs);
|
||||
}
|
||||
|
||||
function copyEntry(spec, outDirAbs) {
|
||||
const copied = [];
|
||||
|
||||
// Directory glob
|
||||
if (spec.includes('*')) {
|
||||
const all = listFilesRec(REPO_ROOT);
|
||||
const includeRe = globToRegex(spec);
|
||||
for (const abs of all) {
|
||||
const rel = normalizePosix(path.relative(REPO_ROOT, abs));
|
||||
if (!includeRe.test(rel)) continue;
|
||||
const destAbs = path.join(outDirAbs, rel);
|
||||
copyFile(abs, destAbs);
|
||||
copied.push(rel);
|
||||
}
|
||||
return copied;
|
||||
}
|
||||
|
||||
const srcAbs = path.join(REPO_ROOT, spec);
|
||||
if (!fs.existsSync(srcAbs)) return [];
|
||||
|
||||
const st = fs.statSync(srcAbs);
|
||||
if (st.isFile()) {
|
||||
const rel = normalizePosix(spec);
|
||||
copyFile(srcAbs, path.join(outDirAbs, rel));
|
||||
copied.push(rel);
|
||||
return copied;
|
||||
}
|
||||
if (st.isDirectory()) {
|
||||
const files = listFilesRec(srcAbs);
|
||||
for (const abs of files) {
|
||||
const rel = normalizePosix(path.relative(REPO_ROOT, abs));
|
||||
copyFile(abs, path.join(outDirAbs, rel));
|
||||
copied.push(rel);
|
||||
}
|
||||
}
|
||||
return copied;
|
||||
}
|
||||
|
||||
function applyRewrite(outDirAbs, rewrite) {
|
||||
const rules = rewrite || {};
|
||||
for (const [relFile, cfg] of Object.entries(rules)) {
|
||||
const target = path.join(outDirAbs, relFile);
|
||||
if (!fs.existsSync(target)) continue;
|
||||
let content = fs.readFileSync(target, 'utf8');
|
||||
const reps = (cfg && cfg.replace) || [];
|
||||
for (const r of reps) {
|
||||
const from = String(r.from || '');
|
||||
const to = String(r.to || '');
|
||||
if (!from) continue;
|
||||
content = content.split(from).join(to);
|
||||
}
|
||||
fs.writeFileSync(target, content, 'utf8');
|
||||
}
|
||||
}
|
||||
|
||||
function rewritePackageJson(outDirAbs) {
|
||||
const p = path.join(outDirAbs, 'package.json');
|
||||
if (!fs.existsSync(p)) return;
|
||||
try {
|
||||
const pkg = JSON.parse(fs.readFileSync(p, 'utf8'));
|
||||
pkg.scripts = {
|
||||
start: 'node index.js',
|
||||
run: 'node index.js run',
|
||||
solidify: 'node index.js solidify',
|
||||
'a2a:export': 'node scripts/a2a_export.js',
|
||||
'a2a:ingest': 'node scripts/a2a_ingest.js',
|
||||
'a2a:promote': 'node scripts/a2a_promote.js',
|
||||
};
|
||||
fs.writeFileSync(p, JSON.stringify(pkg, null, 2) + '\n', 'utf8');
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
function parseSemver(v) {
|
||||
const m = String(v || '').trim().match(/^(\d+)\.(\d+)\.(\d+)$/);
|
||||
if (!m) return null;
|
||||
return { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3]) };
|
||||
}
|
||||
|
||||
function formatSemver(x) {
|
||||
return `${x.major}.${x.minor}.${x.patch}`;
|
||||
}
|
||||
|
||||
function bumpSemver(base, bump) {
|
||||
const v = parseSemver(base);
|
||||
if (!v) return null;
|
||||
if (bump === 'major') return `${v.major + 1}.0.0`;
|
||||
if (bump === 'minor') return `${v.major}.${v.minor + 1}.0`;
|
||||
if (bump === 'patch') return `${v.major}.${v.minor}.${v.patch + 1}`;
|
||||
return formatSemver(v);
|
||||
}
|
||||
|
||||
function git(cmd) {
|
||||
return execSync(cmd, { cwd: REPO_ROOT, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
||||
}
|
||||
|
||||
function getBaseReleaseCommit() {
|
||||
// Prefer last "prepare vX.Y.Z" commit; fallback to HEAD~50 range later.
|
||||
try {
|
||||
const hash = git('git log -n 1 --pretty=%H --grep="chore(release): prepare v"');
|
||||
return hash || null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getCommitSubjectsSince(baseCommit) {
|
||||
try {
|
||||
if (!baseCommit) {
|
||||
const out = git('git log -n 30 --pretty=%s');
|
||||
return out ? out.split('\n').filter(Boolean) : [];
|
||||
}
|
||||
const out = git(`git log ${baseCommit}..HEAD --pretty=%s`);
|
||||
return out ? out.split('\n').filter(Boolean) : [];
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function inferBumpFromSubjects(subjects) {
|
||||
const subs = (subjects || []).map(s => String(s));
|
||||
const hasBreaking = subs.some(s => /\bBREAKING CHANGE\b/i.test(s) || /^[a-z]+(\(.+\))?!:/.test(s));
|
||||
if (hasBreaking) return { bump: 'major', reason: 'breaking change marker in commit subject' };
|
||||
|
||||
const hasFeat = subs.some(s => /^feat(\(.+\))?:/i.test(s));
|
||||
if (hasFeat) return { bump: 'minor', reason: 'feature commit detected (feat:)' };
|
||||
|
||||
const hasFix = subs.some(s => /^(fix|perf)(\(.+\))?:/i.test(s));
|
||||
if (hasFix) return { bump: 'patch', reason: 'fix/perf commit detected' };
|
||||
|
||||
if (subs.length === 0) return { bump: 'none', reason: 'no commits since base release commit' };
|
||||
return { bump: 'patch', reason: 'default to patch for non-breaking changes' };
|
||||
}
|
||||
|
||||
function suggestVersion() {
|
||||
const pkgPath = path.join(REPO_ROOT, 'package.json');
|
||||
let baseVersion = null;
|
||||
try {
|
||||
baseVersion = JSON.parse(fs.readFileSync(pkgPath, 'utf8')).version;
|
||||
} catch (e) {}
|
||||
|
||||
const baseCommit = getBaseReleaseCommit();
|
||||
const subjects = getCommitSubjectsSince(baseCommit);
|
||||
const decision = inferBumpFromSubjects(subjects);
|
||||
|
||||
let suggested = null;
|
||||
if (decision.bump === 'none') suggested = baseVersion;
|
||||
else suggested = bumpSemver(baseVersion, decision.bump);
|
||||
|
||||
return { baseVersion, baseCommit, subjects, decision, suggestedVersion: suggested };
|
||||
}
|
||||
|
||||
function writePrivateSemverNote(note) {
|
||||
const privateDir = path.join(REPO_ROOT, 'memory');
|
||||
ensureDir(privateDir);
|
||||
fs.writeFileSync(path.join(privateDir, 'semver_suggestion.json'), JSON.stringify(note, null, 2) + '\n', 'utf8');
|
||||
}
|
||||
|
||||
function writePrivateSemverPrompt(note) {
|
||||
const privateDir = path.join(REPO_ROOT, 'memory');
|
||||
ensureDir(privateDir);
|
||||
const subjects = Array.isArray(note.subjects) ? note.subjects : [];
|
||||
const semverRule = [
|
||||
'MAJOR.MINOR.PATCH',
|
||||
'- MAJOR: incompatible changes',
|
||||
'- MINOR: backward-compatible features',
|
||||
'- PATCH: backward-compatible bug fixes',
|
||||
].join('\n');
|
||||
|
||||
const prompt = [
|
||||
'You are a release versioning assistant.',
|
||||
'Decide the next version bump using SemVer rules below.',
|
||||
'',
|
||||
semverRule,
|
||||
'',
|
||||
`Base version: ${note.baseVersion || '(unknown)'}`,
|
||||
`Base commit: ${note.baseCommit || '(unknown)'}`,
|
||||
'',
|
||||
'Recent commit subjects (newest first):',
|
||||
...subjects.map(s => `- ${s}`),
|
||||
'',
|
||||
'Output JSON only:',
|
||||
'{ "bump": "major|minor|patch|none", "suggestedVersion": "x.y.z", "reason": ["..."] }',
|
||||
].join('\n');
|
||||
|
||||
fs.writeFileSync(path.join(privateDir, 'semver_prompt.md'), prompt + '\n', 'utf8');
|
||||
}
|
||||
|
||||
function writeDistVersion(outDirAbs, version) {
|
||||
if (!version) return;
|
||||
const p = path.join(outDirAbs, 'package.json');
|
||||
if (!fs.existsSync(p)) return;
|
||||
try {
|
||||
const pkg = JSON.parse(fs.readFileSync(p, 'utf8'));
|
||||
pkg.version = version;
|
||||
fs.writeFileSync(p, JSON.stringify(pkg, null, 2) + '\n', 'utf8');
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function pruneExcluded(outDirAbs, excludeGlobs) {
|
||||
const all = listFilesRec(outDirAbs);
|
||||
for (const abs of all) {
|
||||
const rel = normalizePosix(path.relative(outDirAbs, abs));
|
||||
if (matchesAnyGlobs(rel, excludeGlobs)) {
|
||||
fs.rmSync(abs, { force: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function validateNoPrivatePaths(outDirAbs) {
|
||||
// Basic safeguard: forbid docs/ and memory/ in output.
|
||||
const forbiddenPrefixes = ['docs/', 'memory/'];
|
||||
const all = listFilesRec(outDirAbs);
|
||||
for (const abs of all) {
|
||||
const rel = normalizePosix(path.relative(outDirAbs, abs));
|
||||
for (const pref of forbiddenPrefixes) {
|
||||
if (rel.startsWith(pref)) {
|
||||
throw new Error(`Build validation failed: forbidden path in output: ${rel}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
const manifestPath = path.join(REPO_ROOT, 'public.manifest.json');
|
||||
const manifest = readJson(manifestPath);
|
||||
const outDir = String(manifest.outDir || 'dist-public');
|
||||
const outDirAbs = path.join(REPO_ROOT, outDir);
|
||||
|
||||
// SemVer suggestion (private). This does not modify the source repo version.
|
||||
const semver = suggestVersion();
|
||||
writePrivateSemverNote(semver);
|
||||
writePrivateSemverPrompt(semver);
|
||||
|
||||
rmDir(outDirAbs);
|
||||
ensureDir(outDirAbs);
|
||||
|
||||
const include = manifest.include || [];
|
||||
const exclude = manifest.exclude || [];
|
||||
|
||||
const copied = [];
|
||||
for (const spec of include) {
|
||||
copied.push(...copyEntry(spec, outDirAbs));
|
||||
}
|
||||
|
||||
pruneExcluded(outDirAbs, exclude);
|
||||
applyRewrite(outDirAbs, manifest.rewrite);
|
||||
rewritePackageJson(outDirAbs);
|
||||
|
||||
// Prefer explicit version; otherwise use suggested version.
|
||||
const releaseVersion = process.env.RELEASE_VERSION || semver.suggestedVersion;
|
||||
if (releaseVersion) writeDistVersion(outDirAbs, releaseVersion);
|
||||
|
||||
validateNoPrivatePaths(outDirAbs);
|
||||
|
||||
// Write build manifest for private verification (do not include in dist-public/).
|
||||
const buildInfo = {
|
||||
built_at: new Date().toISOString(),
|
||||
outDir,
|
||||
files: copied.sort(),
|
||||
};
|
||||
const privateDir = path.join(REPO_ROOT, 'memory');
|
||||
ensureDir(privateDir);
|
||||
fs.writeFileSync(path.join(privateDir, 'public_build_info.json'), JSON.stringify(buildInfo, null, 2) + '\n', 'utf8');
|
||||
|
||||
process.stdout.write(`Built public output at ${outDir}\n`);
|
||||
if (semver && semver.suggestedVersion) {
|
||||
process.stdout.write(`Suggested version: ${semver.suggestedVersion}\n`);
|
||||
process.stdout.write(`SemVer decision: ${semver.decision ? semver.decision.bump : 'unknown'}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
main();
|
||||
} catch (e) {
|
||||
process.stderr.write(`${e.message}\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
85
archive/inactive-skills/evolver/scripts/extract_log.js
Normal file
85
archive/inactive-skills/evolver/scripts/extract_log.js
Normal file
@@ -0,0 +1,85 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const REPO_ROOT = path.resolve(__dirname, '..');
|
||||
const LOG_FILE = path.join(REPO_ROOT, 'memory', 'mad_dog_evolution.log');
|
||||
const OUT_FILE = path.join(REPO_ROOT, 'evolution_history.md');
|
||||
|
||||
function parseLog() {
|
||||
if (!fs.existsSync(LOG_FILE)) {
|
||||
console.log("Log file not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(LOG_FILE, 'utf8');
|
||||
const lines = content.split('\n');
|
||||
|
||||
const reports = [];
|
||||
let currentTimestamp = null;
|
||||
|
||||
// Regex for Feishu command
|
||||
// node skills/feishu-card/send.js --title "..." --color ... --text "..."
|
||||
const cmdRegex = /node skills\/feishu-card\/send\.js --title "(.*?)" --color \w+ --text "(.*?)"/;
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
|
||||
// 1. Capture Timestamp
|
||||
if (line.includes('Cycle Start:')) {
|
||||
// Format: Cycle Start: Sun Feb 1 19:17:44 UTC 2026
|
||||
const dateStr = line.split('Cycle Start: ')[1].trim();
|
||||
try {
|
||||
currentTimestamp = new Date(dateStr);
|
||||
} catch (e) {
|
||||
currentTimestamp = null;
|
||||
}
|
||||
}
|
||||
|
||||
const match = line.match(cmdRegex);
|
||||
if (match) {
|
||||
const title = match[1];
|
||||
let text = match[2];
|
||||
|
||||
// Clean up text (unescape newlines)
|
||||
text = text.replace(/\\n/g, '\n').replace(/\\"/g, '"');
|
||||
|
||||
if (currentTimestamp) {
|
||||
reports.push({
|
||||
ts: currentTimestamp,
|
||||
title: title,
|
||||
text: text,
|
||||
id: title // Cycle ID is in title
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Deduplicate by ID (keep latest timestamp?)
|
||||
const uniqueReports = {};
|
||||
reports.forEach(r => {
|
||||
uniqueReports[r.id] = r;
|
||||
});
|
||||
|
||||
const sortedReports = Object.values(uniqueReports).sort((a, b) => a.ts - b.ts);
|
||||
|
||||
let md = "# Evolution History (Extracted)\n\n";
|
||||
sortedReports.forEach(r => {
|
||||
// Convert to CST (UTC+8)
|
||||
const cstDate = r.ts.toLocaleString("zh-CN", {
|
||||
timeZone: "Asia/Shanghai",
|
||||
hour12: false,
|
||||
year: 'numeric', month: '2-digit', day: '2-digit',
|
||||
hour: '2-digit', minute: '2-digit', second: '2-digit'
|
||||
});
|
||||
|
||||
md += `### ${r.title} (${cstDate})\n`;
|
||||
md += `${r.text}\n\n`;
|
||||
md += `---\n\n`;
|
||||
});
|
||||
|
||||
fs.writeFileSync(OUT_FILE, md);
|
||||
console.log(`Extracted ${sortedReports.length} reports to ${OUT_FILE}`);
|
||||
}
|
||||
|
||||
parseLog();
|
||||
|
||||
75
archive/inactive-skills/evolver/scripts/generate_history.js
Normal file
75
archive/inactive-skills/evolver/scripts/generate_history.js
Normal file
@@ -0,0 +1,75 @@
|
||||
const { execSync } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Separator for git log parsing (something unlikely to be in commit messages)
|
||||
const SEP = '|||';
|
||||
const REPO_ROOT = path.resolve(__dirname, '..');
|
||||
|
||||
try {
|
||||
// Git command:
|
||||
// --reverse: Oldest to Newest (Time Sequence)
|
||||
// --grep: Filter by keyword
|
||||
// --format: Hash, Date (ISO), Author, Subject, Body
|
||||
const cmd = `git log --reverse --grep="Evolution" --format="%H${SEP}%ai${SEP}%an${SEP}%s${SEP}%b"`;
|
||||
|
||||
console.log('Executing git log...');
|
||||
const output = execSync(cmd, {
|
||||
encoding: 'utf8',
|
||||
cwd: REPO_ROOT,
|
||||
maxBuffer: 1024 * 1024 * 10 // 10MB buffer just in case
|
||||
});
|
||||
|
||||
const entries = output.split('\n').filter(line => line.trim().length > 0);
|
||||
|
||||
let markdown = '# Evolution History (Time Sequence)\n\n';
|
||||
markdown += '> Filter: "Evolution"\n';
|
||||
markdown += '> Timezone: CST (UTC+8)\n\n';
|
||||
|
||||
let count = 0;
|
||||
|
||||
entries.forEach(entry => {
|
||||
const parts = entry.split(SEP);
|
||||
if (parts.length < 4) return;
|
||||
|
||||
const hash = parts[0];
|
||||
const dateStr = parts[1];
|
||||
const author = parts[2];
|
||||
const subject = parts[3];
|
||||
const body = parts[4] || '';
|
||||
|
||||
// Parse Date and Convert to UTC+8
|
||||
const date = new Date(dateStr);
|
||||
// Add 8 hours (28800000 ms) to UTC timestamp to shift it
|
||||
// Then formatting it as ISO will look like UTC but represent CST values
|
||||
const cstDate = new Date(date.getTime() + 8 * 60 * 60 * 1000);
|
||||
|
||||
// Format: YYYY-MM-DD HH:mm:ss
|
||||
const timeStr = cstDate.toISOString().replace('T', ' ').substring(0, 19);
|
||||
|
||||
markdown += `## ${timeStr}\n`;
|
||||
markdown += `- Commit: \`${hash.substring(0, 7)}\`\n`;
|
||||
markdown += `- Subject: ${subject}\n`;
|
||||
|
||||
if (body.trim()) {
|
||||
// Indent body for better readability
|
||||
const formattedBody = body.trim().split('\n').map(l => `> ${l}`).join('\n');
|
||||
markdown += `- Details:\n${formattedBody}\n`;
|
||||
}
|
||||
markdown += '\n';
|
||||
count++;
|
||||
});
|
||||
|
||||
const outDir = path.join(REPO_ROOT, 'memory');
|
||||
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
|
||||
const outPath = path.join(outDir, 'evolution_history.md');
|
||||
fs.writeFileSync(outPath, markdown);
|
||||
|
||||
console.log(`Successfully generated report with ${count} entries.`);
|
||||
console.log(`Saved to: ${outPath}`);
|
||||
|
||||
} catch (e) {
|
||||
console.error('Error generating history:', e.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
96
archive/inactive-skills/evolver/scripts/gep_append_event.js
Normal file
96
archive/inactive-skills/evolver/scripts/gep_append_event.js
Normal file
@@ -0,0 +1,96 @@
|
||||
const fs = require('fs');
|
||||
const { appendEventJsonl } = require('../src/gep/assetStore');
|
||||
|
||||
function readStdin() {
|
||||
try {
|
||||
return fs.readFileSync(0, 'utf8');
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function readTextIfExists(p) {
|
||||
try {
|
||||
if (!p) return '';
|
||||
if (!fs.existsSync(p)) return '';
|
||||
return fs.readFileSync(p, 'utf8');
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function parseInput(text) {
|
||||
const raw = String(text || '').trim();
|
||||
if (!raw) return [];
|
||||
|
||||
// Accept JSON array or single JSON.
|
||||
try {
|
||||
const maybe = JSON.parse(raw);
|
||||
if (Array.isArray(maybe)) return maybe;
|
||||
if (maybe && typeof maybe === 'object') return [maybe];
|
||||
} catch (e) {}
|
||||
|
||||
// Fallback: JSONL.
|
||||
const lines = raw.split('\n').map(l => l.trim()).filter(Boolean);
|
||||
const out = [];
|
||||
for (const line of lines) {
|
||||
try {
|
||||
const obj = JSON.parse(line);
|
||||
out.push(obj);
|
||||
} catch (e) {}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function isValidEvolutionEvent(ev) {
|
||||
if (!ev || ev.type !== 'EvolutionEvent') return false;
|
||||
if (!ev.id || typeof ev.id !== 'string') return false;
|
||||
// parent may be null or string
|
||||
if (!(ev.parent === null || typeof ev.parent === 'string')) return false;
|
||||
if (!ev.intent || typeof ev.intent !== 'string') return false;
|
||||
if (!Array.isArray(ev.signals)) return false;
|
||||
if (!Array.isArray(ev.genes_used)) return false;
|
||||
// GEP v1.4: mutation + personality are mandatory evolution dimensions
|
||||
if (!ev.mutation_id || typeof ev.mutation_id !== 'string') return false;
|
||||
if (!ev.personality_state || typeof ev.personality_state !== 'object') return false;
|
||||
if (ev.personality_state.type !== 'PersonalityState') return false;
|
||||
for (const k of ['rigor', 'creativity', 'verbosity', 'risk_tolerance', 'obedience']) {
|
||||
const v = Number(ev.personality_state[k]);
|
||||
if (!Number.isFinite(v) || v < 0 || v > 1) return false;
|
||||
}
|
||||
if (!ev.blast_radius || typeof ev.blast_radius !== 'object') return false;
|
||||
if (!Number.isFinite(Number(ev.blast_radius.files))) return false;
|
||||
if (!Number.isFinite(Number(ev.blast_radius.lines))) return false;
|
||||
if (!ev.outcome || typeof ev.outcome !== 'object') return false;
|
||||
if (!ev.outcome.status || typeof ev.outcome.status !== 'string') return false;
|
||||
const score = Number(ev.outcome.score);
|
||||
if (!Number.isFinite(score) || score < 0 || score > 1) return false;
|
||||
|
||||
// capsule_id is optional, but if present must be string or null.
|
||||
if (!('capsule_id' in ev)) return true;
|
||||
return ev.capsule_id === null || typeof ev.capsule_id === 'string';
|
||||
}
|
||||
|
||||
function main() {
|
||||
const args = process.argv.slice(2);
|
||||
const inputPath = args.find(a => a && !a.startsWith('--')) || '';
|
||||
const text = inputPath ? readTextIfExists(inputPath) : readStdin();
|
||||
const items = parseInput(text);
|
||||
|
||||
let appended = 0;
|
||||
for (const it of items) {
|
||||
if (!isValidEvolutionEvent(it)) continue;
|
||||
appendEventJsonl(it);
|
||||
appended += 1;
|
||||
}
|
||||
|
||||
process.stdout.write(`appended=${appended}\n`);
|
||||
}
|
||||
|
||||
try {
|
||||
main();
|
||||
} catch (e) {
|
||||
process.stderr.write(`${e && e.message ? e.message : String(e)}\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { getRepoRoot, getMemoryDir, getGepAssetsDir } = require('../src/gep/paths');
|
||||
const { normalizePersonalityState, personalityKey, defaultPersonalityState } = require('../src/gep/personality');
|
||||
|
||||
function readJsonIfExists(p, fallback) {
|
||||
try {
|
||||
if (!fs.existsSync(p)) return fallback;
|
||||
const raw = fs.readFileSync(p, 'utf8');
|
||||
if (!raw.trim()) return fallback;
|
||||
return JSON.parse(raw);
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
function readJsonlIfExists(p, limitLines = 5000) {
|
||||
try {
|
||||
if (!fs.existsSync(p)) return [];
|
||||
const raw = fs.readFileSync(p, 'utf8');
|
||||
const lines = raw
|
||||
.split('\n')
|
||||
.map(l => l.trim())
|
||||
.filter(Boolean);
|
||||
const recent = lines.slice(Math.max(0, lines.length - limitLines));
|
||||
return recent
|
||||
.map(l => {
|
||||
try {
|
||||
return JSON.parse(l);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(Boolean);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function clamp01(x) {
|
||||
const n = Number(x);
|
||||
if (!Number.isFinite(n)) return 0;
|
||||
return Math.max(0, Math.min(1, n));
|
||||
}
|
||||
|
||||
function pct(x) {
|
||||
const n = Number(x);
|
||||
if (!Number.isFinite(n)) return '0.0%';
|
||||
return `${(n * 100).toFixed(1)}%`;
|
||||
}
|
||||
|
||||
function pad(s, n) {
|
||||
const str = String(s == null ? '' : s);
|
||||
if (str.length >= n) return str.slice(0, n);
|
||||
return str + ' '.repeat(n - str.length);
|
||||
}
|
||||
|
||||
function scoreFromCounts(success, fail, avgScore) {
|
||||
const succ = Number(success) || 0;
|
||||
const fl = Number(fail) || 0;
|
||||
const total = succ + fl;
|
||||
const p = (succ + 1) / (total + 2); // Laplace smoothing
|
||||
const sampleWeight = Math.min(1, total / 8);
|
||||
const q = avgScore == null ? 0.5 : clamp01(avgScore);
|
||||
return p * 0.75 + q * 0.25 * sampleWeight;
|
||||
}
|
||||
|
||||
function aggregateFromEvents(events) {
|
||||
const map = new Map();
|
||||
for (const ev of Array.isArray(events) ? events : []) {
|
||||
if (!ev || ev.type !== 'EvolutionEvent') continue;
|
||||
const ps = ev.personality_state && typeof ev.personality_state === 'object' ? ev.personality_state : null;
|
||||
if (!ps) continue;
|
||||
const key = personalityKey(normalizePersonalityState(ps));
|
||||
const cur = map.get(key) || {
|
||||
key,
|
||||
success: 0,
|
||||
fail: 0,
|
||||
n: 0,
|
||||
avg_score: 0.5,
|
||||
last_event_id: null,
|
||||
last_at: null,
|
||||
mutation: { repair: 0, optimize: 0, innovate: 0 },
|
||||
mutation_success: { repair: 0, optimize: 0, innovate: 0 },
|
||||
};
|
||||
const st = ev.outcome && ev.outcome.status ? String(ev.outcome.status) : 'unknown';
|
||||
if (st === 'success') cur.success += 1;
|
||||
else if (st === 'failed') cur.fail += 1;
|
||||
|
||||
const sc = ev.outcome && Number.isFinite(Number(ev.outcome.score)) ? clamp01(Number(ev.outcome.score)) : null;
|
||||
if (sc != null) {
|
||||
cur.n += 1;
|
||||
cur.avg_score = cur.avg_score + (sc - cur.avg_score) / cur.n;
|
||||
}
|
||||
|
||||
const cat = ev.intent ? String(ev.intent) : null;
|
||||
if (cat && cur.mutation[cat] != null) {
|
||||
cur.mutation[cat] += 1;
|
||||
if (st === 'success') cur.mutation_success[cat] += 1;
|
||||
}
|
||||
|
||||
cur.last_event_id = ev.id || cur.last_event_id;
|
||||
const at = ev.meta && ev.meta.at ? String(ev.meta.at) : null;
|
||||
cur.last_at = at || cur.last_at;
|
||||
map.set(key, cur);
|
||||
}
|
||||
return Array.from(map.values());
|
||||
}
|
||||
|
||||
function main() {
|
||||
const repoRoot = getRepoRoot();
|
||||
const memoryDir = getMemoryDir();
|
||||
const assetsDir = getGepAssetsDir();
|
||||
|
||||
const personalityPath = path.join(memoryDir, 'personality_state.json');
|
||||
const model = readJsonIfExists(personalityPath, null);
|
||||
const current = model && model.current ? normalizePersonalityState(model.current) : defaultPersonalityState();
|
||||
const currentKey = personalityKey(current);
|
||||
|
||||
const eventsPath = path.join(assetsDir, 'events.jsonl');
|
||||
const events = readJsonlIfExists(eventsPath, 10000);
|
||||
const evs = events.filter(e => e && e.type === 'EvolutionEvent');
|
||||
const agg = aggregateFromEvents(evs);
|
||||
|
||||
// Prefer model.stats if present, but still show event-derived aggregation (ground truth).
|
||||
const stats = model && model.stats && typeof model.stats === 'object' ? model.stats : {};
|
||||
const statRows = Object.entries(stats).map(([key, e]) => {
|
||||
const entry = e && typeof e === 'object' ? e : {};
|
||||
const success = Number(entry.success) || 0;
|
||||
const fail = Number(entry.fail) || 0;
|
||||
const total = success + fail;
|
||||
const avg = Number.isFinite(Number(entry.avg_score)) ? clamp01(Number(entry.avg_score)) : null;
|
||||
const score = scoreFromCounts(success, fail, avg);
|
||||
return { key, success, fail, total, avg_score: avg, score, updated_at: entry.updated_at || null, source: 'model' };
|
||||
});
|
||||
|
||||
const evRows = agg.map(e => {
|
||||
const success = Number(e.success) || 0;
|
||||
const fail = Number(e.fail) || 0;
|
||||
const total = success + fail;
|
||||
const avg = Number.isFinite(Number(e.avg_score)) ? clamp01(Number(e.avg_score)) : null;
|
||||
const score = scoreFromCounts(success, fail, avg);
|
||||
return { key: e.key, success, fail, total, avg_score: avg, score, updated_at: e.last_at || null, source: 'events', _ev: e };
|
||||
});
|
||||
|
||||
// Merge rows by key (events take precedence for total/success/fail; model provides updated_at if events missing).
|
||||
const byKey = new Map();
|
||||
for (const r of [...statRows, ...evRows]) {
|
||||
const prev = byKey.get(r.key);
|
||||
if (!prev) {
|
||||
byKey.set(r.key, r);
|
||||
continue;
|
||||
}
|
||||
// Prefer events for counts and avg_score
|
||||
if (r.source === 'events') byKey.set(r.key, { ...prev, ...r });
|
||||
else byKey.set(r.key, { ...r, ...prev });
|
||||
}
|
||||
|
||||
const merged = Array.from(byKey.values()).sort((a, b) => b.score - a.score);
|
||||
|
||||
process.stdout.write(`Repo: ${repoRoot}\n`);
|
||||
process.stdout.write(`MemoryDir: ${memoryDir}\n`);
|
||||
process.stdout.write(`AssetsDir: ${assetsDir}\n\n`);
|
||||
|
||||
process.stdout.write(`[Current Personality]\n`);
|
||||
process.stdout.write(`${currentKey}\n`);
|
||||
process.stdout.write(`${JSON.stringify(current, null, 2)}\n\n`);
|
||||
|
||||
process.stdout.write(`[Personality Stats] (ranked by score)\n`);
|
||||
if (merged.length === 0) {
|
||||
process.stdout.write('(no stats yet; run a few cycles and solidify)\n');
|
||||
return;
|
||||
}
|
||||
|
||||
const header =
|
||||
pad('rank', 5) +
|
||||
pad('total', 8) +
|
||||
pad('succ', 8) +
|
||||
pad('fail', 8) +
|
||||
pad('succ_rate', 11) +
|
||||
pad('avg', 7) +
|
||||
pad('score', 8) +
|
||||
'key';
|
||||
process.stdout.write(header + '\n');
|
||||
process.stdout.write('-'.repeat(Math.min(140, header.length + 40)) + '\n');
|
||||
|
||||
const topN = Math.min(25, merged.length);
|
||||
for (let i = 0; i < topN; i++) {
|
||||
const r = merged[i];
|
||||
const succ = Number(r.success) || 0;
|
||||
const fail = Number(r.fail) || 0;
|
||||
const total = Number(r.total) || succ + fail;
|
||||
const succRate = total > 0 ? succ / total : 0;
|
||||
const avg = r.avg_score == null ? '-' : Number(r.avg_score).toFixed(2);
|
||||
const line =
|
||||
pad(String(i + 1), 5) +
|
||||
pad(String(total), 8) +
|
||||
pad(String(succ), 8) +
|
||||
pad(String(fail), 8) +
|
||||
pad(pct(succRate), 11) +
|
||||
pad(String(avg), 7) +
|
||||
pad(Number(r.score).toFixed(3), 8) +
|
||||
String(r.key);
|
||||
process.stdout.write(line + '\n');
|
||||
|
||||
if (r._ev) {
|
||||
const ev = r._ev;
|
||||
const ms = ev.mutation || {};
|
||||
const mSucc = ev.mutation_success || {};
|
||||
const parts = [];
|
||||
for (const cat of ['repair', 'optimize', 'innovate']) {
|
||||
const n = Number(ms[cat]) || 0;
|
||||
if (n <= 0) continue;
|
||||
const s = Number(mSucc[cat]) || 0;
|
||||
parts.push(`${cat}:${s}/${n}`);
|
||||
}
|
||||
if (parts.length) process.stdout.write(` mutation_success: ${parts.join(' | ')}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
process.stdout.write('\n');
|
||||
process.stdout.write(`[Notes]\n`);
|
||||
process.stdout.write(`- score is a smoothed composite of success_rate + avg_score (sample-weighted)\n`);
|
||||
process.stdout.write(`- current_key appears in the ranking once enough data accumulates\n`);
|
||||
}
|
||||
|
||||
try {
|
||||
main();
|
||||
} catch (e) {
|
||||
process.stderr.write((e && e.message) || String(e));
|
||||
process.stderr.write('\n');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
147
archive/inactive-skills/evolver/scripts/human_report.js
Normal file
147
archive/inactive-skills/evolver/scripts/human_report.js
Normal file
@@ -0,0 +1,147 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const REPO_ROOT = path.resolve(__dirname, '..');
|
||||
const IN_FILE = path.join(REPO_ROOT, 'evolution_history_full.md');
|
||||
const OUT_FILE = path.join(REPO_ROOT, 'evolution_human_summary.md');
|
||||
|
||||
function generateHumanReport() {
|
||||
if (!fs.existsSync(IN_FILE)) return console.error("No input file");
|
||||
|
||||
const content = fs.readFileSync(IN_FILE, 'utf8');
|
||||
const entries = content.split('---').map(e => e.trim()).filter(e => e.length > 0);
|
||||
|
||||
const categories = {
|
||||
'Security & Stability': [],
|
||||
'Performance & Optimization': [],
|
||||
'Tooling & Features': [],
|
||||
'Documentation & Process': []
|
||||
};
|
||||
|
||||
const componentMap = {}; // Component -> Change List
|
||||
|
||||
entries.forEach(entry => {
|
||||
// Extract basic info
|
||||
const lines = entry.split('\n');
|
||||
const header = lines[0]; // ### Title (Date)
|
||||
const body = lines.slice(1).join('\n');
|
||||
|
||||
const dateMatch = header.match(/\((.*?)\)/);
|
||||
const dateStr = dateMatch ? dateMatch[1] : '';
|
||||
const time = dateStr.split(' ')[1] || ''; // HH:mm:ss
|
||||
|
||||
// Classify
|
||||
let category = 'Tooling & Features';
|
||||
let component = 'System';
|
||||
let summary = '';
|
||||
|
||||
const lowerBody = body.toLowerCase();
|
||||
|
||||
// Detect Component
|
||||
if (lowerBody.includes('feishu-card')) component = 'feishu-card';
|
||||
else if (lowerBody.includes('feishu-sticker')) component = 'feishu-sticker';
|
||||
else if (lowerBody.includes('git-sync')) component = 'git-sync';
|
||||
else if (lowerBody.includes('capability-evolver') || lowerBody.includes('evolve.js')) component = 'capability-evolver';
|
||||
else if (lowerBody.includes('interaction-logger')) component = 'interaction-logger';
|
||||
else if (lowerBody.includes('chat-to-image')) component = 'chat-to-image';
|
||||
else if (lowerBody.includes('safe_publish')) component = 'capability-evolver';
|
||||
|
||||
// Detect Category
|
||||
if (lowerBody.includes('security') || lowerBody.includes('permission') || lowerBody.includes('auth') || lowerBody.includes('harden')) {
|
||||
category = 'Security & Stability';
|
||||
} else if (lowerBody.includes('optimiz') || lowerBody.includes('performance') || lowerBody.includes('memory') || lowerBody.includes('fast')) {
|
||||
category = 'Performance & Optimization';
|
||||
} else if (lowerBody.includes('doc') || lowerBody.includes('readme')) {
|
||||
category = 'Documentation & Process';
|
||||
}
|
||||
|
||||
// Extract Human Summary (First meaningful line that isn't Status/Action/Date)
|
||||
const summaryLines = lines.filter(l =>
|
||||
!l.startsWith('###') &&
|
||||
!l.startsWith('Status:') &&
|
||||
!l.startsWith('Action:') &&
|
||||
l.trim().length > 10
|
||||
);
|
||||
|
||||
if (summaryLines.length > 0) {
|
||||
// Clean up the line
|
||||
summary = summaryLines[0]
|
||||
.replace(/^-\s*/, '') // Remove bullets
|
||||
.replace(/\*\*/g, '') // Remove bold
|
||||
.replace(/`/, '')
|
||||
.trim();
|
||||
|
||||
// Deduplicate
|
||||
const key = `${component}:${summary.substring(0, 20)}`;
|
||||
const exists = categories[category].some(i => i.key === key);
|
||||
|
||||
if (!exists && !summary.includes("Stability Scan OK") && !summary.includes("Workspace Sync")) {
|
||||
categories[category].push({ time, component, summary, key });
|
||||
|
||||
if (!componentMap[component]) componentMap[component] = [];
|
||||
componentMap[component].push(summary);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// --- Generate Markdown ---
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
let md = `# Evolution Summary: The Day in Review (${today})\n\n`;
|
||||
md += `> Overview: Grouped summary of changes extracted from evolution history.\n\n`;
|
||||
|
||||
// Section 1: By Theme (Evolution Direction)
|
||||
md += `## 1. Evolution Direction\n`;
|
||||
|
||||
for (const [cat, items] of Object.entries(categories)) {
|
||||
if (items.length === 0) continue;
|
||||
md += `### ${cat}\n`;
|
||||
// Group by component within theme
|
||||
const compGroup = {};
|
||||
items.forEach(i => {
|
||||
if (!compGroup[i.component]) compGroup[i.component] = [];
|
||||
compGroup[i.component].push(i.summary);
|
||||
});
|
||||
|
||||
for (const [comp, sums] of Object.entries(compGroup)) {
|
||||
// Unique summaries only
|
||||
const uniqueSums = [...new Set(sums)];
|
||||
uniqueSums.forEach(s => {
|
||||
md += `- **${comp}**: ${s}\n`;
|
||||
});
|
||||
}
|
||||
md += `\n`;
|
||||
}
|
||||
|
||||
// Section 2: By Timeline (High Level)
|
||||
md += `## 2. Timeline of Critical Events\n`;
|
||||
// Flatten and sort all items by time
|
||||
const allItems = [];
|
||||
Object.values(categories).forEach(list => allItems.push(...list));
|
||||
allItems.sort((a, b) => a.time.localeCompare(b.time));
|
||||
|
||||
// Filter for "Critical" keywords
|
||||
const criticalItems = allItems.filter(i =>
|
||||
i.summary.toLowerCase().includes('fix') ||
|
||||
i.summary.toLowerCase().includes('patch') ||
|
||||
i.summary.toLowerCase().includes('create') ||
|
||||
i.summary.toLowerCase().includes('optimiz')
|
||||
);
|
||||
|
||||
criticalItems.forEach(i => {
|
||||
md += `- \`${i.time}\` (${i.component}): ${i.summary}\n`;
|
||||
});
|
||||
|
||||
// Section 3: Package Adjustments
|
||||
md += `\n## 3. Package & Documentation Adjustments\n`;
|
||||
const comps = Object.keys(componentMap).sort();
|
||||
comps.forEach(comp => {
|
||||
const count = new Set(componentMap[comp]).size;
|
||||
md += `- **${comp}**: Received ${count} significant updates.\n`;
|
||||
});
|
||||
|
||||
fs.writeFileSync(OUT_FILE, md);
|
||||
console.log("Human report generated.");
|
||||
}
|
||||
|
||||
generateHumanReport();
|
||||
|
||||
566
archive/inactive-skills/evolver/scripts/publish_public.js
Normal file
566
archive/inactive-skills/evolver/scripts/publish_public.js
Normal file
@@ -0,0 +1,566 @@
|
||||
const { execSync, spawnSync } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const https = require('https');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
|
||||
function run(cmd, opts = {}) {
|
||||
const { dryRun = false } = opts;
|
||||
if (dryRun) {
|
||||
process.stdout.write(`[dry-run] ${cmd}\n`);
|
||||
return '';
|
||||
}
|
||||
return execSync(cmd, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
|
||||
}
|
||||
|
||||
function hasCommand(cmd) {
|
||||
try {
|
||||
if (process.platform === 'win32') {
|
||||
const res = spawnSync('where', [cmd], { stdio: 'ignore' });
|
||||
return res.status === 0;
|
||||
}
|
||||
const res = spawnSync('which', [cmd], { stdio: 'ignore' });
|
||||
return res.status === 0;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function resolveGhExecutable() {
|
||||
if (hasCommand('gh')) return 'gh';
|
||||
const candidates = [
|
||||
'C:\\Program Files\\GitHub CLI\\gh.exe',
|
||||
'C:\\Program Files (x86)\\GitHub CLI\\gh.exe',
|
||||
];
|
||||
for (const p of candidates) {
|
||||
try {
|
||||
if (fs.existsSync(p)) return p;
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolveClawhubExecutable() {
|
||||
// On Windows, Node spawn/spawnSync does not always resolve PATHEXT the same way as shells.
|
||||
// Prefer the explicit .cmd shim when available to avoid false "not logged in" detection.
|
||||
if (process.platform === 'win32') {
|
||||
if (hasCommand('clawhub.cmd')) return 'clawhub.cmd';
|
||||
if (hasCommand('clawhub')) return 'clawhub';
|
||||
} else {
|
||||
if (hasCommand('clawhub')) return 'clawhub';
|
||||
}
|
||||
// Common npm global bin location on Windows.
|
||||
const candidates = [
|
||||
'C:\\Users\\Administrator\\AppData\\Roaming\\npm\\clawhub.cmd',
|
||||
'C:\\Users\\Administrator\\AppData\\Roaming\\npm\\clawhub.exe',
|
||||
'C:\\Users\\Administrator\\AppData\\Roaming\\npm\\clawhub.ps1',
|
||||
];
|
||||
for (const p of candidates) {
|
||||
try {
|
||||
if (fs.existsSync(p)) return p;
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function canUseClawhub() {
|
||||
const exe = resolveClawhubExecutable();
|
||||
if (!exe) return { ok: false, reason: 'clawhub CLI not found (install: npm i -g clawhub)' };
|
||||
return { ok: true, exe };
|
||||
}
|
||||
|
||||
function isClawhubLoggedIn() {
|
||||
const exe = resolveClawhubExecutable();
|
||||
if (!exe) return false;
|
||||
try {
|
||||
const res = spawnClawhub(exe, ['whoami'], { stdio: 'ignore' });
|
||||
return res.status === 0;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function spawnClawhub(exe, args, options) {
|
||||
// On Windows, directly spawning a .cmd can be flaky; using cmd.exe preserves argument parsing.
|
||||
// (Using shell:true can break clap/commander style option parsing for some CLIs.)
|
||||
const opts = options || {};
|
||||
if (process.platform === 'win32' && typeof exe === 'string') {
|
||||
const lower = exe.toLowerCase();
|
||||
if (lower.endsWith('.cmd')) {
|
||||
return spawnSync('cmd.exe', ['/d', '/s', '/c', exe, ...(args || [])], opts);
|
||||
}
|
||||
}
|
||||
return spawnSync(exe, args || [], opts);
|
||||
}
|
||||
|
||||
function publishToClawhub({ skillDir, slug, name, version, changelog, tags, dryRun }) {
|
||||
const ok = canUseClawhub();
|
||||
if (!ok.ok) throw new Error(ok.reason);
|
||||
|
||||
// Idempotency: if this version already exists on ClawHub, skip publishing.
|
||||
try {
|
||||
const inspect = spawnClawhub(ok.exe, ['inspect', slug, '--version', version], { stdio: 'ignore' });
|
||||
if (inspect.status === 0) {
|
||||
process.stdout.write(`ClawHub already has ${slug}@${version}. Skipping.\n`);
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore inspect failures; publish will surface errors if needed
|
||||
}
|
||||
|
||||
if (!dryRun && !isClawhubLoggedIn()) {
|
||||
throw new Error('Not logged in to ClawHub. Run: clawhub login');
|
||||
}
|
||||
|
||||
const args = ['publish', skillDir, '--slug', slug, '--name', name, '--version', version];
|
||||
if (changelog) args.push('--changelog', changelog);
|
||||
if (tags) args.push('--tags', tags);
|
||||
|
||||
if (dryRun) {
|
||||
process.stdout.write(`[dry-run] ${ok.exe} ${args.map(a => (/\s/.test(a) ? `"${a}"` : a)).join(' ')}\n`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Capture output to handle "version already exists" idempotently.
|
||||
const res = spawnClawhub(ok.exe, args, { encoding: 'utf8' });
|
||||
const out = `${res.stdout || ''}\n${res.stderr || ''}`.trim();
|
||||
|
||||
if (res.status === 0) {
|
||||
if (out) process.stdout.write(out + '\n');
|
||||
return;
|
||||
}
|
||||
|
||||
// Some clawhub deployments do not support reliable "inspect" by slug.
|
||||
// Treat "Version already exists" as success to make publishing idempotent.
|
||||
if (/version already exists/i.test(out)) {
|
||||
process.stdout.write(`ClawHub already has ${slug}@${version}. Skipping.\n`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (out) process.stderr.write(out + '\n');
|
||||
throw new Error(`clawhub publish failed for slug ${slug}`);
|
||||
}
|
||||
|
||||
function requireEnv(name, value) {
|
||||
if (!value) {
|
||||
throw new Error(`Missing required env var: ${name}`);
|
||||
}
|
||||
}
|
||||
|
||||
function ensureClean(dryRun) {
|
||||
const status = run('git status --porcelain', { dryRun });
|
||||
if (!dryRun && status) {
|
||||
throw new Error('Working tree is not clean. Commit or stash before publishing.');
|
||||
}
|
||||
}
|
||||
|
||||
function ensureBranch(expected, dryRun) {
|
||||
const current = run('git rev-parse --abbrev-ref HEAD', { dryRun }) || expected;
|
||||
if (!dryRun && current !== expected) {
|
||||
throw new Error(`Current branch is ${current}. Expected ${expected}.`);
|
||||
}
|
||||
}
|
||||
|
||||
function ensureRemote(remote, dryRun) {
|
||||
try {
|
||||
run(`git remote get-url ${remote}`, { dryRun });
|
||||
} catch (e) {
|
||||
throw new Error(`Remote "${remote}" not found. Add it manually before running this script.`);
|
||||
}
|
||||
}
|
||||
|
||||
function ensureTagAvailable(tag, dryRun) {
|
||||
if (!tag) return;
|
||||
const exists = run(`git tag --list ${tag}`, { dryRun });
|
||||
if (!dryRun && exists) {
|
||||
throw new Error(`Tag ${tag} already exists.`);
|
||||
}
|
||||
}
|
||||
|
||||
function ensureDir(dir, dryRun) {
|
||||
if (dryRun) return;
|
||||
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
|
||||
function rmDir(dir, dryRun) {
|
||||
if (dryRun) return;
|
||||
if (!fs.existsSync(dir)) return;
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
function copyDir(src, dest, dryRun) {
|
||||
if (dryRun) return;
|
||||
if (!fs.existsSync(src)) throw new Error(`Missing build output dir: ${src}`);
|
||||
ensureDir(dest, dryRun);
|
||||
const entries = fs.readdirSync(src, { withFileTypes: true });
|
||||
for (const ent of entries) {
|
||||
const s = path.join(src, ent.name);
|
||||
const d = path.join(dest, ent.name);
|
||||
if (ent.isDirectory()) copyDir(s, d, dryRun);
|
||||
else if (ent.isFile()) {
|
||||
ensureDir(path.dirname(d), dryRun);
|
||||
fs.copyFileSync(s, d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createReleaseWithGh({ repo, tag, title, notes, notesFile, dryRun }) {
|
||||
if (!repo || !tag) return;
|
||||
const ghExe = resolveGhExecutable();
|
||||
if (!ghExe) {
|
||||
throw new Error('gh CLI not found. Install GitHub CLI or provide a GitHub token for API-based release creation.');
|
||||
}
|
||||
const args = ['release', 'create', tag, '--repo', repo];
|
||||
if (title) args.push('-t', title);
|
||||
if (notesFile) args.push('-F', notesFile);
|
||||
else if (notes) args.push('-n', notes);
|
||||
else args.push('-n', 'Release created by publish script.');
|
||||
|
||||
if (dryRun) {
|
||||
process.stdout.write(`[dry-run] ${ghExe} ${args.join(' ')}\n`);
|
||||
return;
|
||||
}
|
||||
|
||||
const res = spawnSync(ghExe, args, { stdio: 'inherit' });
|
||||
if (res.status !== 0) {
|
||||
throw new Error('gh release create failed');
|
||||
}
|
||||
}
|
||||
|
||||
function canUseGhForRelease() {
|
||||
const ghExe = resolveGhExecutable();
|
||||
if (!ghExe) return { ok: false, reason: 'gh CLI not found' };
|
||||
try {
|
||||
// Non-interactive check: returns 0 when authenticated.
|
||||
const res = spawnSync(ghExe, ['auth', 'status', '-h', 'github.com'], { stdio: 'ignore' });
|
||||
if (res.status === 0) return { ok: true };
|
||||
return { ok: false, reason: 'gh not authenticated (run: gh auth login)' };
|
||||
} catch (e) {
|
||||
return { ok: false, reason: 'failed to check gh auth status' };
|
||||
}
|
||||
}
|
||||
|
||||
function getGithubToken() {
|
||||
return process.env.GITHUB_TOKEN || process.env.GH_TOKEN || process.env.GITHUB_PAT || '';
|
||||
}
|
||||
|
||||
function readReleaseNotes(notes, notesFile) {
|
||||
if (notesFile) {
|
||||
try {
|
||||
return fs.readFileSync(notesFile, 'utf8');
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to read RELEASE_NOTES_FILE: ${notesFile}`);
|
||||
}
|
||||
}
|
||||
if (notes) return String(notes);
|
||||
return '';
|
||||
}
|
||||
|
||||
function githubRequestJson({ method, repo, apiPath, token, body, dryRun }) {
|
||||
if (dryRun) {
|
||||
process.stdout.write(`[dry-run] GitHub API ${method} ${repo} ${apiPath}\n`);
|
||||
return Promise.resolve({ status: 200, json: null });
|
||||
}
|
||||
|
||||
const data = body ? Buffer.from(JSON.stringify(body)) : null;
|
||||
const opts = {
|
||||
method,
|
||||
hostname: 'api.github.com',
|
||||
path: `/repos/${repo}${apiPath}`,
|
||||
headers: {
|
||||
'User-Agent': 'evolver-publish-script',
|
||||
Accept: 'application/vnd.github+json',
|
||||
...(token ? { Authorization: `token ${token}` } : {}),
|
||||
...(data ? { 'Content-Type': 'application/json', 'Content-Length': String(data.length) } : {}),
|
||||
},
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = https.request(opts, res => {
|
||||
let raw = '';
|
||||
res.setEncoding('utf8');
|
||||
res.on('data', chunk => (raw += chunk));
|
||||
res.on('end', () => {
|
||||
let json = null;
|
||||
try {
|
||||
json = raw ? JSON.parse(raw) : null;
|
||||
} catch (e) {
|
||||
json = null;
|
||||
}
|
||||
resolve({ status: res.statusCode || 0, json, raw });
|
||||
});
|
||||
});
|
||||
req.on('error', reject);
|
||||
if (data) req.write(data);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
async function ensureReleaseWithApi({ repo, tag, title, notes, notesFile, dryRun }) {
|
||||
if (!repo || !tag) return;
|
||||
|
||||
const token = getGithubToken();
|
||||
if (!dryRun) {
|
||||
requireEnv('GITHUB_TOKEN (or GH_TOKEN/GITHUB_PAT)', token);
|
||||
}
|
||||
|
||||
// If release already exists, skip.
|
||||
const existing = await githubRequestJson({
|
||||
method: 'GET',
|
||||
repo,
|
||||
apiPath: `/releases/tags/${encodeURIComponent(tag)}`,
|
||||
token,
|
||||
dryRun,
|
||||
});
|
||||
|
||||
if (!dryRun && existing.status === 200) {
|
||||
process.stdout.write(`Release already exists for tag ${tag}. Skipping.\n`);
|
||||
return;
|
||||
}
|
||||
|
||||
const bodyText = readReleaseNotes(notes, notesFile) || 'Release created by publish script.';
|
||||
const payload = {
|
||||
tag_name: tag,
|
||||
name: title || tag,
|
||||
body: bodyText,
|
||||
draft: false,
|
||||
prerelease: false,
|
||||
};
|
||||
|
||||
const created = await githubRequestJson({
|
||||
method: 'POST',
|
||||
repo,
|
||||
apiPath: '/releases',
|
||||
token,
|
||||
body: payload,
|
||||
dryRun,
|
||||
});
|
||||
|
||||
if (!dryRun && (created.status < 200 || created.status >= 300)) {
|
||||
const msg = (created.json && created.json.message) || created.raw || 'Unknown error';
|
||||
throw new Error(`Failed to create GitHub Release (${created.status}): ${msg}`);
|
||||
}
|
||||
|
||||
process.stdout.write(`Created GitHub Release for tag ${tag}\n`);
|
||||
}
|
||||
|
||||
function main() {
|
||||
const dryRun = String(process.env.DRY_RUN || '').toLowerCase() === 'true';
|
||||
|
||||
const sourceBranch = process.env.SOURCE_BRANCH || 'main';
|
||||
const publicRemote = process.env.PUBLIC_REMOTE || 'public';
|
||||
const publicBranch = process.env.PUBLIC_BRANCH || 'main';
|
||||
const publicRepo = process.env.PUBLIC_REPO || '';
|
||||
const outDir = process.env.PUBLIC_OUT_DIR || 'dist-public';
|
||||
const useBuildOutput = String(process.env.PUBLIC_USE_BUILD_OUTPUT || 'true').toLowerCase() === 'true';
|
||||
const releaseOnly = String(process.env.PUBLIC_RELEASE_ONLY || '').toLowerCase() === 'true';
|
||||
|
||||
const clawhubSkip = String(process.env.CLAWHUB_SKIP || '').toLowerCase() === 'true';
|
||||
const clawhubPublish = String(process.env.CLAWHUB_PUBLISH || '').toLowerCase() === 'false' ? false : !clawhubSkip;
|
||||
// Workaround for registry redirect/auth issues: default to the www endpoint.
|
||||
const clawhubRegistry = process.env.CLAWHUB_REGISTRY || 'https://www.clawhub.ai';
|
||||
|
||||
// If publishing build output, require a repo URL or GH repo slug for cloning.
|
||||
if (useBuildOutput) {
|
||||
requireEnv('PUBLIC_REPO', publicRepo);
|
||||
}
|
||||
|
||||
let releaseTag = process.env.RELEASE_TAG || '';
|
||||
let releaseTitle = process.env.RELEASE_TITLE || '';
|
||||
const releaseNotes = process.env.RELEASE_NOTES || '';
|
||||
const releaseNotesFile = process.env.RELEASE_NOTES_FILE || '';
|
||||
const releaseSkip = String(process.env.RELEASE_SKIP || '').toLowerCase() === 'true';
|
||||
// Default behavior: create release unless explicitly skipped.
|
||||
// Backward compatibility: RELEASE_CREATE=true forces creation.
|
||||
// Note: RELEASE_CREATE=false is ignored; use RELEASE_SKIP=true instead.
|
||||
const releaseCreate = String(process.env.RELEASE_CREATE || '').toLowerCase() === 'true' ? true : !releaseSkip;
|
||||
const releaseUseGh = String(process.env.RELEASE_USE_GH || '').toLowerCase() === 'true';
|
||||
|
||||
// If not provided, infer from build output package.json version.
|
||||
if (!releaseTag && useBuildOutput) {
|
||||
try {
|
||||
const builtPkg = JSON.parse(fs.readFileSync(path.resolve(process.cwd(), outDir, 'package.json'), 'utf8'));
|
||||
if (builtPkg && builtPkg.version) releaseTag = `v${builtPkg.version}`;
|
||||
if (!releaseTitle && releaseTag) releaseTitle = releaseTag;
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
const releaseVersion = String(releaseTag || '').startsWith('v') ? String(releaseTag).slice(1) : '';
|
||||
|
||||
// Fail fast on missing release prerequisites to avoid half-publishing.
|
||||
// Strategy:
|
||||
// - If RELEASE_USE_GH=true: require gh + auth
|
||||
// - Else: prefer gh+auth; fallback to API token; else fail
|
||||
let releaseMode = 'none';
|
||||
if (releaseCreate && releaseTag) {
|
||||
if (releaseUseGh) {
|
||||
const ghOk = canUseGhForRelease();
|
||||
if (!dryRun && !ghOk.ok) {
|
||||
throw new Error(`Cannot create release via gh: ${ghOk.reason}`);
|
||||
}
|
||||
releaseMode = 'gh';
|
||||
} else {
|
||||
const ghOk = canUseGhForRelease();
|
||||
if (ghOk.ok) {
|
||||
releaseMode = 'gh';
|
||||
} else {
|
||||
const token = getGithubToken();
|
||||
if (!dryRun && !token) {
|
||||
throw new Error(
|
||||
'Cannot create GitHub Release: neither gh (installed+authenticated) nor GITHUB_TOKEN (or GH_TOKEN/GITHUB_PAT) is available.'
|
||||
);
|
||||
}
|
||||
releaseMode = 'api';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// In release-only mode we do not push code or tags, only create a GitHub Release for an existing tag.
|
||||
if (!releaseOnly) {
|
||||
ensureClean(dryRun);
|
||||
ensureBranch(sourceBranch, dryRun);
|
||||
ensureTagAvailable(releaseTag, dryRun);
|
||||
} else {
|
||||
requireEnv('RELEASE_TAG', releaseTag);
|
||||
}
|
||||
|
||||
if (!releaseOnly) {
|
||||
if (!useBuildOutput) {
|
||||
ensureRemote(publicRemote, dryRun);
|
||||
run(`git push ${publicRemote} ${sourceBranch}:${publicBranch}`, { dryRun });
|
||||
} else {
|
||||
const tmpBase = path.join(os.tmpdir(), 'evolver-public-publish');
|
||||
const tmpRepoDir = path.join(tmpBase, `repo_${Date.now()}`);
|
||||
const buildAbs = path.resolve(process.cwd(), outDir);
|
||||
|
||||
rmDir(tmpRepoDir, dryRun);
|
||||
ensureDir(tmpRepoDir, dryRun);
|
||||
|
||||
run(`git clone --depth 1 https://github.com/${publicRepo}.git "${tmpRepoDir}"`, { dryRun });
|
||||
run(`git -C "${tmpRepoDir}" checkout -B ${publicBranch}`, { dryRun });
|
||||
|
||||
// Replace repo contents with build output (except .git)
|
||||
if (!dryRun) {
|
||||
const entries = fs.readdirSync(tmpRepoDir, { withFileTypes: true });
|
||||
for (const ent of entries) {
|
||||
if (ent.name === '.git') continue;
|
||||
fs.rmSync(path.join(tmpRepoDir, ent.name), { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
copyDir(buildAbs, tmpRepoDir, dryRun);
|
||||
|
||||
run(`git -C "${tmpRepoDir}" add -A`, { dryRun });
|
||||
const msg = releaseTag ? `Release ${releaseTag}` : `Publish build output`;
|
||||
|
||||
// If build output is identical to current public branch, skip commit/push.
|
||||
const pending = run(`git -C "${tmpRepoDir}" status --porcelain`, { dryRun });
|
||||
if (!dryRun && !pending) {
|
||||
process.stdout.write('Public repo already matches build output. Skipping commit/push.\n');
|
||||
} else {
|
||||
// Avoid relying on global git config (CI environments often lack user.name/user.email).
|
||||
run(
|
||||
`git -C "${tmpRepoDir}" -c user.name="evolver-publish" -c user.email="evolver-publish@local" commit -m "${msg.replace(
|
||||
/"/g,
|
||||
'\\"'
|
||||
)}"`,
|
||||
{ dryRun }
|
||||
);
|
||||
run(`git -C "${tmpRepoDir}" push origin ${publicBranch}`, { dryRun });
|
||||
}
|
||||
|
||||
if (releaseTag) {
|
||||
const tagMsg = releaseTitle || `Release ${releaseTag}`;
|
||||
// If tag already exists in the public repo, do not recreate it.
|
||||
try {
|
||||
run(`git -C "${tmpRepoDir}" fetch --tags`, { dryRun });
|
||||
const exists = run(`git -C "${tmpRepoDir}" tag --list ${releaseTag}`, { dryRun });
|
||||
if (!dryRun && exists) {
|
||||
process.stdout.write(`Tag ${releaseTag} already exists in public repo. Skipping tag creation.\n`);
|
||||
} else {
|
||||
run(`git -C "${tmpRepoDir}" tag -a ${releaseTag} -m "${tagMsg.replace(/"/g, '\\"')}"`, { dryRun });
|
||||
run(`git -C "${tmpRepoDir}" push origin ${releaseTag}`, { dryRun });
|
||||
}
|
||||
} catch (e) {
|
||||
// If tag operations fail, rethrow to avoid publishing a release without a tag.
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (releaseTag) {
|
||||
if (!useBuildOutput) {
|
||||
const msg = releaseTitle || `Release ${releaseTag}`;
|
||||
run(`git tag -a ${releaseTag} -m "${msg.replace(/"/g, '\\"')}"`, { dryRun });
|
||||
run(`git push ${publicRemote} ${releaseTag}`, { dryRun });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (releaseCreate) {
|
||||
if (releaseMode === 'gh') {
|
||||
createReleaseWithGh({
|
||||
repo: publicRepo,
|
||||
tag: releaseTag,
|
||||
title: releaseTitle,
|
||||
notes: releaseNotes,
|
||||
notesFile: releaseNotesFile,
|
||||
dryRun,
|
||||
});
|
||||
} else if (releaseMode === 'api') {
|
||||
return ensureReleaseWithApi({
|
||||
repo: publicRepo,
|
||||
tag: releaseTag,
|
||||
title: releaseTitle,
|
||||
notes: releaseNotes,
|
||||
notesFile: releaseNotesFile,
|
||||
dryRun,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Publish to ClawHub after GitHub release succeeds (default enabled).
|
||||
if (clawhubPublish && releaseVersion) {
|
||||
process.env.CLAWHUB_REGISTRY = clawhubRegistry;
|
||||
|
||||
const skillDir = useBuildOutput ? path.resolve(process.cwd(), outDir) : process.cwd();
|
||||
const changelog = releaseTitle ? `GitHub Release ${releaseTitle}` : `GitHub Release ${releaseTag}`;
|
||||
|
||||
publishToClawhub({
|
||||
skillDir,
|
||||
slug: 'evolver',
|
||||
name: 'Evolver',
|
||||
version: releaseVersion,
|
||||
changelog,
|
||||
tags: 'latest',
|
||||
dryRun,
|
||||
});
|
||||
|
||||
publishToClawhub({
|
||||
skillDir,
|
||||
slug: 'capability-evolver',
|
||||
name: 'Capability Evolver',
|
||||
version: releaseVersion,
|
||||
changelog,
|
||||
tags: 'latest',
|
||||
dryRun,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const maybePromise = main();
|
||||
if (maybePromise && typeof maybePromise.then === 'function') {
|
||||
maybePromise.catch(e => {
|
||||
process.stderr.write(`${e.message}\n`);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
process.stderr.write(`${e.message}\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
61
archive/inactive-skills/evolver/scripts/recover_loop.js
Normal file
61
archive/inactive-skills/evolver/scripts/recover_loop.js
Normal file
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env node
|
||||
/* eslint-disable no-console */
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { spawnSync } = require('child_process');
|
||||
|
||||
function exists(p) {
|
||||
try {
|
||||
return fs.existsSync(p);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function sleepMs(ms) {
|
||||
const n = Number(ms);
|
||||
const t = Number.isFinite(n) ? Math.max(0, n) : 0;
|
||||
if (t <= 0) return;
|
||||
spawnSync('sleep', [String(Math.ceil(t / 1000))], { stdio: 'ignore' });
|
||||
}
|
||||
|
||||
function resolveWorkspaceRoot() {
|
||||
// In OpenClaw exec, cwd is usually the workspace root.
|
||||
// Keep it simple: do not try to walk up arbitrarily.
|
||||
return process.cwd();
|
||||
}
|
||||
|
||||
function resolveEvolverEntry(workspaceRoot) {
|
||||
const candidates = [
|
||||
path.join(workspaceRoot, 'skills', 'evolver', 'index.js'),
|
||||
path.join(workspaceRoot, 'skills', 'capability-evolver', 'index.js'),
|
||||
];
|
||||
for (const p of candidates) {
|
||||
if (exists(p)) return p;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function main() {
|
||||
const waitMs = parseInt(String(process.env.EVOLVER_RECOVER_WAIT_MS || '10000'), 10);
|
||||
const wait = Number.isFinite(waitMs) ? Math.max(0, waitMs) : 10000;
|
||||
|
||||
console.log(`[RECOVERY] Waiting ${wait}ms before restart...`);
|
||||
sleepMs(wait);
|
||||
|
||||
const workspaceRoot = resolveWorkspaceRoot();
|
||||
const entry = resolveEvolverEntry(workspaceRoot);
|
||||
if (!entry) {
|
||||
console.error('[RECOVERY] Failed: cannot locate evolver entry under skills/.');
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
console.log(`[RECOVERY] Restarting loop via ${path.relative(workspaceRoot, entry)} ...`);
|
||||
const r = spawnSync(process.execPath, [entry, '--loop'], { stdio: 'inherit' });
|
||||
process.exit(typeof r.status === 'number' ? r.status : 1);
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
|
||||
89
archive/inactive-skills/evolver/scripts/suggest_version.js
Normal file
89
archive/inactive-skills/evolver/scripts/suggest_version.js
Normal file
@@ -0,0 +1,89 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
const REPO_ROOT = path.resolve(__dirname, '..');
|
||||
|
||||
function ensureDir(dir) {
|
||||
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
|
||||
function parseSemver(v) {
|
||||
const m = String(v || '').trim().match(/^(\d+)\.(\d+)\.(\d+)$/);
|
||||
if (!m) return null;
|
||||
return { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3]) };
|
||||
}
|
||||
|
||||
function bumpSemver(base, bump) {
|
||||
const v = parseSemver(base);
|
||||
if (!v) return null;
|
||||
if (bump === 'major') return `${v.major + 1}.0.0`;
|
||||
if (bump === 'minor') return `${v.major}.${v.minor + 1}.0`;
|
||||
if (bump === 'patch') return `${v.major}.${v.minor}.${v.patch + 1}`;
|
||||
return `${v.major}.${v.minor}.${v.patch}`;
|
||||
}
|
||||
|
||||
function git(cmd) {
|
||||
return execSync(cmd, { cwd: REPO_ROOT, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
||||
}
|
||||
|
||||
function getBaseReleaseCommit() {
|
||||
try {
|
||||
const hash = git('git log -n 1 --pretty=%H --grep="chore(release): prepare v"');
|
||||
return hash || null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getCommitSubjectsSince(baseCommit) {
|
||||
try {
|
||||
if (!baseCommit) {
|
||||
const out = git('git log -n 30 --pretty=%s');
|
||||
return out ? out.split('\n').filter(Boolean) : [];
|
||||
}
|
||||
const out = git(`git log ${baseCommit}..HEAD --pretty=%s`);
|
||||
return out ? out.split('\n').filter(Boolean) : [];
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function inferBumpFromSubjects(subjects) {
|
||||
const subs = (subjects || []).map(s => String(s));
|
||||
const hasBreaking = subs.some(s => /\bBREAKING CHANGE\b/i.test(s) || /^[a-z]+(\(.+\))?!:/.test(s));
|
||||
if (hasBreaking) return { bump: 'major', reason: 'breaking change marker in commit subject' };
|
||||
|
||||
const hasFeat = subs.some(s => /^feat(\(.+\))?:/i.test(s));
|
||||
if (hasFeat) return { bump: 'minor', reason: 'feature commit detected (feat:)' };
|
||||
|
||||
const hasFix = subs.some(s => /^(fix|perf)(\(.+\))?:/i.test(s));
|
||||
if (hasFix) return { bump: 'patch', reason: 'fix/perf commit detected' };
|
||||
|
||||
if (subs.length === 0) return { bump: 'none', reason: 'no commits since base release commit' };
|
||||
return { bump: 'patch', reason: 'default to patch for non-breaking changes' };
|
||||
}
|
||||
|
||||
function main() {
|
||||
const pkgPath = path.join(REPO_ROOT, 'package.json');
|
||||
const baseVersion = JSON.parse(fs.readFileSync(pkgPath, 'utf8')).version;
|
||||
|
||||
const baseCommit = getBaseReleaseCommit();
|
||||
const subjects = getCommitSubjectsSince(baseCommit);
|
||||
const decision = inferBumpFromSubjects(subjects);
|
||||
const suggestedVersion = decision.bump === 'none' ? baseVersion : bumpSemver(baseVersion, decision.bump);
|
||||
|
||||
const out = { baseVersion, baseCommit, subjects, decision, suggestedVersion };
|
||||
const memDir = path.join(REPO_ROOT, 'memory');
|
||||
ensureDir(memDir);
|
||||
fs.writeFileSync(path.join(memDir, 'semver_suggestion.json'), JSON.stringify(out, null, 2) + '\n', 'utf8');
|
||||
process.stdout.write(JSON.stringify(out, null, 2) + '\n');
|
||||
}
|
||||
|
||||
try {
|
||||
main();
|
||||
} catch (e) {
|
||||
process.stderr.write(`${e.message}\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
1025
archive/inactive-skills/evolver/src/evolve.js
Normal file
1025
archive/inactive-skills/evolver/src/evolve.js
Normal file
File diff suppressed because it is too large
Load Diff
152
archive/inactive-skills/evolver/src/gep/a2a.js
Normal file
152
archive/inactive-skills/evolver/src/gep/a2a.js
Normal file
@@ -0,0 +1,152 @@
|
||||
const fs = require('fs');
|
||||
const { readAllEvents } = require('./assetStore');
|
||||
const { computeAssetId, SCHEMA_VERSION } = require('./contentHash');
|
||||
const { unwrapAssetFromMessage } = require('./a2aProtocol');
|
||||
|
||||
function nowIso() { return new Date().toISOString(); }
|
||||
|
||||
function isAllowedA2AAsset(obj) {
|
||||
if (!obj || typeof obj !== 'object') return false;
|
||||
var t = obj.type;
|
||||
return t === 'Gene' || t === 'Capsule' || t === 'EvolutionEvent';
|
||||
}
|
||||
|
||||
function safeNumber(x, fallback) {
|
||||
if (fallback === undefined) fallback = null;
|
||||
var n = Number(x);
|
||||
return Number.isFinite(n) ? n : fallback;
|
||||
}
|
||||
|
||||
function getBlastRadiusLimits() {
|
||||
var maxFiles = safeNumber(process.env.A2A_MAX_FILES, 5);
|
||||
var maxLines = safeNumber(process.env.A2A_MAX_LINES, 200);
|
||||
return {
|
||||
maxFiles: Number.isFinite(maxFiles) ? maxFiles : 5,
|
||||
maxLines: Number.isFinite(maxLines) ? maxLines : 200,
|
||||
};
|
||||
}
|
||||
|
||||
function isBlastRadiusSafe(blastRadius) {
|
||||
var lim = getBlastRadiusLimits();
|
||||
var files = blastRadius && Number.isFinite(Number(blastRadius.files)) ? Number(blastRadius.files) : 0;
|
||||
var lines = blastRadius && Number.isFinite(Number(blastRadius.lines)) ? Number(blastRadius.lines) : 0;
|
||||
return files <= lim.maxFiles && lines <= lim.maxLines;
|
||||
}
|
||||
|
||||
function clamp01(n) {
|
||||
var x = Number(n);
|
||||
if (!Number.isFinite(x)) return 0;
|
||||
return Math.max(0, Math.min(1, x));
|
||||
}
|
||||
|
||||
function lowerConfidence(asset, opts) {
|
||||
if (!opts) opts = {};
|
||||
var factor = Number.isFinite(Number(opts.factor)) ? Number(opts.factor) : 0.6;
|
||||
var receivedFrom = opts.source || 'external';
|
||||
var receivedAt = opts.received_at || nowIso();
|
||||
var cloned = JSON.parse(JSON.stringify(asset || {}));
|
||||
if (!isAllowedA2AAsset(cloned)) return null;
|
||||
if (cloned.type === 'Capsule') {
|
||||
if (typeof cloned.confidence === 'number') cloned.confidence = clamp01(cloned.confidence * factor);
|
||||
else if (cloned.confidence != null) cloned.confidence = clamp01(Number(cloned.confidence) * factor);
|
||||
}
|
||||
if (!cloned.a2a || typeof cloned.a2a !== 'object') cloned.a2a = {};
|
||||
cloned.a2a.status = 'external_candidate';
|
||||
cloned.a2a.source = receivedFrom;
|
||||
cloned.a2a.received_at = receivedAt;
|
||||
cloned.a2a.confidence_factor = factor;
|
||||
if (!cloned.schema_version) cloned.schema_version = SCHEMA_VERSION;
|
||||
if (!cloned.asset_id) { try { cloned.asset_id = computeAssetId(cloned); } catch (e) {} }
|
||||
return cloned;
|
||||
}
|
||||
|
||||
function readEvolutionEvents() {
|
||||
var events = readAllEvents();
|
||||
return Array.isArray(events) ? events.filter(function (e) { return e && e.type === 'EvolutionEvent'; }) : [];
|
||||
}
|
||||
|
||||
function normalizeEventsList(events) {
|
||||
return Array.isArray(events) ? events : [];
|
||||
}
|
||||
|
||||
function computeCapsuleSuccessStreak(params) {
|
||||
var capsuleId = params.capsuleId;
|
||||
var events = params.events;
|
||||
var id = capsuleId ? String(capsuleId) : '';
|
||||
if (!id) return 0;
|
||||
var list = normalizeEventsList(events || readEvolutionEvents());
|
||||
var streak = 0;
|
||||
for (var i = list.length - 1; i >= 0; i--) {
|
||||
var ev = list[i];
|
||||
if (!ev || ev.type !== 'EvolutionEvent') continue;
|
||||
if (!ev.capsule_id || String(ev.capsule_id) !== id) continue;
|
||||
var st = ev.outcome && ev.outcome.status ? String(ev.outcome.status) : 'unknown';
|
||||
if (st === 'success') streak += 1; else break;
|
||||
}
|
||||
return streak;
|
||||
}
|
||||
|
||||
function isCapsuleBroadcastEligible(capsule, opts) {
|
||||
if (!opts) opts = {};
|
||||
if (!capsule || capsule.type !== 'Capsule') return false;
|
||||
var score = capsule.outcome && capsule.outcome.score != null ? safeNumber(capsule.outcome.score, null) : null;
|
||||
if (score == null || score < 0.7) return false;
|
||||
var blast = capsule.blast_radius || (capsule.outcome && capsule.outcome.blast_radius) || null;
|
||||
if (!isBlastRadiusSafe(blast)) return false;
|
||||
var events = Array.isArray(opts.events) ? opts.events : readEvolutionEvents();
|
||||
var streak = computeCapsuleSuccessStreak({ capsuleId: capsule.id, events: events });
|
||||
if (streak < 2) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function exportEligibleCapsules(params) {
|
||||
if (!params) params = {};
|
||||
var list = Array.isArray(params.capsules) ? params.capsules : [];
|
||||
var evs = Array.isArray(params.events) ? params.events : readEvolutionEvents();
|
||||
var eligible = list.filter(function (c) { return isCapsuleBroadcastEligible(c, { events: evs }); });
|
||||
for (var i = 0; i < eligible.length; i++) {
|
||||
var c = eligible[i];
|
||||
if (!c.schema_version) c.schema_version = SCHEMA_VERSION;
|
||||
if (!c.asset_id) { try { c.asset_id = computeAssetId(c); } catch (e) {} }
|
||||
}
|
||||
return eligible;
|
||||
}
|
||||
|
||||
function parseA2AInput(text) {
|
||||
var raw = String(text || '').trim();
|
||||
if (!raw) return [];
|
||||
try {
|
||||
var maybe = JSON.parse(raw);
|
||||
if (Array.isArray(maybe)) {
|
||||
return maybe.map(function (item) { return unwrapAssetFromMessage(item) || item; }).filter(Boolean);
|
||||
}
|
||||
if (maybe && typeof maybe === 'object') {
|
||||
var unwrapped = unwrapAssetFromMessage(maybe);
|
||||
return unwrapped ? [unwrapped] : [maybe];
|
||||
}
|
||||
} catch (e) {}
|
||||
var lines = raw.split('\n').map(function (l) { return l.trim(); }).filter(Boolean);
|
||||
var items = [];
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
try {
|
||||
var obj = JSON.parse(lines[i]);
|
||||
var uw = unwrapAssetFromMessage(obj);
|
||||
items.push(uw || obj);
|
||||
} catch (e) { continue; }
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
function readTextIfExists(filePath) {
|
||||
try {
|
||||
if (!filePath) return '';
|
||||
if (!fs.existsSync(filePath)) return '';
|
||||
return fs.readFileSync(filePath, 'utf8');
|
||||
} catch { return ''; }
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isAllowedA2AAsset, lowerConfidence, isBlastRadiusSafe,
|
||||
computeCapsuleSuccessStreak, isCapsuleBroadcastEligible,
|
||||
exportEligibleCapsules, parseA2AInput, readTextIfExists,
|
||||
};
|
||||
271
archive/inactive-skills/evolver/src/gep/a2aProtocol.js
Normal file
271
archive/inactive-skills/evolver/src/gep/a2aProtocol.js
Normal file
@@ -0,0 +1,271 @@
|
||||
// GEP A2A Protocol - Standard message types and pluggable transport layer.
|
||||
//
|
||||
// Protocol messages:
|
||||
// hello - capability advertisement and node discovery
|
||||
// publish - broadcast an eligible asset (Capsule/Gene)
|
||||
// fetch - request a specific asset by id or content hash
|
||||
// report - send a ValidationReport for a received asset
|
||||
// decision - accept/reject/quarantine decision on a received asset
|
||||
// revoke - withdraw a previously published asset
|
||||
//
|
||||
// Transport interface:
|
||||
// send(message, opts) - send a protocol message
|
||||
// receive(opts) - receive pending messages
|
||||
// list(opts) - list available message files/streams
|
||||
//
|
||||
// Default transport: FileTransport (reads/writes JSONL to a2a/ directory).
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const { getGepAssetsDir } = require('./paths');
|
||||
const { computeAssetId } = require('./contentHash');
|
||||
const { captureEnvFingerprint } = require('./envFingerprint');
|
||||
|
||||
const PROTOCOL_NAME = 'gep-a2a';
|
||||
const PROTOCOL_VERSION = '1.0.0';
|
||||
const VALID_MESSAGE_TYPES = ['hello', 'publish', 'fetch', 'report', 'decision', 'revoke'];
|
||||
|
||||
function generateMessageId() {
|
||||
return 'msg_' + Date.now() + '_' + crypto.randomBytes(4).toString('hex');
|
||||
}
|
||||
|
||||
function getNodeId() {
|
||||
if (process.env.A2A_NODE_ID) return String(process.env.A2A_NODE_ID);
|
||||
const raw = process.cwd() + '|' + (process.env.AGENT_NAME || 'default');
|
||||
return 'node_' + crypto.createHash('sha256').update(raw).digest('hex').slice(0, 12);
|
||||
}
|
||||
|
||||
// --- Base message builder ---
|
||||
|
||||
function buildMessage(params) {
|
||||
var messageType = params.messageType;
|
||||
var payload = params.payload;
|
||||
var senderId = params.senderId;
|
||||
if (!VALID_MESSAGE_TYPES.includes(messageType)) {
|
||||
throw new Error('Invalid message type: ' + messageType + '. Valid: ' + VALID_MESSAGE_TYPES.join(', '));
|
||||
}
|
||||
return {
|
||||
protocol: PROTOCOL_NAME,
|
||||
protocol_version: PROTOCOL_VERSION,
|
||||
message_type: messageType,
|
||||
message_id: generateMessageId(),
|
||||
sender_id: senderId || getNodeId(),
|
||||
timestamp: new Date().toISOString(),
|
||||
payload: payload || {},
|
||||
};
|
||||
}
|
||||
|
||||
// --- Typed message builders ---
|
||||
|
||||
function buildHello(opts) {
|
||||
var o = opts || {};
|
||||
return buildMessage({
|
||||
messageType: 'hello',
|
||||
senderId: o.nodeId,
|
||||
payload: {
|
||||
capabilities: o.capabilities || {},
|
||||
gene_count: typeof o.geneCount === 'number' ? o.geneCount : null,
|
||||
capsule_count: typeof o.capsuleCount === 'number' ? o.capsuleCount : null,
|
||||
env_fingerprint: captureEnvFingerprint(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function buildPublish(opts) {
|
||||
var o = opts || {};
|
||||
var asset = o.asset;
|
||||
if (!asset || !asset.type || !asset.id) {
|
||||
throw new Error('publish: asset must have type and id');
|
||||
}
|
||||
return buildMessage({
|
||||
messageType: 'publish',
|
||||
senderId: o.nodeId,
|
||||
payload: {
|
||||
asset_type: asset.type,
|
||||
asset_id: asset.asset_id || computeAssetId(asset),
|
||||
local_id: asset.id,
|
||||
asset: asset,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function buildFetch(opts) {
|
||||
var o = opts || {};
|
||||
return buildMessage({
|
||||
messageType: 'fetch',
|
||||
senderId: o.nodeId,
|
||||
payload: {
|
||||
asset_type: o.assetType || null,
|
||||
local_id: o.localId || null,
|
||||
content_hash: o.contentHash || null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function buildReport(opts) {
|
||||
var o = opts || {};
|
||||
return buildMessage({
|
||||
messageType: 'report',
|
||||
senderId: o.nodeId,
|
||||
payload: {
|
||||
target_asset_id: o.assetId || null,
|
||||
target_local_id: o.localId || null,
|
||||
validation_report: o.validationReport || null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function buildDecision(opts) {
|
||||
var o = opts || {};
|
||||
var validDecisions = ['accept', 'reject', 'quarantine'];
|
||||
if (!validDecisions.includes(o.decision)) {
|
||||
throw new Error('decision must be one of: ' + validDecisions.join(', '));
|
||||
}
|
||||
return buildMessage({
|
||||
messageType: 'decision',
|
||||
senderId: o.nodeId,
|
||||
payload: {
|
||||
target_asset_id: o.assetId || null,
|
||||
target_local_id: o.localId || null,
|
||||
decision: o.decision,
|
||||
reason: o.reason || null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function buildRevoke(opts) {
|
||||
var o = opts || {};
|
||||
return buildMessage({
|
||||
messageType: 'revoke',
|
||||
senderId: o.nodeId,
|
||||
payload: {
|
||||
target_asset_id: o.assetId || null,
|
||||
target_local_id: o.localId || null,
|
||||
reason: o.reason || null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// --- Validation ---
|
||||
|
||||
function isValidProtocolMessage(msg) {
|
||||
if (!msg || typeof msg !== 'object') return false;
|
||||
if (msg.protocol !== PROTOCOL_NAME) return false;
|
||||
if (!msg.message_type || !VALID_MESSAGE_TYPES.includes(msg.message_type)) return false;
|
||||
if (!msg.message_id || typeof msg.message_id !== 'string') return false;
|
||||
if (!msg.timestamp || typeof msg.timestamp !== 'string') return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Try to extract a raw asset from either a protocol message or a plain asset object.
|
||||
// This enables backward-compatible ingestion of both old-format and new-format payloads.
|
||||
function unwrapAssetFromMessage(input) {
|
||||
if (!input || typeof input !== 'object') return null;
|
||||
// If it is a protocol message with a publish payload, extract the asset.
|
||||
if (input.protocol === PROTOCOL_NAME && input.message_type === 'publish') {
|
||||
var p = input.payload;
|
||||
if (p && p.asset && typeof p.asset === 'object') return p.asset;
|
||||
return null;
|
||||
}
|
||||
// If it is a plain asset (Gene/Capsule/EvolutionEvent), return as-is.
|
||||
if (input.type === 'Gene' || input.type === 'Capsule' || input.type === 'EvolutionEvent') {
|
||||
return input;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// --- File Transport ---
|
||||
|
||||
function ensureDir(dir) {
|
||||
try {
|
||||
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function defaultA2ADir() {
|
||||
return process.env.A2A_DIR || path.join(getGepAssetsDir(), 'a2a');
|
||||
}
|
||||
|
||||
function fileTransportSend(message, opts) {
|
||||
var dir = (opts && opts.dir) || defaultA2ADir();
|
||||
var subdir = path.join(dir, 'outbox');
|
||||
ensureDir(subdir);
|
||||
var filePath = path.join(subdir, message.message_type + '.jsonl');
|
||||
fs.appendFileSync(filePath, JSON.stringify(message) + '\n', 'utf8');
|
||||
return { ok: true, path: filePath };
|
||||
}
|
||||
|
||||
function fileTransportReceive(opts) {
|
||||
var dir = (opts && opts.dir) || defaultA2ADir();
|
||||
var subdir = path.join(dir, 'inbox');
|
||||
if (!fs.existsSync(subdir)) return [];
|
||||
var files = fs.readdirSync(subdir).filter(function (f) { return f.endsWith('.jsonl'); });
|
||||
var messages = [];
|
||||
for (var fi = 0; fi < files.length; fi++) {
|
||||
try {
|
||||
var raw = fs.readFileSync(path.join(subdir, files[fi]), 'utf8');
|
||||
var lines = raw.split('\n').map(function (l) { return l.trim(); }).filter(Boolean);
|
||||
for (var li = 0; li < lines.length; li++) {
|
||||
try {
|
||||
var msg = JSON.parse(lines[li]);
|
||||
if (msg && msg.protocol === PROTOCOL_NAME) messages.push(msg);
|
||||
} catch (e) {}
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
|
||||
function fileTransportList(opts) {
|
||||
var dir = (opts && opts.dir) || defaultA2ADir();
|
||||
var subdir = path.join(dir, 'outbox');
|
||||
if (!fs.existsSync(subdir)) return [];
|
||||
return fs.readdirSync(subdir).filter(function (f) { return f.endsWith('.jsonl'); });
|
||||
}
|
||||
|
||||
// --- Transport registry ---
|
||||
|
||||
var transports = {
|
||||
file: {
|
||||
send: fileTransportSend,
|
||||
receive: fileTransportReceive,
|
||||
list: fileTransportList,
|
||||
},
|
||||
};
|
||||
|
||||
function getTransport(name) {
|
||||
var n = String(name || process.env.A2A_TRANSPORT || 'file').toLowerCase();
|
||||
var t = transports[n];
|
||||
if (!t) throw new Error('Unknown A2A transport: ' + n + '. Available: ' + Object.keys(transports).join(', '));
|
||||
return t;
|
||||
}
|
||||
|
||||
function registerTransport(name, impl) {
|
||||
if (!name || typeof name !== 'string') throw new Error('transport name required');
|
||||
if (!impl || typeof impl.send !== 'function' || typeof impl.receive !== 'function') {
|
||||
throw new Error('transport must implement send() and receive()');
|
||||
}
|
||||
transports[name] = impl;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
PROTOCOL_NAME,
|
||||
PROTOCOL_VERSION,
|
||||
VALID_MESSAGE_TYPES,
|
||||
getNodeId,
|
||||
buildMessage,
|
||||
buildHello,
|
||||
buildPublish,
|
||||
buildFetch,
|
||||
buildReport,
|
||||
buildDecision,
|
||||
buildRevoke,
|
||||
isValidProtocolMessage,
|
||||
unwrapAssetFromMessage,
|
||||
getTransport,
|
||||
registerTransport,
|
||||
fileTransportSend,
|
||||
fileTransportReceive,
|
||||
fileTransportList,
|
||||
};
|
||||
183
archive/inactive-skills/evolver/src/gep/assetStore.js
Normal file
183
archive/inactive-skills/evolver/src/gep/assetStore.js
Normal file
@@ -0,0 +1,183 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { getGepAssetsDir } = require('./paths');
|
||||
const { computeAssetId, SCHEMA_VERSION } = require('./contentHash');
|
||||
|
||||
function ensureDir(dir) {
|
||||
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
|
||||
function readJsonIfExists(filePath, fallback) {
|
||||
try {
|
||||
if (!fs.existsSync(filePath)) return fallback;
|
||||
const raw = fs.readFileSync(filePath, 'utf8');
|
||||
if (!raw.trim()) return fallback;
|
||||
return JSON.parse(raw);
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
function writeJsonAtomic(filePath, obj) {
|
||||
const dir = path.dirname(filePath);
|
||||
ensureDir(dir);
|
||||
const tmp = `${filePath}.tmp`;
|
||||
fs.writeFileSync(tmp, JSON.stringify(obj, null, 2) + '\n', 'utf8');
|
||||
fs.renameSync(tmp, filePath);
|
||||
}
|
||||
|
||||
function getDefaultGenes() {
|
||||
return {
|
||||
version: 1,
|
||||
genes: [
|
||||
{
|
||||
type: 'Gene', id: 'gene_gep_repair_from_errors', category: 'repair',
|
||||
signals_match: ['error', 'exception', 'failed', 'unstable'],
|
||||
preconditions: ['signals contains error-related indicators'],
|
||||
strategy: [
|
||||
'Extract structured signals from logs and user instructions',
|
||||
'Select an existing Gene by signals match (no improvisation)',
|
||||
'Estimate blast radius (files, lines) before editing',
|
||||
'Apply smallest reversible patch',
|
||||
'Validate using declared validation steps; rollback on failure',
|
||||
'Solidify knowledge: append EvolutionEvent, update Gene/Capsule store',
|
||||
],
|
||||
constraints: { max_files: 12, forbidden_paths: ['.git', 'node_modules'] },
|
||||
validation: [
|
||||
'node -e "require(\'./src/evolve\'); require(\'./src/gep/solidify\'); console.log(\'ok\')"',
|
||||
'node -e "require(\'./src/gep/selector\'); require(\'./src/gep/memoryGraph\'); console.log(\'ok\')"',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'Gene', id: 'gene_gep_optimize_prompt_and_assets', category: 'optimize',
|
||||
signals_match: ['protocol', 'gep', 'prompt', 'audit', 'reusable'],
|
||||
preconditions: ['need stricter, auditable evolution protocol outputs'],
|
||||
strategy: [
|
||||
'Extract signals and determine selection rationale via Selector JSON',
|
||||
'Prefer reusing existing Gene/Capsule; only create if no match exists',
|
||||
'Refactor prompt assembly to embed assets (genes, capsules, parent event)',
|
||||
'Reduce noise and ambiguity; enforce strict output schema',
|
||||
'Validate by running node index.js run and ensuring no runtime errors',
|
||||
'Solidify: record EvolutionEvent, update Gene definitions, create Capsule on success',
|
||||
],
|
||||
constraints: { max_files: 20, forbidden_paths: ['.git', 'node_modules'] },
|
||||
validation: ['node -e "require(\'./src/evolve\'); require(\'./src/gep/prompt\'); console.log(\'ok\')"'],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function getDefaultCapsules() { return { version: 1, capsules: [] }; }
|
||||
function genesPath() { return path.join(getGepAssetsDir(), 'genes.json'); }
|
||||
function capsulesPath() { return path.join(getGepAssetsDir(), 'capsules.json'); }
|
||||
function eventsPath() { return path.join(getGepAssetsDir(), 'events.jsonl'); }
|
||||
function candidatesPath() { return path.join(getGepAssetsDir(), 'candidates.jsonl'); }
|
||||
function externalCandidatesPath() { return path.join(getGepAssetsDir(), 'external_candidates.jsonl'); }
|
||||
|
||||
function loadGenes() { return readJsonIfExists(genesPath(), getDefaultGenes()).genes || []; }
|
||||
function loadCapsules() { return readJsonIfExists(capsulesPath(), getDefaultCapsules()).capsules || []; }
|
||||
|
||||
function getLastEventId() {
|
||||
try {
|
||||
const p = eventsPath();
|
||||
if (!fs.existsSync(p)) return null;
|
||||
const raw = fs.readFileSync(p, 'utf8');
|
||||
const lines = raw.split('\n').map(l => l.trim()).filter(Boolean);
|
||||
if (lines.length === 0) return null;
|
||||
const last = JSON.parse(lines[lines.length - 1]);
|
||||
return last && typeof last.id === 'string' ? last.id : null;
|
||||
} catch { return null; }
|
||||
}
|
||||
|
||||
function readAllEvents() {
|
||||
try {
|
||||
const p = eventsPath();
|
||||
if (!fs.existsSync(p)) return [];
|
||||
const raw = fs.readFileSync(p, 'utf8');
|
||||
return raw.split('\n').map(l => l.trim()).filter(Boolean).map(l => {
|
||||
try { return JSON.parse(l); } catch { return null; }
|
||||
}).filter(Boolean);
|
||||
} catch { return []; }
|
||||
}
|
||||
|
||||
function appendEventJsonl(eventObj) {
|
||||
const dir = getGepAssetsDir(); ensureDir(dir);
|
||||
fs.appendFileSync(eventsPath(), JSON.stringify(eventObj) + '\n', 'utf8');
|
||||
}
|
||||
|
||||
function appendCandidateJsonl(candidateObj) {
|
||||
const dir = getGepAssetsDir(); ensureDir(dir);
|
||||
fs.appendFileSync(candidatesPath(), JSON.stringify(candidateObj) + '\n', 'utf8');
|
||||
}
|
||||
|
||||
function appendExternalCandidateJsonl(obj) {
|
||||
const dir = getGepAssetsDir(); ensureDir(dir);
|
||||
fs.appendFileSync(externalCandidatesPath(), JSON.stringify(obj) + '\n', 'utf8');
|
||||
}
|
||||
|
||||
function readRecentCandidates(limit = 20) {
|
||||
try {
|
||||
const p = candidatesPath();
|
||||
if (!fs.existsSync(p)) return [];
|
||||
const raw = fs.readFileSync(p, 'utf8');
|
||||
const lines = raw.split('\n').map(l => l.trim()).filter(Boolean);
|
||||
return lines.slice(Math.max(0, lines.length - limit)).map(l => {
|
||||
try { return JSON.parse(l); } catch { return null; }
|
||||
}).filter(Boolean);
|
||||
} catch { return []; }
|
||||
}
|
||||
|
||||
function readRecentExternalCandidates(limit = 50) {
|
||||
try {
|
||||
const p = externalCandidatesPath();
|
||||
if (!fs.existsSync(p)) return [];
|
||||
const raw = fs.readFileSync(p, 'utf8');
|
||||
const lines = raw.split('\n').map(l => l.trim()).filter(Boolean);
|
||||
return lines.slice(Math.max(0, lines.length - limit)).map(l => {
|
||||
try { return JSON.parse(l); } catch { return null; }
|
||||
}).filter(Boolean);
|
||||
} catch { return []; }
|
||||
}
|
||||
|
||||
// Safety net: ensure schema_version and asset_id are present before writing.
|
||||
function ensureSchemaFields(obj) {
|
||||
if (!obj || typeof obj !== 'object') return obj;
|
||||
if (!obj.schema_version) obj.schema_version = SCHEMA_VERSION;
|
||||
if (!obj.asset_id) { try { obj.asset_id = computeAssetId(obj); } catch (e) {} }
|
||||
return obj;
|
||||
}
|
||||
|
||||
function upsertGene(geneObj) {
|
||||
ensureSchemaFields(geneObj);
|
||||
const current = readJsonIfExists(genesPath(), getDefaultGenes());
|
||||
const genes = Array.isArray(current.genes) ? current.genes : [];
|
||||
const idx = genes.findIndex(g => g && g.id === geneObj.id);
|
||||
if (idx >= 0) genes[idx] = geneObj; else genes.push(geneObj);
|
||||
writeJsonAtomic(genesPath(), { version: current.version || 1, genes });
|
||||
}
|
||||
|
||||
function appendCapsule(capsuleObj) {
|
||||
ensureSchemaFields(capsuleObj);
|
||||
const current = readJsonIfExists(capsulesPath(), getDefaultCapsules());
|
||||
const capsules = Array.isArray(current.capsules) ? current.capsules : [];
|
||||
capsules.push(capsuleObj);
|
||||
writeJsonAtomic(capsulesPath(), { version: current.version || 1, capsules });
|
||||
}
|
||||
|
||||
function upsertCapsule(capsuleObj) {
|
||||
if (!capsuleObj || capsuleObj.type !== 'Capsule' || !capsuleObj.id) return;
|
||||
ensureSchemaFields(capsuleObj);
|
||||
const current = readJsonIfExists(capsulesPath(), getDefaultCapsules());
|
||||
const capsules = Array.isArray(current.capsules) ? current.capsules : [];
|
||||
const idx = capsules.findIndex(c => c && c.type === 'Capsule' && String(c.id) === String(capsuleObj.id));
|
||||
if (idx >= 0) capsules[idx] = capsuleObj; else capsules.push(capsuleObj);
|
||||
writeJsonAtomic(capsulesPath(), { version: current.version || 1, capsules });
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
loadGenes, loadCapsules, readAllEvents, getLastEventId,
|
||||
appendEventJsonl, appendCandidateJsonl, appendExternalCandidateJsonl,
|
||||
readRecentCandidates, readRecentExternalCandidates,
|
||||
upsertGene, appendCapsule, upsertCapsule,
|
||||
genesPath, capsulesPath, eventsPath, candidatesPath, externalCandidatesPath,
|
||||
};
|
||||
71
archive/inactive-skills/evolver/src/gep/bridge.js
Normal file
71
archive/inactive-skills/evolver/src/gep/bridge.js
Normal file
@@ -0,0 +1,71 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
function ensureDir(dir) {
|
||||
try {
|
||||
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function nowIso() {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
||||
function clip(text, maxChars) {
|
||||
const s = String(text || '');
|
||||
const n = Number(maxChars);
|
||||
if (!Number.isFinite(n) || n <= 0) return s;
|
||||
if (s.length <= n) return s;
|
||||
return s.slice(0, Math.max(0, n - 40)) + '\n...[TRUNCATED]...\n';
|
||||
}
|
||||
|
||||
function writePromptArtifact({ memoryDir, cycleId, runId, prompt, meta }) {
|
||||
const dir = String(memoryDir || '').trim();
|
||||
if (!dir) throw new Error('bridge: missing memoryDir');
|
||||
ensureDir(dir);
|
||||
const safeCycle = String(cycleId || 'cycle').replace(/[^a-zA-Z0-9_\-#]/g, '_');
|
||||
const safeRun = String(runId || Date.now()).replace(/[^a-zA-Z0-9_\-]/g, '_');
|
||||
const base = `gep_prompt_${safeCycle}_${safeRun}`;
|
||||
const promptPath = path.join(dir, base + '.txt');
|
||||
const metaPath = path.join(dir, base + '.json');
|
||||
|
||||
fs.writeFileSync(promptPath, String(prompt || ''), 'utf8');
|
||||
fs.writeFileSync(
|
||||
metaPath,
|
||||
JSON.stringify(
|
||||
{
|
||||
type: 'GepPromptArtifact',
|
||||
at: nowIso(),
|
||||
cycle_id: cycleId || null,
|
||||
run_id: runId || null,
|
||||
prompt_path: promptPath,
|
||||
meta: meta && typeof meta === 'object' ? meta : null,
|
||||
},
|
||||
null,
|
||||
2
|
||||
) + '\n',
|
||||
'utf8'
|
||||
);
|
||||
|
||||
return { promptPath, metaPath };
|
||||
}
|
||||
|
||||
function renderSessionsSpawnCall({ task, agentId, label, cleanup }) {
|
||||
const t = String(task || '').trim();
|
||||
if (!t) throw new Error('bridge: missing task');
|
||||
const a = String(agentId || 'main');
|
||||
const l = String(label || 'gep_bridge');
|
||||
const c = cleanup ? String(cleanup) : 'delete';
|
||||
|
||||
// Output valid JSON so wrappers can parse with JSON.parse (not regex).
|
||||
// The wrapper uses lastIndexOf('sessions_spawn(') + JSON.parse to extract the task.
|
||||
const payload = JSON.stringify({ task: t, agentId: a, cleanup: c, label: l });
|
||||
return `sessions_spawn(${payload})`;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
clip,
|
||||
writePromptArtifact,
|
||||
renderSessionsSpawnCall,
|
||||
};
|
||||
|
||||
142
archive/inactive-skills/evolver/src/gep/candidates.js
Normal file
142
archive/inactive-skills/evolver/src/gep/candidates.js
Normal file
@@ -0,0 +1,142 @@
|
||||
function stableHash(input) {
|
||||
// Deterministic lightweight hash (not cryptographic).
|
||||
const s = String(input || '');
|
||||
let h = 2166136261;
|
||||
for (let i = 0; i < s.length; i++) {
|
||||
h ^= s.charCodeAt(i);
|
||||
h = Math.imul(h, 16777619);
|
||||
}
|
||||
return (h >>> 0).toString(16).padStart(8, '0');
|
||||
}
|
||||
|
||||
function clip(text, maxChars) {
|
||||
const s = String(text || '');
|
||||
if (!maxChars || s.length <= maxChars) return s;
|
||||
return s.slice(0, Math.max(0, maxChars - 20)) + ' ...[TRUNCATED]';
|
||||
}
|
||||
|
||||
function toLines(text) {
|
||||
return String(text || '')
|
||||
.split('\n')
|
||||
.map(l => l.trimEnd())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function extractToolCalls(transcript) {
|
||||
const lines = toLines(transcript);
|
||||
const calls = [];
|
||||
for (const line of lines) {
|
||||
const m = line.match(/\[TOOL:\s*([^\]]+)\]/i);
|
||||
if (m && m[1]) calls.push(m[1].trim());
|
||||
}
|
||||
return calls;
|
||||
}
|
||||
|
||||
function countFreq(items) {
|
||||
const map = new Map();
|
||||
for (const it of items) map.set(it, (map.get(it) || 0) + 1);
|
||||
return map;
|
||||
}
|
||||
|
||||
function buildFiveQuestionsShape({ title, signals, evidence }) {
|
||||
// Keep it short and structured; this is a template, not a perfect inference.
|
||||
const input = 'Recent session transcript + memory snippets + user instructions';
|
||||
const output = 'A safe, auditable evolution patch guided by GEP assets';
|
||||
const invariants = 'Protocol order, small reversible patches, validation, append-only events';
|
||||
const params = `Signals: ${Array.isArray(signals) ? signals.join(', ') : ''}`.trim();
|
||||
const failurePoints = 'Missing signals, over-broad changes, skipped validation, missing knowledge solidification';
|
||||
return {
|
||||
title: String(title || '').slice(0, 120),
|
||||
input,
|
||||
output,
|
||||
invariants,
|
||||
params: params || 'Signals: (none)',
|
||||
failure_points: failurePoints,
|
||||
evidence: clip(evidence, 240),
|
||||
};
|
||||
}
|
||||
|
||||
function extractCapabilityCandidates({ recentSessionTranscript, signals }) {
|
||||
const candidates = [];
|
||||
const toolCalls = extractToolCalls(recentSessionTranscript);
|
||||
const freq = countFreq(toolCalls);
|
||||
|
||||
for (const [tool, count] of freq.entries()) {
|
||||
if (count < 2) continue;
|
||||
const title = `Repeated tool usage: ${tool}`;
|
||||
const evidence = `Observed ${count} occurrences of tool call marker for ${tool}.`;
|
||||
const shape = buildFiveQuestionsShape({ title, signals, evidence });
|
||||
candidates.push({
|
||||
type: 'CapabilityCandidate',
|
||||
id: `cand_${stableHash(title)}`,
|
||||
title,
|
||||
source: 'transcript',
|
||||
created_at: new Date().toISOString(),
|
||||
signals: Array.isArray(signals) ? signals : [],
|
||||
shape,
|
||||
});
|
||||
}
|
||||
|
||||
// Signals-as-candidates: capture recurring pain points as reusable capability shapes.
|
||||
const signalList = Array.isArray(signals) ? signals : [];
|
||||
const signalCandidates = [
|
||||
// Defensive signals
|
||||
{ signal: 'log_error', title: 'Repair recurring runtime errors' },
|
||||
{ signal: 'protocol_drift', title: 'Prevent protocol drift and enforce auditable outputs' },
|
||||
{ signal: 'windows_shell_incompatible', title: 'Avoid platform-specific shell assumptions (Windows compatibility)' },
|
||||
{ signal: 'session_logs_missing', title: 'Harden session log detection and fallback behavior' },
|
||||
// Opportunity signals (innovation)
|
||||
{ signal: 'user_feature_request', title: 'Implement user-requested feature' },
|
||||
{ signal: 'user_improvement_suggestion', title: 'Apply user improvement suggestion' },
|
||||
{ signal: 'perf_bottleneck', title: 'Resolve performance bottleneck' },
|
||||
{ signal: 'capability_gap', title: 'Fill capability gap' },
|
||||
{ signal: 'stable_success_plateau', title: 'Explore new strategies during stability plateau' },
|
||||
{ signal: 'external_opportunity', title: 'Evaluate external A2A asset for local adoption' },
|
||||
];
|
||||
|
||||
for (const sc of signalCandidates) {
|
||||
if (!signalList.includes(sc.signal)) continue;
|
||||
const evidence = `Signal present: ${sc.signal}`;
|
||||
const shape = buildFiveQuestionsShape({ title: sc.title, signals, evidence });
|
||||
candidates.push({
|
||||
type: 'CapabilityCandidate',
|
||||
id: `cand_${stableHash(sc.signal)}`,
|
||||
title: sc.title,
|
||||
source: 'signals',
|
||||
created_at: new Date().toISOString(),
|
||||
signals: signalList,
|
||||
shape,
|
||||
});
|
||||
}
|
||||
|
||||
// Dedup by id
|
||||
const seen = new Set();
|
||||
return candidates.filter(c => {
|
||||
if (!c || !c.id) return false;
|
||||
if (seen.has(c.id)) return false;
|
||||
seen.add(c.id);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
function renderCandidatesPreview(candidates, maxChars = 1400) {
|
||||
const list = Array.isArray(candidates) ? candidates : [];
|
||||
const lines = [];
|
||||
for (const c of list) {
|
||||
const s = c && c.shape ? c.shape : {};
|
||||
lines.push(`- ${c.id}: ${c.title}`);
|
||||
lines.push(` - input: ${s.input || ''}`);
|
||||
lines.push(` - output: ${s.output || ''}`);
|
||||
lines.push(` - invariants: ${s.invariants || ''}`);
|
||||
lines.push(` - params: ${s.params || ''}`);
|
||||
lines.push(` - failure_points: ${s.failure_points || ''}`);
|
||||
if (s.evidence) lines.push(` - evidence: ${s.evidence}`);
|
||||
}
|
||||
return clip(lines.join('\n'), maxChars);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
extractCapabilityCandidates,
|
||||
renderCandidatesPreview,
|
||||
};
|
||||
|
||||
65
archive/inactive-skills/evolver/src/gep/contentHash.js
Normal file
65
archive/inactive-skills/evolver/src/gep/contentHash.js
Normal file
@@ -0,0 +1,65 @@
|
||||
// Content-addressable hashing for GEP assets.
|
||||
// Provides canonical JSON serialization and SHA-256 based asset IDs.
|
||||
// This enables deduplication, tamper detection, and cross-node consistency.
|
||||
|
||||
const crypto = require('crypto');
|
||||
|
||||
// Schema version for all GEP asset types.
|
||||
// Bump MINOR for additive fields; MAJOR for breaking changes.
|
||||
const SCHEMA_VERSION = '1.5.0';
|
||||
|
||||
// Canonical JSON: deterministic serialization with sorted keys at all levels.
|
||||
// Arrays preserve order; non-finite numbers become null; undefined becomes null.
|
||||
function canonicalize(obj) {
|
||||
if (obj === null || obj === undefined) return 'null';
|
||||
if (typeof obj === 'boolean') return obj ? 'true' : 'false';
|
||||
if (typeof obj === 'number') {
|
||||
if (!Number.isFinite(obj)) return 'null';
|
||||
return String(obj);
|
||||
}
|
||||
if (typeof obj === 'string') return JSON.stringify(obj);
|
||||
if (Array.isArray(obj)) {
|
||||
return '[' + obj.map(canonicalize).join(',') + ']';
|
||||
}
|
||||
if (typeof obj === 'object') {
|
||||
const keys = Object.keys(obj).sort();
|
||||
const pairs = [];
|
||||
for (const k of keys) {
|
||||
pairs.push(JSON.stringify(k) + ':' + canonicalize(obj[k]));
|
||||
}
|
||||
return '{' + pairs.join(',') + '}';
|
||||
}
|
||||
return 'null';
|
||||
}
|
||||
|
||||
// Compute a content-addressable asset ID.
|
||||
// Excludes self-referential fields (asset_id itself) from the hash input.
|
||||
// Returns "sha256:<hex>".
|
||||
function computeAssetId(obj, excludeFields) {
|
||||
if (!obj || typeof obj !== 'object') return null;
|
||||
const exclude = new Set(Array.isArray(excludeFields) ? excludeFields : ['asset_id']);
|
||||
const clean = {};
|
||||
for (const k of Object.keys(obj)) {
|
||||
if (exclude.has(k)) continue;
|
||||
clean[k] = obj[k];
|
||||
}
|
||||
const canonical = canonicalize(clean);
|
||||
const hash = crypto.createHash('sha256').update(canonical, 'utf8').digest('hex');
|
||||
return 'sha256:' + hash;
|
||||
}
|
||||
|
||||
// Verify that an object's asset_id matches its content.
|
||||
function verifyAssetId(obj) {
|
||||
if (!obj || typeof obj !== 'object') return false;
|
||||
const claimed = obj.asset_id;
|
||||
if (!claimed || typeof claimed !== 'string') return false;
|
||||
const computed = computeAssetId(obj);
|
||||
return claimed === computed;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
SCHEMA_VERSION,
|
||||
canonicalize,
|
||||
computeAssetId,
|
||||
verifyAssetId,
|
||||
};
|
||||
55
archive/inactive-skills/evolver/src/gep/envFingerprint.js
Normal file
55
archive/inactive-skills/evolver/src/gep/envFingerprint.js
Normal file
@@ -0,0 +1,55 @@
|
||||
// Environment fingerprint capture for GEP assets.
|
||||
// Records the runtime environment so that cross-environment diffusion
|
||||
// success rates (GDI) can be measured scientifically.
|
||||
|
||||
const os = require('os');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const { getRepoRoot } = require('./paths');
|
||||
|
||||
// Capture a structured environment fingerprint.
|
||||
// This is embedded into Capsules, EvolutionEvents, and ValidationReports.
|
||||
function captureEnvFingerprint() {
|
||||
const repoRoot = getRepoRoot();
|
||||
let pkgVersion = null;
|
||||
try {
|
||||
const raw = fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8');
|
||||
const pkg = JSON.parse(raw);
|
||||
pkgVersion = pkg && pkg.version ? String(pkg.version) : null;
|
||||
} catch (e) {}
|
||||
|
||||
return {
|
||||
node_version: process.version,
|
||||
platform: process.platform,
|
||||
arch: process.arch,
|
||||
os_release: os.release(),
|
||||
evolver_version: pkgVersion,
|
||||
cwd: process.cwd(),
|
||||
captured_at: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
// Compute a short fingerprint key for comparison and grouping.
|
||||
// Two nodes with the same key are considered "same environment class".
|
||||
function envFingerprintKey(fp) {
|
||||
if (!fp || typeof fp !== 'object') return 'unknown';
|
||||
const parts = [
|
||||
fp.node_version || '',
|
||||
fp.platform || '',
|
||||
fp.arch || '',
|
||||
fp.evolver_version || '',
|
||||
].join('|');
|
||||
return crypto.createHash('sha256').update(parts, 'utf8').digest('hex').slice(0, 16);
|
||||
}
|
||||
|
||||
// Check if two fingerprints are from the same environment class.
|
||||
function isSameEnvClass(fpA, fpB) {
|
||||
return envFingerprintKey(fpA) === envFingerprintKey(fpB);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
captureEnvFingerprint,
|
||||
envFingerprintKey,
|
||||
isSameEnvClass,
|
||||
};
|
||||
770
archive/inactive-skills/evolver/src/gep/memoryGraph.js
Normal file
770
archive/inactive-skills/evolver/src/gep/memoryGraph.js
Normal file
@@ -0,0 +1,770 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { getMemoryDir } = require('./paths');
|
||||
const { normalizePersonalityState, isValidPersonalityState, personalityKey } = require('./personality');
|
||||
const { isValidMutation, normalizeMutation } = require('./mutation');
|
||||
|
||||
function ensureDir(dir) {
|
||||
try {
|
||||
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function stableHash(input) {
|
||||
const s = String(input || '');
|
||||
let h = 2166136261;
|
||||
for (let i = 0; i < s.length; i++) {
|
||||
h ^= s.charCodeAt(i);
|
||||
h = Math.imul(h, 16777619);
|
||||
}
|
||||
return (h >>> 0).toString(16).padStart(8, '0');
|
||||
}
|
||||
|
||||
function nowIso() {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
||||
function normalizeErrorSignature(text) {
|
||||
const s = String(text || '').trim();
|
||||
if (!s) return null;
|
||||
return (
|
||||
s
|
||||
.toLowerCase()
|
||||
// normalize Windows paths
|
||||
.replace(/[a-z]:\\[^ \n\r\t]+/gi, '<path>')
|
||||
// normalize Unix paths
|
||||
.replace(/\/[^ \n\r\t]+/g, '<path>')
|
||||
// normalize hex and numbers
|
||||
.replace(/\b0x[0-9a-f]+\b/gi, '<hex>')
|
||||
.replace(/\b\d+\b/g, '<n>')
|
||||
// normalize whitespace
|
||||
.replace(/\s+/g, ' ')
|
||||
.slice(0, 220)
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeSignalsForMatching(signals) {
|
||||
const list = Array.isArray(signals) ? signals : [];
|
||||
const out = [];
|
||||
for (const s of list) {
|
||||
const str = String(s || '').trim();
|
||||
if (!str) continue;
|
||||
if (str.startsWith('errsig:')) {
|
||||
const norm = normalizeErrorSignature(str.slice('errsig:'.length));
|
||||
if (norm) out.push(`errsig_norm:${stableHash(norm)}`);
|
||||
continue;
|
||||
}
|
||||
out.push(str);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function computeSignalKey(signals) {
|
||||
// Key must be stable across runs; normalize noisy signatures (paths, numbers).
|
||||
const list = normalizeSignalsForMatching(signals);
|
||||
const uniq = Array.from(new Set(list.filter(Boolean))).sort();
|
||||
return uniq.join('|') || '(none)';
|
||||
}
|
||||
|
||||
function extractErrorSignatureFromSignals(signals) {
|
||||
// Convention: signals can include "errsig:<raw>" emitted by signals extractor.
|
||||
const list = Array.isArray(signals) ? signals : [];
|
||||
for (const s of list) {
|
||||
const str = String(s || '');
|
||||
if (str.startsWith('errsig:')) return normalizeErrorSignature(str.slice('errsig:'.length));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function memoryGraphPath() {
|
||||
const memoryDir = getMemoryDir();
|
||||
return process.env.MEMORY_GRAPH_PATH || path.join(memoryDir, 'memory_graph.jsonl');
|
||||
}
|
||||
|
||||
function memoryGraphStatePath() {
|
||||
const memoryDir = getMemoryDir();
|
||||
return path.join(memoryDir, 'memory_graph_state.json');
|
||||
}
|
||||
|
||||
function appendJsonl(filePath, obj) {
|
||||
const dir = path.dirname(filePath);
|
||||
ensureDir(dir);
|
||||
fs.appendFileSync(filePath, JSON.stringify(obj) + '\n', 'utf8');
|
||||
}
|
||||
|
||||
function readJsonIfExists(filePath, fallback) {
|
||||
try {
|
||||
if (!fs.existsSync(filePath)) return fallback;
|
||||
const raw = fs.readFileSync(filePath, 'utf8');
|
||||
if (!raw.trim()) return fallback;
|
||||
return JSON.parse(raw);
|
||||
} catch (e) {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
function writeJsonAtomic(filePath, obj) {
|
||||
const dir = path.dirname(filePath);
|
||||
ensureDir(dir);
|
||||
const tmp = `${filePath}.tmp`;
|
||||
fs.writeFileSync(tmp, JSON.stringify(obj, null, 2) + '\n', 'utf8');
|
||||
fs.renameSync(tmp, filePath);
|
||||
}
|
||||
|
||||
function tryReadMemoryGraphEvents(limitLines = 2000) {
|
||||
try {
|
||||
const p = memoryGraphPath();
|
||||
if (!fs.existsSync(p)) return [];
|
||||
const raw = fs.readFileSync(p, 'utf8');
|
||||
const lines = raw
|
||||
.split('\n')
|
||||
.map(l => l.trim())
|
||||
.filter(Boolean);
|
||||
const recent = lines.slice(Math.max(0, lines.length - limitLines));
|
||||
return recent
|
||||
.map(l => {
|
||||
try {
|
||||
return JSON.parse(l);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(Boolean);
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function jaccard(aList, bList) {
|
||||
const aNorm = normalizeSignalsForMatching(aList);
|
||||
const bNorm = normalizeSignalsForMatching(bList);
|
||||
const a = new Set((Array.isArray(aNorm) ? aNorm : []).map(String));
|
||||
const b = new Set((Array.isArray(bNorm) ? bNorm : []).map(String));
|
||||
if (a.size === 0 && b.size === 0) return 1;
|
||||
if (a.size === 0 || b.size === 0) return 0;
|
||||
let inter = 0;
|
||||
for (const x of a) if (b.has(x)) inter++;
|
||||
const union = a.size + b.size - inter;
|
||||
return union === 0 ? 0 : inter / union;
|
||||
}
|
||||
|
||||
function decayWeight(updatedAtIso, halfLifeDays) {
|
||||
const hl = Number(halfLifeDays);
|
||||
if (!Number.isFinite(hl) || hl <= 0) return 1;
|
||||
const t = Date.parse(updatedAtIso);
|
||||
if (!Number.isFinite(t)) return 1;
|
||||
const ageDays = (Date.now() - t) / (1000 * 60 * 60 * 24);
|
||||
if (!Number.isFinite(ageDays) || ageDays <= 0) return 1;
|
||||
// Exponential half-life decay: weight = 0.5^(age/hl)
|
||||
return Math.pow(0.5, ageDays / hl);
|
||||
}
|
||||
|
||||
function aggregateEdges(events) {
|
||||
// Aggregate by (signal_key, gene_id) from outcome events.
|
||||
// Laplace smoothing to avoid 0/1 extremes.
|
||||
const map = new Map();
|
||||
for (const ev of events) {
|
||||
if (!ev || ev.type !== 'MemoryGraphEvent') continue;
|
||||
if (ev.kind !== 'outcome') continue;
|
||||
const signalKey = ev.signal && ev.signal.key ? String(ev.signal.key) : '(none)';
|
||||
const geneId = ev.gene && ev.gene.id ? String(ev.gene.id) : null;
|
||||
if (!geneId) continue;
|
||||
|
||||
const k = `${signalKey}::${geneId}`;
|
||||
const cur = map.get(k) || { signalKey, geneId, success: 0, fail: 0, last_ts: null, last_score: null };
|
||||
const status = ev.outcome && ev.outcome.status ? String(ev.outcome.status) : 'unknown';
|
||||
if (status === 'success') cur.success += 1;
|
||||
else if (status === 'failed') cur.fail += 1;
|
||||
|
||||
const ts = ev.ts || ev.created_at || ev.at;
|
||||
if (ts && (!cur.last_ts || Date.parse(ts) > Date.parse(cur.last_ts))) {
|
||||
cur.last_ts = ts;
|
||||
cur.last_score =
|
||||
ev.outcome && Number.isFinite(Number(ev.outcome.score)) ? Number(ev.outcome.score) : cur.last_score;
|
||||
}
|
||||
map.set(k, cur);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
function aggregateGeneOutcomes(events) {
|
||||
// Aggregate by gene_id from outcome events (gene -> outcome success probability).
|
||||
const map = new Map();
|
||||
for (const ev of events) {
|
||||
if (!ev || ev.type !== 'MemoryGraphEvent') continue;
|
||||
if (ev.kind !== 'outcome') continue;
|
||||
const geneId = ev.gene && ev.gene.id ? String(ev.gene.id) : null;
|
||||
if (!geneId) continue;
|
||||
const cur = map.get(geneId) || { geneId, success: 0, fail: 0, last_ts: null, last_score: null };
|
||||
const status = ev.outcome && ev.outcome.status ? String(ev.outcome.status) : 'unknown';
|
||||
if (status === 'success') cur.success += 1;
|
||||
else if (status === 'failed') cur.fail += 1;
|
||||
const ts = ev.ts || ev.created_at || ev.at;
|
||||
if (ts && (!cur.last_ts || Date.parse(ts) > Date.parse(cur.last_ts))) {
|
||||
cur.last_ts = ts;
|
||||
cur.last_score =
|
||||
ev.outcome && Number.isFinite(Number(ev.outcome.score)) ? Number(ev.outcome.score) : cur.last_score;
|
||||
}
|
||||
map.set(geneId, cur);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
function edgeExpectedSuccess(edge, opts) {
|
||||
const e = edge || { success: 0, fail: 0, last_ts: null };
|
||||
const succ = Number(e.success) || 0;
|
||||
const fail = Number(e.fail) || 0;
|
||||
const total = succ + fail;
|
||||
const p = (succ + 1) / (total + 2); // Laplace smoothing
|
||||
const halfLifeDays = opts && Number.isFinite(Number(opts.half_life_days)) ? Number(opts.half_life_days) : 30;
|
||||
const w = decayWeight(e.last_ts || '', halfLifeDays);
|
||||
return { p, w, total, value: p * w };
|
||||
}
|
||||
|
||||
function getMemoryAdvice({ signals, genes, driftEnabled }) {
|
||||
const events = tryReadMemoryGraphEvents(2000);
|
||||
const edges = aggregateEdges(events);
|
||||
const geneOutcomes = aggregateGeneOutcomes(events);
|
||||
const curSignals = Array.isArray(signals) ? signals : [];
|
||||
const curKey = computeSignalKey(curSignals);
|
||||
|
||||
const bannedGeneIds = new Set();
|
||||
const scoredGeneIds = [];
|
||||
|
||||
// Similarity: consider exact key first, then any key with overlap.
|
||||
const seenKeys = new Set();
|
||||
const candidateKeys = [];
|
||||
candidateKeys.push({ key: curKey, sim: 1 });
|
||||
seenKeys.add(curKey);
|
||||
|
||||
for (const ev of events) {
|
||||
if (!ev || ev.type !== 'MemoryGraphEvent') continue;
|
||||
const k = ev.signal && ev.signal.key ? String(ev.signal.key) : '(none)';
|
||||
if (seenKeys.has(k)) continue;
|
||||
const sigs = ev.signal && Array.isArray(ev.signal.signals) ? ev.signal.signals : [];
|
||||
const sim = jaccard(curSignals, sigs);
|
||||
if (sim >= 0.34) {
|
||||
candidateKeys.push({ key: k, sim });
|
||||
seenKeys.add(k);
|
||||
}
|
||||
}
|
||||
|
||||
const byGene = new Map();
|
||||
for (const ck of candidateKeys) {
|
||||
for (const g of Array.isArray(genes) ? genes : []) {
|
||||
if (!g || g.type !== 'Gene' || !g.id) continue;
|
||||
const k = `${ck.key}::${g.id}`;
|
||||
const edge = edges.get(k);
|
||||
const cur = byGene.get(g.id) || { geneId: g.id, best: 0, attempts: 0, prior: 0, prior_attempts: 0 };
|
||||
|
||||
// Signal->Gene edge score (if available)
|
||||
if (edge) {
|
||||
const ex = edgeExpectedSuccess(edge, { half_life_days: 30 });
|
||||
const weighted = ex.value * ck.sim;
|
||||
if (weighted > cur.best) cur.best = weighted;
|
||||
cur.attempts = Math.max(cur.attempts, ex.total);
|
||||
}
|
||||
|
||||
// Gene->Outcome prior (independent of signal): stabilizer when signal edges are sparse.
|
||||
const gEdge = geneOutcomes.get(String(g.id));
|
||||
if (gEdge) {
|
||||
const gx = edgeExpectedSuccess(gEdge, { half_life_days: 45 });
|
||||
cur.prior = Math.max(cur.prior, gx.value);
|
||||
cur.prior_attempts = Math.max(cur.prior_attempts, gx.total);
|
||||
}
|
||||
|
||||
byGene.set(g.id, cur);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [geneId, info] of byGene.entries()) {
|
||||
const combined = info.best > 0 ? info.best + info.prior * 0.12 : info.prior * 0.4;
|
||||
scoredGeneIds.push({ geneId, score: combined, attempts: info.attempts, prior: info.prior });
|
||||
// Low-efficiency path suppression (unless drift is explicit).
|
||||
if (!driftEnabled && info.attempts >= 2 && info.best < 0.18) {
|
||||
bannedGeneIds.add(geneId);
|
||||
}
|
||||
// Also suppress genes with consistently poor global outcomes when signal edges are sparse.
|
||||
if (!driftEnabled && info.attempts < 2 && info.prior_attempts >= 3 && info.prior < 0.12) {
|
||||
bannedGeneIds.add(geneId);
|
||||
}
|
||||
}
|
||||
|
||||
scoredGeneIds.sort((a, b) => b.score - a.score);
|
||||
const preferredGeneId = scoredGeneIds.length ? scoredGeneIds[0].geneId : null;
|
||||
|
||||
const explanation = [];
|
||||
if (preferredGeneId) explanation.push(`memory_prefer:${preferredGeneId}`);
|
||||
if (bannedGeneIds.size) explanation.push(`memory_ban:${Array.from(bannedGeneIds).slice(0, 6).join(',')}`);
|
||||
if (preferredGeneId) {
|
||||
const top = scoredGeneIds.find(x => x && x.geneId === preferredGeneId);
|
||||
if (top && Number.isFinite(Number(top.prior)) && top.prior > 0) explanation.push(`gene_prior:${top.prior.toFixed(3)}`);
|
||||
}
|
||||
if (driftEnabled) explanation.push('random_drift:enabled');
|
||||
|
||||
return {
|
||||
currentSignalKey: curKey,
|
||||
preferredGeneId,
|
||||
bannedGeneIds,
|
||||
explanation,
|
||||
};
|
||||
}
|
||||
|
||||
function recordSignalSnapshot({ signals, observations }) {
|
||||
const signalKey = computeSignalKey(signals);
|
||||
const ts = nowIso();
|
||||
const errsig = extractErrorSignatureFromSignals(signals);
|
||||
const ev = {
|
||||
type: 'MemoryGraphEvent',
|
||||
kind: 'signal',
|
||||
id: `mge_${Date.now()}_${stableHash(`${signalKey}|signal|${ts}`)}`,
|
||||
ts,
|
||||
signal: {
|
||||
key: signalKey,
|
||||
signals: Array.isArray(signals) ? signals : [],
|
||||
error_signature: errsig || null,
|
||||
},
|
||||
observed: observations && typeof observations === 'object' ? observations : null,
|
||||
};
|
||||
appendJsonl(memoryGraphPath(), ev);
|
||||
return ev;
|
||||
}
|
||||
|
||||
function buildHypothesisText({ signalKey, signals, geneId, geneCategory, driftEnabled }) {
|
||||
const sigCount = Array.isArray(signals) ? signals.length : 0;
|
||||
const drift = driftEnabled ? 'drift' : 'directed';
|
||||
const g = geneId ? `${geneId}${geneCategory ? `(${geneCategory})` : ''}` : '(none)';
|
||||
return `Given signal_key=${signalKey} with ${sigCount} signals, selecting gene=${g} under mode=${drift} is expected to reduce repeated errors and improve stability.`;
|
||||
}
|
||||
|
||||
function recordHypothesis({
|
||||
signals,
|
||||
mutation,
|
||||
personality_state,
|
||||
selectedGene,
|
||||
selector,
|
||||
driftEnabled,
|
||||
selectedBy,
|
||||
capsulesUsed,
|
||||
observations,
|
||||
}) {
|
||||
const signalKey = computeSignalKey(signals);
|
||||
const geneId = selectedGene && selectedGene.id ? String(selectedGene.id) : null;
|
||||
const geneCategory = selectedGene && selectedGene.category ? String(selectedGene.category) : null;
|
||||
const ts = nowIso();
|
||||
const errsig = extractErrorSignatureFromSignals(signals);
|
||||
const hypothesisId = `hyp_${Date.now()}_${stableHash(`${signalKey}|${geneId || 'none'}|${ts}`)}`;
|
||||
const personalityState = personality_state || null;
|
||||
const mutNorm = mutation && isValidMutation(mutation) ? normalizeMutation(mutation) : null;
|
||||
const psNorm = personalityState && isValidPersonalityState(personalityState) ? normalizePersonalityState(personalityState) : null;
|
||||
const ev = {
|
||||
type: 'MemoryGraphEvent',
|
||||
kind: 'hypothesis',
|
||||
id: `mge_${Date.now()}_${stableHash(`${hypothesisId}|${ts}`)}`,
|
||||
ts,
|
||||
signal: { key: signalKey, signals: Array.isArray(signals) ? signals : [], error_signature: errsig || null },
|
||||
hypothesis: {
|
||||
id: hypothesisId,
|
||||
text: buildHypothesisText({ signalKey, signals, geneId, geneCategory, driftEnabled }),
|
||||
predicted_outcome: { status: null, score: null },
|
||||
},
|
||||
mutation: mutNorm
|
||||
? {
|
||||
id: mutNorm.id,
|
||||
category: mutNorm.category,
|
||||
trigger_signals: mutNorm.trigger_signals,
|
||||
target: mutNorm.target,
|
||||
expected_effect: mutNorm.expected_effect,
|
||||
risk_level: mutNorm.risk_level,
|
||||
}
|
||||
: null,
|
||||
personality: psNorm
|
||||
? {
|
||||
key: personalityKey(psNorm),
|
||||
state: psNorm,
|
||||
}
|
||||
: null,
|
||||
gene: { id: geneId, category: geneCategory },
|
||||
action: {
|
||||
drift: !!driftEnabled,
|
||||
selected_by: selectedBy || 'selector',
|
||||
selector: selector || null,
|
||||
},
|
||||
capsules: {
|
||||
used: Array.isArray(capsulesUsed) ? capsulesUsed.map(String).filter(Boolean) : [],
|
||||
},
|
||||
observed: observations && typeof observations === 'object' ? observations : null,
|
||||
};
|
||||
appendJsonl(memoryGraphPath(), ev);
|
||||
return { hypothesisId, signalKey };
|
||||
}
|
||||
|
||||
function hasErrorSignal(signals) {
|
||||
const list = Array.isArray(signals) ? signals : [];
|
||||
return list.includes('log_error');
|
||||
}
|
||||
|
||||
function recordAttempt({
|
||||
signals,
|
||||
mutation,
|
||||
personality_state,
|
||||
selectedGene,
|
||||
selector,
|
||||
driftEnabled,
|
||||
selectedBy,
|
||||
hypothesisId,
|
||||
capsulesUsed,
|
||||
observations,
|
||||
}) {
|
||||
const signalKey = computeSignalKey(signals);
|
||||
const geneId = selectedGene && selectedGene.id ? String(selectedGene.id) : null;
|
||||
const geneCategory = selectedGene && selectedGene.category ? String(selectedGene.category) : null;
|
||||
const ts = nowIso();
|
||||
const errsig = extractErrorSignatureFromSignals(signals);
|
||||
const actionId = `act_${Date.now()}_${stableHash(`${signalKey}|${geneId || 'none'}|${ts}`)}`;
|
||||
const personalityState = personality_state || null;
|
||||
const mutNorm = mutation && isValidMutation(mutation) ? normalizeMutation(mutation) : null;
|
||||
const psNorm = personalityState && isValidPersonalityState(personalityState) ? normalizePersonalityState(personalityState) : null;
|
||||
const ev = {
|
||||
type: 'MemoryGraphEvent',
|
||||
kind: 'attempt',
|
||||
id: `mge_${Date.now()}_${stableHash(actionId)}`,
|
||||
ts,
|
||||
signal: { key: signalKey, signals: Array.isArray(signals) ? signals : [], error_signature: errsig || null },
|
||||
mutation: mutNorm
|
||||
? {
|
||||
id: mutNorm.id,
|
||||
category: mutNorm.category,
|
||||
trigger_signals: mutNorm.trigger_signals,
|
||||
target: mutNorm.target,
|
||||
expected_effect: mutNorm.expected_effect,
|
||||
risk_level: mutNorm.risk_level,
|
||||
}
|
||||
: null,
|
||||
personality: psNorm
|
||||
? {
|
||||
key: personalityKey(psNorm),
|
||||
state: psNorm,
|
||||
}
|
||||
: null,
|
||||
gene: { id: geneId, category: geneCategory },
|
||||
hypothesis: hypothesisId ? { id: String(hypothesisId) } : null,
|
||||
action: {
|
||||
id: actionId,
|
||||
drift: !!driftEnabled,
|
||||
selected_by: selectedBy || 'selector',
|
||||
selector: selector || null,
|
||||
},
|
||||
capsules: {
|
||||
used: Array.isArray(capsulesUsed) ? capsulesUsed.map(String).filter(Boolean) : [],
|
||||
},
|
||||
observed: observations && typeof observations === 'object' ? observations : null,
|
||||
};
|
||||
|
||||
appendJsonl(memoryGraphPath(), ev);
|
||||
|
||||
// State is mutable; graph is append-only.
|
||||
const statePath = memoryGraphStatePath();
|
||||
const state = readJsonIfExists(statePath, { last_action: null });
|
||||
state.last_action = {
|
||||
action_id: actionId,
|
||||
signal_key: signalKey,
|
||||
signals: Array.isArray(signals) ? signals : [],
|
||||
mutation_id: mutNorm ? mutNorm.id : null,
|
||||
mutation_category: mutNorm ? mutNorm.category : null,
|
||||
mutation_risk_level: mutNorm ? mutNorm.risk_level : null,
|
||||
personality_key: psNorm ? personalityKey(psNorm) : null,
|
||||
personality_state: psNorm || null,
|
||||
gene_id: geneId,
|
||||
gene_category: geneCategory,
|
||||
hypothesis_id: hypothesisId ? String(hypothesisId) : null,
|
||||
capsules_used: Array.isArray(capsulesUsed) ? capsulesUsed.map(String).filter(Boolean) : [],
|
||||
had_error: hasErrorSignal(signals),
|
||||
created_at: ts,
|
||||
outcome_recorded: false,
|
||||
baseline_observed: observations && typeof observations === 'object' ? observations : null,
|
||||
};
|
||||
writeJsonAtomic(statePath, state);
|
||||
|
||||
return { actionId, signalKey };
|
||||
}
|
||||
|
||||
function inferOutcomeFromSignals({ prevHadError, currentHasError }) {
|
||||
if (prevHadError && !currentHasError) return { status: 'success', score: 0.85, note: 'error_cleared' };
|
||||
if (prevHadError && currentHasError) return { status: 'failed', score: 0.2, note: 'error_persisted' };
|
||||
if (!prevHadError && currentHasError) return { status: 'failed', score: 0.15, note: 'new_error_appeared' };
|
||||
return { status: 'success', score: 0.6, note: 'stable_no_error' };
|
||||
}
|
||||
|
||||
function clamp01(x) {
|
||||
const n = Number(x);
|
||||
if (!Number.isFinite(n)) return 0;
|
||||
return Math.max(0, Math.min(1, n));
|
||||
}
|
||||
|
||||
function tryParseLastEvolutionEventOutcome(evidenceText) {
|
||||
// Scan tail text for an EvolutionEvent JSON line and extract its outcome.
|
||||
const s = String(evidenceText || '');
|
||||
if (!s) return null;
|
||||
const lines = s.split('\n').slice(-400);
|
||||
for (let i = lines.length - 1; i >= 0; i--) {
|
||||
const line = lines[i].trim();
|
||||
if (!line) continue;
|
||||
if (!line.includes('"type"') || !line.includes('EvolutionEvent')) continue;
|
||||
try {
|
||||
const obj = JSON.parse(line);
|
||||
if (!obj || obj.type !== 'EvolutionEvent') continue;
|
||||
const o = obj.outcome && typeof obj.outcome === 'object' ? obj.outcome : null;
|
||||
if (!o) continue;
|
||||
const status = o.status === 'success' || o.status === 'failed' ? o.status : null;
|
||||
const score = Number.isFinite(Number(o.score)) ? clamp01(Number(o.score)) : null;
|
||||
if (!status && score == null) continue;
|
||||
return {
|
||||
status: status || (score != null && score >= 0.5 ? 'success' : 'failed'),
|
||||
score: score != null ? score : status === 'success' ? 0.75 : 0.25,
|
||||
note: 'evolutionevent_observed',
|
||||
};
|
||||
} catch (e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function inferOutcomeEnhanced({ prevHadError, currentHasError, baselineObserved, currentObserved }) {
|
||||
const evidence =
|
||||
currentObserved &&
|
||||
currentObserved.evidence &&
|
||||
(currentObserved.evidence.recent_session_tail || currentObserved.evidence.today_log_tail)
|
||||
? currentObserved.evidence
|
||||
: null;
|
||||
const combinedEvidence = evidence
|
||||
? `${String(evidence.recent_session_tail || '')}\n${String(evidence.today_log_tail || '')}`
|
||||
: '';
|
||||
const observed = tryParseLastEvolutionEventOutcome(combinedEvidence);
|
||||
if (observed) return observed;
|
||||
|
||||
const base = inferOutcomeFromSignals({ prevHadError, currentHasError });
|
||||
|
||||
const prevErrCount =
|
||||
baselineObserved && Number.isFinite(Number(baselineObserved.recent_error_count))
|
||||
? Number(baselineObserved.recent_error_count)
|
||||
: null;
|
||||
const curErrCount =
|
||||
currentObserved && Number.isFinite(Number(currentObserved.recent_error_count))
|
||||
? Number(currentObserved.recent_error_count)
|
||||
: null;
|
||||
|
||||
let score = base.score;
|
||||
if (prevErrCount != null && curErrCount != null) {
|
||||
const delta = prevErrCount - curErrCount;
|
||||
score += Math.max(-0.12, Math.min(0.12, delta / 50));
|
||||
}
|
||||
|
||||
const prevScan =
|
||||
baselineObserved && Number.isFinite(Number(baselineObserved.scan_ms)) ? Number(baselineObserved.scan_ms) : null;
|
||||
const curScan =
|
||||
currentObserved && Number.isFinite(Number(currentObserved.scan_ms)) ? Number(currentObserved.scan_ms) : null;
|
||||
if (prevScan != null && curScan != null && prevScan > 0) {
|
||||
const ratio = (prevScan - curScan) / prevScan;
|
||||
score += Math.max(-0.06, Math.min(0.06, ratio));
|
||||
}
|
||||
|
||||
return { status: base.status, score: clamp01(score), note: `${base.note}|heuristic_delta` };
|
||||
}
|
||||
|
||||
function buildConfidenceEdgeEvent({ signalKey, signals, geneId, geneCategory, outcomeEventId, halfLifeDays }) {
|
||||
const events = tryReadMemoryGraphEvents(2000);
|
||||
const edges = aggregateEdges(events);
|
||||
const k = `${signalKey}::${geneId}`;
|
||||
const edge = edges.get(k) || { success: 0, fail: 0, last_ts: null };
|
||||
const ex = edgeExpectedSuccess(edge, { half_life_days: halfLifeDays });
|
||||
const ts = nowIso();
|
||||
return {
|
||||
type: 'MemoryGraphEvent',
|
||||
kind: 'confidence_edge',
|
||||
id: `mge_${Date.now()}_${stableHash(`${signalKey}|${geneId}|confidence|${ts}`)}`,
|
||||
ts,
|
||||
signal: { key: signalKey, signals: Array.isArray(signals) ? signals : [] },
|
||||
gene: { id: geneId, category: geneCategory || null },
|
||||
edge: { signal_key: signalKey, gene_id: geneId },
|
||||
stats: {
|
||||
success: Number(edge.success) || 0,
|
||||
fail: Number(edge.fail) || 0,
|
||||
attempts: Number(ex.total) || 0,
|
||||
p: ex.p,
|
||||
decay_weight: ex.w,
|
||||
value: ex.value,
|
||||
half_life_days: halfLifeDays,
|
||||
updated_at: ts,
|
||||
},
|
||||
derived_from: { outcome_event_id: outcomeEventId || null },
|
||||
};
|
||||
}
|
||||
|
||||
function buildGeneOutcomeConfidenceEvent({ geneId, geneCategory, outcomeEventId, halfLifeDays }) {
|
||||
const events = tryReadMemoryGraphEvents(2000);
|
||||
const geneOutcomes = aggregateGeneOutcomes(events);
|
||||
const edge = geneOutcomes.get(String(geneId)) || { success: 0, fail: 0, last_ts: null };
|
||||
const ex = edgeExpectedSuccess(edge, { half_life_days: halfLifeDays });
|
||||
const ts = nowIso();
|
||||
return {
|
||||
type: 'MemoryGraphEvent',
|
||||
kind: 'confidence_gene_outcome',
|
||||
id: `mge_${Date.now()}_${stableHash(`${geneId}|gene_outcome|confidence|${ts}`)}`,
|
||||
ts,
|
||||
gene: { id: String(geneId), category: geneCategory || null },
|
||||
edge: { gene_id: String(geneId) },
|
||||
stats: {
|
||||
success: Number(edge.success) || 0,
|
||||
fail: Number(edge.fail) || 0,
|
||||
attempts: Number(ex.total) || 0,
|
||||
p: ex.p,
|
||||
decay_weight: ex.w,
|
||||
value: ex.value,
|
||||
half_life_days: halfLifeDays,
|
||||
updated_at: ts,
|
||||
},
|
||||
derived_from: { outcome_event_id: outcomeEventId || null },
|
||||
};
|
||||
}
|
||||
|
||||
function recordOutcomeFromState({ signals, observations }) {
|
||||
const statePath = memoryGraphStatePath();
|
||||
const state = readJsonIfExists(statePath, { last_action: null });
|
||||
const last = state && state.last_action ? state.last_action : null;
|
||||
if (!last || !last.action_id) return null;
|
||||
if (last.outcome_recorded) return null;
|
||||
|
||||
const currentHasError = hasErrorSignal(signals);
|
||||
const inferred = inferOutcomeEnhanced({
|
||||
prevHadError: !!last.had_error,
|
||||
currentHasError,
|
||||
baselineObserved: last.baseline_observed || null,
|
||||
currentObserved: observations || null,
|
||||
});
|
||||
const ts = nowIso();
|
||||
const errsig = extractErrorSignatureFromSignals(signals);
|
||||
const ev = {
|
||||
type: 'MemoryGraphEvent',
|
||||
kind: 'outcome',
|
||||
id: `mge_${Date.now()}_${stableHash(`${last.action_id}|outcome|${ts}`)}`,
|
||||
ts,
|
||||
signal: {
|
||||
key: String(last.signal_key || '(none)'),
|
||||
signals: Array.isArray(last.signals) ? last.signals : [],
|
||||
error_signature: errsig || null,
|
||||
},
|
||||
mutation:
|
||||
last.mutation_id || last.mutation_category || last.mutation_risk_level
|
||||
? {
|
||||
id: last.mutation_id || null,
|
||||
category: last.mutation_category || null,
|
||||
risk_level: last.mutation_risk_level || null,
|
||||
}
|
||||
: null,
|
||||
personality:
|
||||
last.personality_key || last.personality_state
|
||||
? {
|
||||
key: last.personality_key || null,
|
||||
state: last.personality_state || null,
|
||||
}
|
||||
: null,
|
||||
gene: { id: last.gene_id || null, category: last.gene_category || null },
|
||||
action: { id: String(last.action_id) },
|
||||
hypothesis: last.hypothesis_id ? { id: String(last.hypothesis_id) } : null,
|
||||
outcome: {
|
||||
status: inferred.status,
|
||||
score: inferred.score,
|
||||
note: inferred.note,
|
||||
observed: { current_signals: Array.isArray(signals) ? signals : [] },
|
||||
},
|
||||
confidence: {
|
||||
// This is an interpretable, decayed success estimate derived from outcomes; aggregation is computed at read-time.
|
||||
half_life_days: 30,
|
||||
},
|
||||
observed: observations && typeof observations === 'object' ? observations : null,
|
||||
baseline: last.baseline_observed || null,
|
||||
capsules: {
|
||||
used: Array.isArray(last.capsules_used) ? last.capsules_used : [],
|
||||
},
|
||||
};
|
||||
|
||||
appendJsonl(memoryGraphPath(), ev);
|
||||
|
||||
// Persist explicit confidence snapshots (append-only) for auditability.
|
||||
try {
|
||||
if (last.gene_id) {
|
||||
const edgeEv = buildConfidenceEdgeEvent({
|
||||
signalKey: String(last.signal_key || '(none)'),
|
||||
signals: Array.isArray(last.signals) ? last.signals : [],
|
||||
geneId: String(last.gene_id),
|
||||
geneCategory: last.gene_category || null,
|
||||
outcomeEventId: ev.id,
|
||||
halfLifeDays: 30,
|
||||
});
|
||||
appendJsonl(memoryGraphPath(), edgeEv);
|
||||
|
||||
const geneEv = buildGeneOutcomeConfidenceEvent({
|
||||
geneId: String(last.gene_id),
|
||||
geneCategory: last.gene_category || null,
|
||||
outcomeEventId: ev.id,
|
||||
halfLifeDays: 45,
|
||||
});
|
||||
appendJsonl(memoryGraphPath(), geneEv);
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
last.outcome_recorded = true;
|
||||
last.outcome_recorded_at = ts;
|
||||
state.last_action = last;
|
||||
writeJsonAtomic(statePath, state);
|
||||
|
||||
return ev;
|
||||
}
|
||||
|
||||
function recordExternalCandidate({ asset, source, signals }) {
|
||||
// Append-only annotation: external assets enter as candidates only.
|
||||
// This does not affect outcome aggregation (which only uses kind === 'outcome').
|
||||
const a = asset && typeof asset === 'object' ? asset : null;
|
||||
const type = a && a.type ? String(a.type) : null;
|
||||
const id = a && a.id ? String(a.id) : null;
|
||||
if (!type || !id) return null;
|
||||
|
||||
const ts = nowIso();
|
||||
const signalKey = computeSignalKey(signals);
|
||||
const ev = {
|
||||
type: 'MemoryGraphEvent',
|
||||
kind: 'external_candidate',
|
||||
id: `mge_${Date.now()}_${stableHash(`${type}|${id}|external|${ts}`)}`,
|
||||
ts,
|
||||
signal: { key: signalKey, signals: Array.isArray(signals) ? signals : [] },
|
||||
external: {
|
||||
source: source || 'external',
|
||||
received_at: ts,
|
||||
},
|
||||
asset: { type, id },
|
||||
candidate: {
|
||||
// Minimal hints for later local triggering/validation.
|
||||
trigger: type === 'Capsule' && Array.isArray(a.trigger) ? a.trigger : [],
|
||||
gene: type === 'Capsule' && a.gene ? String(a.gene) : null,
|
||||
confidence: type === 'Capsule' && Number.isFinite(Number(a.confidence)) ? Number(a.confidence) : null,
|
||||
},
|
||||
};
|
||||
|
||||
appendJsonl(memoryGraphPath(), ev);
|
||||
return ev;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
memoryGraphPath,
|
||||
computeSignalKey,
|
||||
tryReadMemoryGraphEvents,
|
||||
getMemoryAdvice,
|
||||
recordSignalSnapshot,
|
||||
recordHypothesis,
|
||||
recordAttempt,
|
||||
recordOutcomeFromState,
|
||||
recordExternalCandidate,
|
||||
};
|
||||
|
||||
174
archive/inactive-skills/evolver/src/gep/mutation.js
Normal file
174
archive/inactive-skills/evolver/src/gep/mutation.js
Normal file
@@ -0,0 +1,174 @@
|
||||
function clamp01(x) {
|
||||
const n = Number(x);
|
||||
if (!Number.isFinite(n)) return 0;
|
||||
return Math.max(0, Math.min(1, n));
|
||||
}
|
||||
|
||||
function nowTsMs() {
|
||||
return Date.now();
|
||||
}
|
||||
|
||||
function uniqStrings(list) {
|
||||
const out = [];
|
||||
const seen = new Set();
|
||||
for (const x of Array.isArray(list) ? list : []) {
|
||||
const s = String(x || '').trim();
|
||||
if (!s) continue;
|
||||
if (seen.has(s)) continue;
|
||||
seen.add(s);
|
||||
out.push(s);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function hasErrorishSignal(signals) {
|
||||
const list = Array.isArray(signals) ? signals.map(s => String(s || '')) : [];
|
||||
if (list.includes('log_error')) return true;
|
||||
if (list.some(s => s.startsWith('errsig:') || s.startsWith('errsig_norm:'))) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Opportunity signals that indicate a chance to innovate (not just fix).
|
||||
var OPPORTUNITY_SIGNALS = [
|
||||
'user_feature_request',
|
||||
'user_improvement_suggestion',
|
||||
'perf_bottleneck',
|
||||
'capability_gap',
|
||||
'stable_success_plateau',
|
||||
'external_opportunity',
|
||||
];
|
||||
|
||||
function hasOpportunitySignal(signals) {
|
||||
var list = Array.isArray(signals) ? signals.map(function (s) { return String(s || ''); }) : [];
|
||||
for (var i = 0; i < OPPORTUNITY_SIGNALS.length; i++) {
|
||||
if (list.includes(OPPORTUNITY_SIGNALS[i])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function mutationCategoryFromContext({ signals, driftEnabled }) {
|
||||
if (hasErrorishSignal(signals)) return 'repair';
|
||||
if (driftEnabled) return 'innovate';
|
||||
// Auto-innovate: opportunity signals present and no errors
|
||||
if (hasOpportunitySignal(signals)) return 'innovate';
|
||||
return 'optimize';
|
||||
}
|
||||
|
||||
function expectedEffectFromCategory(category) {
|
||||
const c = String(category || '');
|
||||
if (c === 'repair') return 'reduce runtime errors, increase stability, and lower failure rate';
|
||||
if (c === 'optimize') return 'improve success rate and reduce repeated operational cost';
|
||||
if (c === 'innovate') return 'explore new strategy combinations to escape local optimum';
|
||||
return 'improve robustness and success probability';
|
||||
}
|
||||
|
||||
function targetFromGene(selectedGene) {
|
||||
if (selectedGene && selectedGene.id) return `gene:${String(selectedGene.id)}`;
|
||||
return 'behavior:protocol';
|
||||
}
|
||||
|
||||
function isHighRiskPersonality(p) {
|
||||
// Conservative definition: low rigor or high risk_tolerance is treated as high-risk personality.
|
||||
const rigor = p && Number.isFinite(Number(p.rigor)) ? Number(p.rigor) : null;
|
||||
const riskTol = p && Number.isFinite(Number(p.risk_tolerance)) ? Number(p.risk_tolerance) : null;
|
||||
if (rigor != null && rigor < 0.5) return true;
|
||||
if (riskTol != null && riskTol > 0.6) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function isHighRiskMutationAllowed(personalityState) {
|
||||
const rigor = personalityState && Number.isFinite(Number(personalityState.rigor)) ? Number(personalityState.rigor) : 0;
|
||||
const riskTol =
|
||||
personalityState && Number.isFinite(Number(personalityState.risk_tolerance))
|
||||
? Number(personalityState.risk_tolerance)
|
||||
: 1;
|
||||
return rigor >= 0.6 && riskTol <= 0.5;
|
||||
}
|
||||
|
||||
function buildMutation({
|
||||
signals,
|
||||
selectedGene,
|
||||
driftEnabled,
|
||||
personalityState,
|
||||
allowHighRisk = false,
|
||||
target,
|
||||
expected_effect,
|
||||
} = {}) {
|
||||
const ts = nowTsMs();
|
||||
const category = mutationCategoryFromContext({ signals, driftEnabled: !!driftEnabled });
|
||||
const triggerSignals = uniqStrings(signals);
|
||||
|
||||
const base = {
|
||||
type: 'Mutation',
|
||||
id: `mut_${ts}`,
|
||||
category,
|
||||
trigger_signals: triggerSignals,
|
||||
target: String(target || targetFromGene(selectedGene)),
|
||||
expected_effect: String(expected_effect || expectedEffectFromCategory(category)),
|
||||
risk_level: 'low',
|
||||
};
|
||||
|
||||
// Default risk assignment: innovate is medium; others low.
|
||||
if (category === 'innovate') base.risk_level = 'medium';
|
||||
|
||||
// Optional high-risk escalation (rare, and guarded by strict safety constraints).
|
||||
if (allowHighRisk && category === 'innovate') {
|
||||
base.risk_level = 'high';
|
||||
}
|
||||
|
||||
// Safety constraints (hard):
|
||||
// - forbid innovate + high-risk personality (downgrade innovation to optimize)
|
||||
// - forbid high-risk mutation unless personality satisfies constraints
|
||||
const highRiskPersonality = isHighRiskPersonality(personalityState || null);
|
||||
if (base.category === 'innovate' && highRiskPersonality) {
|
||||
base.category = 'optimize';
|
||||
base.expected_effect = 'safety downgrade: optimize under high-risk personality (avoid innovate+high-risk combo)';
|
||||
base.risk_level = 'low';
|
||||
base.trigger_signals = uniqStrings([...(base.trigger_signals || []), 'safety:avoid_innovate_with_high_risk_personality']);
|
||||
}
|
||||
|
||||
if (base.risk_level === 'high' && !isHighRiskMutationAllowed(personalityState || null)) {
|
||||
// Downgrade rather than emit illegal high-risk mutation.
|
||||
base.risk_level = 'medium';
|
||||
base.trigger_signals = uniqStrings([...(base.trigger_signals || []), 'safety:downgrade_high_risk']);
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
function isValidMutation(obj) {
|
||||
if (!obj || typeof obj !== 'object') return false;
|
||||
if (obj.type !== 'Mutation') return false;
|
||||
if (!obj.id || typeof obj.id !== 'string') return false;
|
||||
if (!obj.category || !['repair', 'optimize', 'innovate'].includes(String(obj.category))) return false;
|
||||
if (!Array.isArray(obj.trigger_signals)) return false;
|
||||
if (!obj.target || typeof obj.target !== 'string') return false;
|
||||
if (!obj.expected_effect || typeof obj.expected_effect !== 'string') return false;
|
||||
if (!obj.risk_level || !['low', 'medium', 'high'].includes(String(obj.risk_level))) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function normalizeMutation(obj) {
|
||||
const m = obj && typeof obj === 'object' ? obj : {};
|
||||
const out = {
|
||||
type: 'Mutation',
|
||||
id: typeof m.id === 'string' ? m.id : `mut_${nowTsMs()}`,
|
||||
category: ['repair', 'optimize', 'innovate'].includes(String(m.category)) ? String(m.category) : 'optimize',
|
||||
trigger_signals: uniqStrings(m.trigger_signals),
|
||||
target: typeof m.target === 'string' ? m.target : 'behavior:protocol',
|
||||
expected_effect: typeof m.expected_effect === 'string' ? m.expected_effect : expectedEffectFromCategory(m.category),
|
||||
risk_level: ['low', 'medium', 'high'].includes(String(m.risk_level)) ? String(m.risk_level) : 'low',
|
||||
};
|
||||
return out;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
clamp01,
|
||||
buildMutation,
|
||||
isValidMutation,
|
||||
normalizeMutation,
|
||||
isHighRiskMutationAllowed,
|
||||
isHighRiskPersonality,
|
||||
hasOpportunitySignal,
|
||||
};
|
||||
|
||||
23
archive/inactive-skills/evolver/src/gep/paths.js
Normal file
23
archive/inactive-skills/evolver/src/gep/paths.js
Normal file
@@ -0,0 +1,23 @@
|
||||
const path = require('path');
|
||||
|
||||
function getRepoRoot() {
|
||||
// src/gep/paths.js -> repo root
|
||||
return path.resolve(__dirname, '..', '..');
|
||||
}
|
||||
|
||||
function getMemoryDir() {
|
||||
const repoRoot = getRepoRoot();
|
||||
return process.env.MEMORY_DIR || path.join(repoRoot, 'memory');
|
||||
}
|
||||
|
||||
function getGepAssetsDir() {
|
||||
const repoRoot = getRepoRoot();
|
||||
return process.env.GEP_ASSETS_DIR || path.join(repoRoot, 'assets', 'gep');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getRepoRoot,
|
||||
getMemoryDir,
|
||||
getGepAssetsDir,
|
||||
};
|
||||
|
||||
355
archive/inactive-skills/evolver/src/gep/personality.js
Normal file
355
archive/inactive-skills/evolver/src/gep/personality.js
Normal file
@@ -0,0 +1,355 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { getMemoryDir } = require('./paths');
|
||||
const { hasOpportunitySignal } = require('./mutation');
|
||||
|
||||
function nowIso() {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
||||
function clamp01(x) {
|
||||
const n = Number(x);
|
||||
if (!Number.isFinite(n)) return 0;
|
||||
return Math.max(0, Math.min(1, n));
|
||||
}
|
||||
|
||||
function ensureDir(dir) {
|
||||
try {
|
||||
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function readJsonIfExists(filePath, fallback) {
|
||||
try {
|
||||
if (!fs.existsSync(filePath)) return fallback;
|
||||
const raw = fs.readFileSync(filePath, 'utf8');
|
||||
if (!raw.trim()) return fallback;
|
||||
return JSON.parse(raw);
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
function writeJsonAtomic(filePath, obj) {
|
||||
const dir = path.dirname(filePath);
|
||||
ensureDir(dir);
|
||||
const tmp = `${filePath}.tmp`;
|
||||
fs.writeFileSync(tmp, JSON.stringify(obj, null, 2) + '\n', 'utf8');
|
||||
fs.renameSync(tmp, filePath);
|
||||
}
|
||||
|
||||
function personalityFilePath() {
|
||||
const memoryDir = getMemoryDir();
|
||||
return path.join(memoryDir, 'personality_state.json');
|
||||
}
|
||||
|
||||
function defaultPersonalityState() {
|
||||
// Conservative defaults: protocol-first, safe, low-risk.
|
||||
return {
|
||||
type: 'PersonalityState',
|
||||
rigor: 0.7,
|
||||
creativity: 0.35,
|
||||
verbosity: 0.25,
|
||||
risk_tolerance: 0.4,
|
||||
obedience: 0.85,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizePersonalityState(state) {
|
||||
const s = state && typeof state === 'object' ? state : {};
|
||||
return {
|
||||
type: 'PersonalityState',
|
||||
rigor: clamp01(s.rigor),
|
||||
creativity: clamp01(s.creativity),
|
||||
verbosity: clamp01(s.verbosity),
|
||||
risk_tolerance: clamp01(s.risk_tolerance),
|
||||
obedience: clamp01(s.obedience),
|
||||
};
|
||||
}
|
||||
|
||||
function isValidPersonalityState(obj) {
|
||||
if (!obj || typeof obj !== 'object') return false;
|
||||
if (obj.type !== 'PersonalityState') return false;
|
||||
for (const k of ['rigor', 'creativity', 'verbosity', 'risk_tolerance', 'obedience']) {
|
||||
const v = obj[k];
|
||||
if (!Number.isFinite(Number(v))) return false;
|
||||
const n = Number(v);
|
||||
if (n < 0 || n > 1) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function roundToStep(x, step) {
|
||||
const s = Number(step);
|
||||
if (!Number.isFinite(s) || s <= 0) return x;
|
||||
return Math.round(Number(x) / s) * s;
|
||||
}
|
||||
|
||||
function personalityKey(state) {
|
||||
const s = normalizePersonalityState(state);
|
||||
const step = 0.1;
|
||||
const r = roundToStep(s.rigor, step).toFixed(1);
|
||||
const c = roundToStep(s.creativity, step).toFixed(1);
|
||||
const v = roundToStep(s.verbosity, step).toFixed(1);
|
||||
const rt = roundToStep(s.risk_tolerance, step).toFixed(1);
|
||||
const o = roundToStep(s.obedience, step).toFixed(1);
|
||||
return `rigor=${r}|creativity=${c}|verbosity=${v}|risk_tolerance=${rt}|obedience=${o}`;
|
||||
}
|
||||
|
||||
function getParamDeltas(fromState, toState) {
|
||||
const a = normalizePersonalityState(fromState);
|
||||
const b = normalizePersonalityState(toState);
|
||||
const deltas = [];
|
||||
for (const k of ['rigor', 'creativity', 'verbosity', 'risk_tolerance', 'obedience']) {
|
||||
deltas.push({ param: k, delta: Number(b[k]) - Number(a[k]) });
|
||||
}
|
||||
deltas.sort((x, y) => Math.abs(y.delta) - Math.abs(x.delta));
|
||||
return deltas;
|
||||
}
|
||||
|
||||
function personalityScore(statsEntry) {
|
||||
const e = statsEntry && typeof statsEntry === 'object' ? statsEntry : {};
|
||||
const succ = Number(e.success) || 0;
|
||||
const fail = Number(e.fail) || 0;
|
||||
const total = succ + fail;
|
||||
// Laplace-smoothed success probability
|
||||
const p = (succ + 1) / (total + 2);
|
||||
// Penalize tiny-sample overconfidence
|
||||
const sampleWeight = Math.min(1, total / 8);
|
||||
// Use avg_score (if present) as mild quality proxy
|
||||
const avg = Number.isFinite(Number(e.avg_score)) ? Number(e.avg_score) : null;
|
||||
const q = avg == null ? 0.5 : clamp01(avg);
|
||||
return p * 0.75 + q * 0.25 * sampleWeight;
|
||||
}
|
||||
|
||||
function chooseBestKnownPersonality(statsByKey) {
|
||||
const stats = statsByKey && typeof statsByKey === 'object' ? statsByKey : {};
|
||||
let best = null;
|
||||
for (const [k, entry] of Object.entries(stats)) {
|
||||
const e = entry || {};
|
||||
const total = (Number(e.success) || 0) + (Number(e.fail) || 0);
|
||||
if (total < 3) continue;
|
||||
const sc = personalityScore(e);
|
||||
if (!best || sc > best.score) best = { key: k, score: sc, entry: e };
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
function parseKeyToState(key) {
|
||||
// key format: rigor=0.7|creativity=0.3|...
|
||||
const out = defaultPersonalityState();
|
||||
const parts = String(key || '').split('|').map(s => s.trim()).filter(Boolean);
|
||||
for (const p of parts) {
|
||||
const [k, v] = p.split('=').map(x => String(x || '').trim());
|
||||
if (!k) continue;
|
||||
if (!['rigor', 'creativity', 'verbosity', 'risk_tolerance', 'obedience'].includes(k)) continue;
|
||||
out[k] = clamp01(Number(v));
|
||||
}
|
||||
return normalizePersonalityState(out);
|
||||
}
|
||||
|
||||
function applyPersonalityMutations(state, mutations) {
|
||||
let cur = normalizePersonalityState(state);
|
||||
const muts = Array.isArray(mutations) ? mutations : [];
|
||||
const applied = [];
|
||||
let count = 0;
|
||||
for (const m of muts) {
|
||||
if (!m || typeof m !== 'object') continue;
|
||||
const param = String(m.param || '').trim();
|
||||
if (!['rigor', 'creativity', 'verbosity', 'risk_tolerance', 'obedience'].includes(param)) continue;
|
||||
const delta = Number(m.delta);
|
||||
if (!Number.isFinite(delta)) continue;
|
||||
const clipped = Math.max(-0.2, Math.min(0.2, delta));
|
||||
cur[param] = clamp01(Number(cur[param]) + clipped);
|
||||
applied.push({ type: 'PersonalityMutation', param, delta: clipped, reason: String(m.reason || '').slice(0, 140) });
|
||||
count += 1;
|
||||
if (count >= 2) break;
|
||||
}
|
||||
return { state: cur, applied };
|
||||
}
|
||||
|
||||
function proposeMutations({ baseState, reason, driftEnabled, signals }) {
|
||||
const s = normalizePersonalityState(baseState);
|
||||
const sig = Array.isArray(signals) ? signals.map(x => String(x || '')) : [];
|
||||
const muts = [];
|
||||
|
||||
const r = String(reason || '');
|
||||
if (driftEnabled) {
|
||||
muts.push({ type: 'PersonalityMutation', param: 'creativity', delta: +0.1, reason: r || 'drift enabled' });
|
||||
// Keep risk bounded under drift by default.
|
||||
muts.push({ type: 'PersonalityMutation', param: 'risk_tolerance', delta: -0.05, reason: 'drift safety clamp' });
|
||||
} else if (sig.includes('protocol_drift')) {
|
||||
muts.push({ type: 'PersonalityMutation', param: 'obedience', delta: +0.1, reason: r || 'protocol drift' });
|
||||
muts.push({ type: 'PersonalityMutation', param: 'rigor', delta: +0.05, reason: 'tighten protocol compliance' });
|
||||
} else if (sig.includes('log_error') || sig.some(x => x.startsWith('errsig:') || x.startsWith('errsig_norm:'))) {
|
||||
muts.push({ type: 'PersonalityMutation', param: 'rigor', delta: +0.1, reason: r || 'repair instability' });
|
||||
muts.push({ type: 'PersonalityMutation', param: 'risk_tolerance', delta: -0.1, reason: 'reduce risky changes under errors' });
|
||||
} else if (hasOpportunitySignal(sig)) {
|
||||
// Opportunity detected: nudge towards creativity to enable innovation.
|
||||
muts.push({ type: 'PersonalityMutation', param: 'creativity', delta: +0.1, reason: r || 'opportunity signal detected' });
|
||||
muts.push({ type: 'PersonalityMutation', param: 'risk_tolerance', delta: +0.05, reason: 'allow exploration for innovation' });
|
||||
} else {
|
||||
// Plateau-like generic: slightly increase rigor, slightly decrease verbosity (more concise execution).
|
||||
muts.push({ type: 'PersonalityMutation', param: 'rigor', delta: +0.05, reason: r || 'stability bias' });
|
||||
muts.push({ type: 'PersonalityMutation', param: 'verbosity', delta: -0.05, reason: 'reduce noise' });
|
||||
}
|
||||
|
||||
// If already very high obedience, avoid pushing it further; swap second mutation to creativity.
|
||||
if (s.obedience >= 0.95) {
|
||||
const idx = muts.findIndex(x => x.param === 'obedience');
|
||||
if (idx >= 0) muts[idx] = { type: 'PersonalityMutation', param: 'creativity', delta: +0.05, reason: 'obedience saturated' };
|
||||
}
|
||||
return muts;
|
||||
}
|
||||
|
||||
function shouldTriggerPersonalityMutation({ driftEnabled, recentEvents }) {
|
||||
if (driftEnabled) return { ok: true, reason: 'drift enabled' };
|
||||
const list = Array.isArray(recentEvents) ? recentEvents : [];
|
||||
const tail = list.slice(-6);
|
||||
const outcomes = tail
|
||||
.map(e => (e && e.outcome && e.outcome.status ? String(e.outcome.status) : null))
|
||||
.filter(Boolean);
|
||||
if (outcomes.length >= 4) {
|
||||
const recentFailed = outcomes.slice(-4).filter(x => x === 'failed').length;
|
||||
if (recentFailed >= 3) return { ok: true, reason: 'long failure streak' };
|
||||
}
|
||||
// Mutation consecutive failure proxy: last 3 events that have mutation_id.
|
||||
const withMut = tail.filter(e => e && typeof e.mutation_id === 'string' && e.mutation_id);
|
||||
if (withMut.length >= 3) {
|
||||
const last3 = withMut.slice(-3);
|
||||
const fail3 = last3.filter(e => e && e.outcome && e.outcome.status === 'failed').length;
|
||||
if (fail3 >= 3) return { ok: true, reason: 'mutation consecutive failures' };
|
||||
}
|
||||
return { ok: false, reason: '' };
|
||||
}
|
||||
|
||||
function loadPersonalityModel() {
|
||||
const p = personalityFilePath();
|
||||
const fallback = {
|
||||
version: 1,
|
||||
current: defaultPersonalityState(),
|
||||
stats: {},
|
||||
history: [],
|
||||
updated_at: nowIso(),
|
||||
};
|
||||
const raw = readJsonIfExists(p, fallback);
|
||||
const cur = normalizePersonalityState(raw && raw.current ? raw.current : defaultPersonalityState());
|
||||
const stats = raw && typeof raw.stats === 'object' ? raw.stats : {};
|
||||
const history = Array.isArray(raw && raw.history) ? raw.history : [];
|
||||
return { version: 1, current: cur, stats, history, updated_at: raw && raw.updated_at ? raw.updated_at : nowIso() };
|
||||
}
|
||||
|
||||
function savePersonalityModel(model) {
|
||||
const m = model && typeof model === 'object' ? model : {};
|
||||
const out = {
|
||||
version: 1,
|
||||
current: normalizePersonalityState(m.current || defaultPersonalityState()),
|
||||
stats: m.stats && typeof m.stats === 'object' ? m.stats : {},
|
||||
history: Array.isArray(m.history) ? m.history.slice(-120) : [],
|
||||
updated_at: nowIso(),
|
||||
};
|
||||
writeJsonAtomic(personalityFilePath(), out);
|
||||
return out;
|
||||
}
|
||||
|
||||
function selectPersonalityForRun({ driftEnabled, signals, recentEvents } = {}) {
|
||||
const model = loadPersonalityModel();
|
||||
const base = normalizePersonalityState(model.current);
|
||||
const stats = model.stats || {};
|
||||
|
||||
const best = chooseBestKnownPersonality(stats);
|
||||
let naturalSelectionApplied = [];
|
||||
|
||||
// Natural selection: nudge towards the best-known configuration (small, max 2 params).
|
||||
if (best && best.key) {
|
||||
const bestState = parseKeyToState(best.key);
|
||||
const diffs = getParamDeltas(base, bestState).filter(d => Math.abs(d.delta) >= 0.05);
|
||||
const muts = [];
|
||||
for (const d of diffs.slice(0, 2)) {
|
||||
const clipped = Math.max(-0.1, Math.min(0.1, d.delta));
|
||||
muts.push({ type: 'PersonalityMutation', param: d.param, delta: clipped, reason: 'natural_selection' });
|
||||
}
|
||||
const applied = applyPersonalityMutations(base, muts);
|
||||
model.current = applied.state;
|
||||
naturalSelectionApplied = applied.applied;
|
||||
}
|
||||
|
||||
// Triggered personality mutation (explicit rule-based).
|
||||
const trig = shouldTriggerPersonalityMutation({ driftEnabled: !!driftEnabled, recentEvents });
|
||||
let triggeredApplied = [];
|
||||
if (trig.ok) {
|
||||
const props = proposeMutations({
|
||||
baseState: model.current,
|
||||
reason: trig.reason,
|
||||
driftEnabled: !!driftEnabled,
|
||||
signals,
|
||||
});
|
||||
const applied = applyPersonalityMutations(model.current, props);
|
||||
model.current = applied.state;
|
||||
triggeredApplied = applied.applied;
|
||||
}
|
||||
|
||||
// Persist updated current state.
|
||||
const saved = savePersonalityModel(model);
|
||||
const key = personalityKey(saved.current);
|
||||
const known = !!(saved.stats && saved.stats[key]);
|
||||
|
||||
return {
|
||||
personality_state: saved.current,
|
||||
personality_key: key,
|
||||
personality_known: known,
|
||||
personality_mutations: [...naturalSelectionApplied, ...triggeredApplied],
|
||||
model_meta: {
|
||||
best_known_key: best && best.key ? best.key : null,
|
||||
best_known_score: best && Number.isFinite(Number(best.score)) ? Number(best.score) : null,
|
||||
triggered: trig.ok ? { reason: trig.reason } : null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function updatePersonalityStats({ personalityState, outcome, score, notes } = {}) {
|
||||
const model = loadPersonalityModel();
|
||||
const st = normalizePersonalityState(personalityState || model.current);
|
||||
const key = personalityKey(st);
|
||||
if (!model.stats || typeof model.stats !== 'object') model.stats = {};
|
||||
const cur = model.stats[key] && typeof model.stats[key] === 'object' ? model.stats[key] : { success: 0, fail: 0, avg_score: 0.5, n: 0 };
|
||||
|
||||
const out = String(outcome || '').toLowerCase();
|
||||
if (out === 'success') cur.success = (Number(cur.success) || 0) + 1;
|
||||
else if (out === 'failed') cur.fail = (Number(cur.fail) || 0) + 1;
|
||||
|
||||
const sc = Number.isFinite(Number(score)) ? clamp01(Number(score)) : null;
|
||||
if (sc != null) {
|
||||
const n = (Number(cur.n) || 0) + 1;
|
||||
const prev = Number.isFinite(Number(cur.avg_score)) ? Number(cur.avg_score) : 0.5;
|
||||
cur.avg_score = prev + (sc - prev) / n;
|
||||
cur.n = n;
|
||||
}
|
||||
cur.updated_at = nowIso();
|
||||
model.stats[key] = cur;
|
||||
|
||||
model.history = Array.isArray(model.history) ? model.history : [];
|
||||
model.history.push({
|
||||
at: nowIso(),
|
||||
key,
|
||||
outcome: out === 'success' || out === 'failed' ? out : 'unknown',
|
||||
score: sc,
|
||||
notes: notes ? String(notes).slice(0, 220) : null,
|
||||
});
|
||||
|
||||
savePersonalityModel(model);
|
||||
return { key, stats: cur };
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
clamp01,
|
||||
defaultPersonalityState,
|
||||
normalizePersonalityState,
|
||||
isValidPersonalityState,
|
||||
personalityKey,
|
||||
loadPersonalityModel,
|
||||
savePersonalityModel,
|
||||
selectPersonalityForRun,
|
||||
updatePersonalityStats,
|
||||
};
|
||||
|
||||
332
archive/inactive-skills/evolver/src/gep/prompt.js
Normal file
332
archive/inactive-skills/evolver/src/gep/prompt.js
Normal file
@@ -0,0 +1,332 @@
|
||||
function buildGepPrompt({
|
||||
nowIso,
|
||||
context,
|
||||
signals,
|
||||
selector,
|
||||
parentEventId,
|
||||
selectedGene,
|
||||
capsuleCandidates,
|
||||
genesPreview,
|
||||
capsulesPreview,
|
||||
capabilityCandidatesPreview,
|
||||
externalCandidatesPreview,
|
||||
}) {
|
||||
const parentValue = parentEventId ? `"${parentEventId}"` : 'null';
|
||||
const selectedGeneId = selectedGene && selectedGene.id ? selectedGene.id : null;
|
||||
const capsuleIds = (capsuleCandidates || []).map(c => c && c.id).filter(Boolean);
|
||||
|
||||
const basePrompt = `
|
||||
GEP — GENOME EVOLUTION PROTOCOL (STANDARD EXECUTION) [${nowIso}]
|
||||
|
||||
You are not a chat assistant.
|
||||
You are not a free agent.
|
||||
You are a protocol-bound evolution execution engine.
|
||||
|
||||
All actions must comply with this protocol.
|
||||
Any deviation is a failure even if the outcome appears correct.
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━
|
||||
I. Protocol Positioning (Non-Negotiable)
|
||||
━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Protocol goals:
|
||||
- Convert reasoning into reusable, auditable, shareable evolution assets
|
||||
- Make evolution a standard process, not improvisation
|
||||
- Reduce future reasoning cost for similar problems
|
||||
|
||||
Protocol compliance overrides local optimality.
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━
|
||||
II. Mandatory Evolution Object Model (All Required)
|
||||
━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Every evolution run must explicitly output the following five objects.
|
||||
Missing any one is an immediate failure.
|
||||
|
||||
──────────────────────
|
||||
0 Mutation
|
||||
──────────────────────
|
||||
|
||||
You must emit a Mutation object for every evolution run:
|
||||
|
||||
\`\`\`json
|
||||
{
|
||||
"type": "Mutation",
|
||||
"id": "mut_<timestamp>",
|
||||
"category": "repair | optimize | innovate",
|
||||
"trigger_signals": ["<signal>"],
|
||||
"target": "<module | behavior | gene>",
|
||||
"expected_effect": "<effect>",
|
||||
"risk_level": "low | medium | high"
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
Hard safety constraints:
|
||||
- Do NOT run high-risk mutation unless rigor >= 0.6 AND risk_tolerance <= 0.5
|
||||
- Do NOT combine innovation mutation with a high-risk personality state
|
||||
|
||||
──────────────────────
|
||||
1 PersonalityState
|
||||
──────────────────────
|
||||
|
||||
You must emit a PersonalityState object for every evolution run:
|
||||
|
||||
\`\`\`json
|
||||
{
|
||||
"type": "PersonalityState",
|
||||
"rigor": 0.0-1.0,
|
||||
"creativity": 0.0-1.0,
|
||||
"verbosity": 0.0-1.0,
|
||||
"risk_tolerance": 0.0-1.0,
|
||||
"obedience": 0.0-1.0
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
Personality mutation (optional, small deltas only):
|
||||
\`\`\`json
|
||||
{
|
||||
"type": "PersonalityMutation",
|
||||
"param": "creativity",
|
||||
"delta": 0.1,
|
||||
"reason": "<reason>"
|
||||
}
|
||||
\`\`\`
|
||||
Constraints:
|
||||
- Each delta must be within [-0.2, +0.2]
|
||||
- Do not adjust more than 2 parameters in one run
|
||||
|
||||
──────────────────────
|
||||
2 EvolutionEvent
|
||||
──────────────────────
|
||||
|
||||
You must emit an EvolutionEvent with all fields present:
|
||||
|
||||
\`\`\`json
|
||||
{
|
||||
"type": "EvolutionEvent",
|
||||
"id": "evt_<timestamp>",
|
||||
"parent": ${parentValue},
|
||||
"intent": "repair | optimize | innovate",
|
||||
"signals": ["<signal_1>", "<signal_2>"],
|
||||
"genes_used": ["<gene_id>"],
|
||||
"mutation_id": "<mut_id>",
|
||||
"personality_state": { "type": "PersonalityState", "...": "..." },
|
||||
"blast_radius": {
|
||||
"files": <number>,
|
||||
"lines": <number>
|
||||
},
|
||||
"outcome": {
|
||||
"status": "success | failed",
|
||||
"score": <0.0-1.0>
|
||||
}
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
EvolutionEvent is the only legal node type in the evolution tree.
|
||||
|
||||
──────────────────────
|
||||
3 Gene
|
||||
──────────────────────
|
||||
|
||||
If a Gene is used, you must reuse an existing Gene first.
|
||||
Only create a new Gene when no match exists.
|
||||
|
||||
Gene must follow this schema:
|
||||
|
||||
\`\`\`json
|
||||
{
|
||||
"type": "Gene",
|
||||
"id": "gene_<name>",
|
||||
"category": "repair | optimize | innovate",
|
||||
"signals_match": ["<pattern>"],
|
||||
"preconditions": ["<condition>"],
|
||||
"strategy": [
|
||||
"<step_1>",
|
||||
"<step_2>"
|
||||
],
|
||||
"constraints": {
|
||||
"max_files": <number>,
|
||||
"forbidden_paths": ["<path>"]
|
||||
},
|
||||
"validation": ["<check_1>", "<check_2>"]
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
A Gene is an evolution interface definition, not code or generic advice.
|
||||
|
||||
──────────────────────
|
||||
4 Capsule
|
||||
──────────────────────
|
||||
|
||||
Only when evolution succeeds, you must generate a Capsule:
|
||||
|
||||
\`\`\`json
|
||||
{
|
||||
"type": "Capsule",
|
||||
"id": "capsule_<timestamp>",
|
||||
"trigger": ["<signal>"],
|
||||
"gene": "<gene_id>",
|
||||
"summary": "<one sentence>",
|
||||
"confidence": <0.0-1.0>
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
Capsules exist to prevent repeated reasoning for similar problems.
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━
|
||||
III. Standard Evolution Execution (Strict Order)
|
||||
━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Follow this order exactly. Do not skip, merge, or reorder steps:
|
||||
|
||||
1 Signal Extraction
|
||||
- Extract structured signals from logs, errors, metrics, or instructions
|
||||
- Do not proceed to repair or optimize before signals are extracted
|
||||
|
||||
2 Selection
|
||||
- Prefer existing Genes first
|
||||
- Then consider existing Capsules
|
||||
- No improvisation or trial-and-error strategies
|
||||
|
||||
You must provide a clear, auditable selection rationale.
|
||||
|
||||
3 Patch Execution
|
||||
- All changes must be small and reversible
|
||||
- blast_radius must be estimated and recorded before edits
|
||||
|
||||
4 Validation
|
||||
- Execute Gene-declared validation steps
|
||||
- On failure, rollback
|
||||
- Failure must still record an EvolutionEvent
|
||||
|
||||
5 Knowledge Solidification (Mandatory)
|
||||
- Update or add Gene if a new pattern is found
|
||||
- Generate Capsule on success
|
||||
- Append EvolutionEvent
|
||||
|
||||
If knowledge solidification is missing, the evolution is a failure even if functionality works.
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━
|
||||
IV. Selector (Mandatory Decision Logic)
|
||||
━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
When choosing a Gene or Capsule, you must emit a Selector decision.
|
||||
|
||||
Selector must be explainable, for example:
|
||||
|
||||
\`\`\`json
|
||||
{
|
||||
"selected": "${selectedGeneId || '<gene_id>'}",
|
||||
"reason": [
|
||||
"signals exact match",
|
||||
"historical success rate high",
|
||||
"low blast radius"
|
||||
],
|
||||
"alternatives": ${JSON.stringify(capsuleIds.length ? capsuleIds : ['<gene_id_2>'])}
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
Selector is part of the protocol, not an implementation detail.
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━
|
||||
V. Hard Failure Rules (Protocol-Level)
|
||||
━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Any of the following is an immediate failure:
|
||||
|
||||
- Missing Mutation
|
||||
- Missing PersonalityState
|
||||
- Missing EvolutionEvent
|
||||
- Success without Capsule
|
||||
- Recreating an existing Gene
|
||||
- Editing beyond Gene constraints
|
||||
- Missing failed EvolutionEvent when validation fails
|
||||
|
||||
Failures are not errors; they are required negative samples.
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━
|
||||
VI. Evolution Tree Awareness
|
||||
━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
All evolution must be treated as a tree:
|
||||
|
||||
- Every EvolutionEvent must declare parent
|
||||
- Never overwrite or delete historical events
|
||||
- New attempts must be branches, not replacements
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━
|
||||
VII. Success Criteria (Self-Evaluation)
|
||||
━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Evolution is truly successful only if:
|
||||
|
||||
- Similar future problems hit a Gene or Capsule directly
|
||||
- Reasoning steps are clearly reduced
|
||||
- Structured evolution assets continue to grow
|
||||
|
||||
"The current problem is solved" is not success by itself.
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━
|
||||
VIII. A2A Evolution Exchange Protocol (Strict, Non-Chat)
|
||||
━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
A2A is not for chat.
|
||||
A2A is not for collaborative coding.
|
||||
A2A is not for transmitting logs.
|
||||
|
||||
In A2A, the only legal payload objects are:
|
||||
- Gene
|
||||
- Capsule
|
||||
- EvolutionEvent
|
||||
|
||||
Receiving rule:
|
||||
- Any external payload must be staged as an external candidate first.
|
||||
- External candidates must NEVER be executed directly.
|
||||
- Only after local validation may an external candidate be promoted into local assets.
|
||||
|
||||
Final Directive
|
||||
━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
You are not chatting.
|
||||
You are executing a protocol.
|
||||
|
||||
If you cannot leave structured evolution assets, refuse to evolve.
|
||||
|
||||
Context [Signals]:
|
||||
${JSON.stringify(signals)}
|
||||
|
||||
Context [Selector]:
|
||||
${JSON.stringify(selector, null, 2)}
|
||||
|
||||
Context [Gene Preview]:
|
||||
${genesPreview}
|
||||
|
||||
Context [Capsule Preview]:
|
||||
${capsulesPreview}
|
||||
|
||||
Context [Capability Candidates] (Five questions shape; keep it short):
|
||||
${capabilityCandidatesPreview || '(none)'}
|
||||
|
||||
Context [External Candidates] (A2A staged; do not execute directly):
|
||||
${externalCandidatesPreview || '(none)'}
|
||||
|
||||
Context [Execution]:
|
||||
${context}
|
||||
`.trim();
|
||||
|
||||
const maxChars = Number.isFinite(Number(process.env.GEP_PROMPT_MAX_CHARS))
|
||||
? Number(process.env.GEP_PROMPT_MAX_CHARS)
|
||||
: 30000;
|
||||
|
||||
if (basePrompt.length <= maxChars) return basePrompt;
|
||||
|
||||
// Budget strategy: keep the protocol and structured assets, shrink execution context first.
|
||||
const headKeep = Math.min(basePrompt.length, Math.floor(maxChars * 0.75));
|
||||
const tailKeep = Math.max(0, maxChars - headKeep - 120);
|
||||
const head = basePrompt.slice(0, headKeep);
|
||||
const tail = tailKeep > 0 ? basePrompt.slice(basePrompt.length - tailKeep) : '';
|
||||
return `${head}\n\n...[PROMPT TRUNCATED FOR BUDGET]...\n\n${tail}`.slice(0, maxChars);
|
||||
}
|
||||
|
||||
module.exports = { buildGepPrompt };
|
||||
|
||||
134
archive/inactive-skills/evolver/src/gep/selector.js
Normal file
134
archive/inactive-skills/evolver/src/gep/selector.js
Normal file
@@ -0,0 +1,134 @@
|
||||
function matchPatternToSignals(pattern, signals) {
|
||||
if (!pattern || !signals || signals.length === 0) return false;
|
||||
const p = String(pattern);
|
||||
const sig = signals.map(s => String(s));
|
||||
|
||||
const regexLike = p.length >= 2 && p.startsWith('/') && p.lastIndexOf('/') > 0;
|
||||
if (regexLike) {
|
||||
const lastSlash = p.lastIndexOf('/');
|
||||
const body = p.slice(1, lastSlash);
|
||||
const flags = p.slice(lastSlash + 1);
|
||||
try {
|
||||
const re = new RegExp(body, flags || 'i');
|
||||
return sig.some(s => re.test(s));
|
||||
} catch (e) {
|
||||
// fallback to substring
|
||||
}
|
||||
}
|
||||
|
||||
const needle = p.toLowerCase();
|
||||
return sig.some(s => s.toLowerCase().includes(needle));
|
||||
}
|
||||
|
||||
function scoreGene(gene, signals) {
|
||||
if (!gene || gene.type !== 'Gene') return 0;
|
||||
const patterns = Array.isArray(gene.signals_match) ? gene.signals_match : [];
|
||||
if (patterns.length === 0) return 0;
|
||||
let score = 0;
|
||||
for (const pat of patterns) {
|
||||
if (matchPatternToSignals(pat, signals)) score += 1;
|
||||
}
|
||||
return score;
|
||||
}
|
||||
|
||||
function selectGene(genes, signals, opts) {
|
||||
const bannedGeneIds = opts && opts.bannedGeneIds ? opts.bannedGeneIds : new Set();
|
||||
const driftEnabled = !!(opts && opts.driftEnabled);
|
||||
const preferredGeneId = opts && typeof opts.preferredGeneId === 'string' ? opts.preferredGeneId : null;
|
||||
|
||||
const scored = genes
|
||||
.map(g => ({ gene: g, score: scoreGene(g, signals) }))
|
||||
.filter(x => x.score > 0)
|
||||
.sort((a, b) => b.score - a.score);
|
||||
|
||||
if (scored.length === 0) return { selected: null, alternatives: [] };
|
||||
|
||||
// Memory graph preference: only override when the preferred gene is already a match candidate.
|
||||
if (preferredGeneId) {
|
||||
const preferred = scored.find(x => x.gene && x.gene.id === preferredGeneId);
|
||||
if (preferred && (driftEnabled || !bannedGeneIds.has(preferredGeneId))) {
|
||||
const rest = scored.filter(x => x.gene && x.gene.id !== preferredGeneId);
|
||||
const filteredRest = driftEnabled ? rest : rest.filter(x => x.gene && !bannedGeneIds.has(x.gene.id));
|
||||
return {
|
||||
selected: preferred.gene,
|
||||
alternatives: filteredRest.slice(0, 4).map(x => x.gene),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Low-efficiency suppression: do not repeat low-confidence paths unless drift is explicit.
|
||||
const filtered = driftEnabled ? scored : scored.filter(x => x.gene && !bannedGeneIds.has(x.gene.id));
|
||||
if (filtered.length === 0) return { selected: null, alternatives: scored.slice(0, 4).map(x => x.gene) };
|
||||
|
||||
return {
|
||||
selected: filtered[0].gene,
|
||||
alternatives: filtered.slice(1, 4).map(x => x.gene),
|
||||
};
|
||||
}
|
||||
|
||||
function selectCapsule(capsules, signals) {
|
||||
const scored = (capsules || [])
|
||||
.map(c => {
|
||||
const triggers = Array.isArray(c.trigger) ? c.trigger : [];
|
||||
const score = triggers.reduce((acc, t) => (matchPatternToSignals(t, signals) ? acc + 1 : acc), 0);
|
||||
return { capsule: c, score };
|
||||
})
|
||||
.filter(x => x.score > 0)
|
||||
.sort((a, b) => b.score - a.score);
|
||||
return scored.length ? scored[0].capsule : null;
|
||||
}
|
||||
|
||||
function selectGeneAndCapsule({ genes, capsules, signals, memoryAdvice, driftEnabled }) {
|
||||
const bannedGeneIds =
|
||||
memoryAdvice && memoryAdvice.bannedGeneIds instanceof Set ? memoryAdvice.bannedGeneIds : new Set();
|
||||
const preferredGeneId = memoryAdvice && memoryAdvice.preferredGeneId ? memoryAdvice.preferredGeneId : null;
|
||||
|
||||
const { selected, alternatives } = selectGene(genes, signals, {
|
||||
bannedGeneIds,
|
||||
preferredGeneId,
|
||||
driftEnabled: !!driftEnabled,
|
||||
});
|
||||
const capsule = selectCapsule(capsules, signals);
|
||||
const selector = buildSelectorDecision({
|
||||
gene: selected,
|
||||
capsule,
|
||||
signals,
|
||||
alternatives,
|
||||
memoryAdvice,
|
||||
driftEnabled,
|
||||
});
|
||||
return {
|
||||
selectedGene: selected,
|
||||
capsuleCandidates: capsule ? [capsule] : [],
|
||||
selector,
|
||||
};
|
||||
}
|
||||
|
||||
function buildSelectorDecision({ gene, capsule, signals, alternatives, memoryAdvice, driftEnabled }) {
|
||||
const reason = [];
|
||||
if (gene) reason.push('signals match gene.signals_match');
|
||||
if (capsule) reason.push('capsule trigger matches signals');
|
||||
if (!gene) reason.push('no matching gene found; new gene may be required');
|
||||
if (signals && signals.length) reason.push(`signals: ${signals.join(', ')}`);
|
||||
|
||||
if (memoryAdvice && Array.isArray(memoryAdvice.explanation) && memoryAdvice.explanation.length) {
|
||||
reason.push(`memory_graph: ${memoryAdvice.explanation.join(' | ')}`);
|
||||
}
|
||||
if (driftEnabled) {
|
||||
reason.push('random_drift_override: true');
|
||||
}
|
||||
|
||||
return {
|
||||
selected: gene ? gene.id : null,
|
||||
reason,
|
||||
alternatives: Array.isArray(alternatives) ? alternatives.map(g => g.id) : [],
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
selectGeneAndCapsule,
|
||||
selectGene,
|
||||
selectCapsule,
|
||||
buildSelectorDecision,
|
||||
};
|
||||
|
||||
95
archive/inactive-skills/evolver/src/gep/signals.js
Normal file
95
archive/inactive-skills/evolver/src/gep/signals.js
Normal file
@@ -0,0 +1,95 @@
|
||||
// Opportunity signal names (shared with mutation.js and personality.js).
|
||||
var OPPORTUNITY_SIGNALS = [
|
||||
'user_feature_request',
|
||||
'user_improvement_suggestion',
|
||||
'perf_bottleneck',
|
||||
'capability_gap',
|
||||
'stable_success_plateau',
|
||||
'external_opportunity',
|
||||
];
|
||||
|
||||
function hasOpportunitySignal(signals) {
|
||||
var list = Array.isArray(signals) ? signals : [];
|
||||
for (var i = 0; i < OPPORTUNITY_SIGNALS.length; i++) {
|
||||
if (list.includes(OPPORTUNITY_SIGNALS[i])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function extractSignals({ recentSessionTranscript, todayLog, memorySnippet, userSnippet }) {
|
||||
var signals = [];
|
||||
var corpus = [
|
||||
String(recentSessionTranscript || ''),
|
||||
String(todayLog || ''),
|
||||
String(memorySnippet || ''),
|
||||
String(userSnippet || ''),
|
||||
].join('\n');
|
||||
var lower = corpus.toLowerCase();
|
||||
|
||||
// --- Defensive signals (errors, missing resources) ---
|
||||
|
||||
var errorHit = /\[error|error:|exception|fail|failed|iserror":true/.test(lower);
|
||||
if (errorHit) signals.push('log_error');
|
||||
|
||||
// Error signature (more reproducible than a coarse "log_error" tag).
|
||||
try {
|
||||
var lines = corpus
|
||||
.split('\n')
|
||||
.map(function (l) { return String(l || '').trim(); })
|
||||
.filter(Boolean);
|
||||
|
||||
var errLine =
|
||||
lines.find(function (l) { return /\b(typeerror|referenceerror|syntaxerror)\b\s*:|error\s*:|exception\s*:|\[error/i.test(l); }) ||
|
||||
null;
|
||||
|
||||
if (errLine) {
|
||||
var clipped = errLine.replace(/\s+/g, ' ').slice(0, 260);
|
||||
signals.push('errsig:' + clipped);
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
if (lower.includes('memory.md missing')) signals.push('memory_missing');
|
||||
if (lower.includes('user.md missing')) signals.push('user_missing');
|
||||
if (lower.includes('key missing')) signals.push('integration_key_missing');
|
||||
if (lower.includes('no session logs found') || lower.includes('no jsonl files')) signals.push('session_logs_missing');
|
||||
if (lower.includes('pgrep') || lower.includes('ps aux')) signals.push('windows_shell_incompatible');
|
||||
if (lower.includes('path.resolve(__dirname, \'../../')) signals.push('path_outside_workspace');
|
||||
|
||||
// Protocol-specific drift signals
|
||||
if (lower.includes('prompt') && !lower.includes('evolutionevent')) signals.push('protocol_drift');
|
||||
|
||||
// --- Opportunity signals (innovation / feature requests) ---
|
||||
|
||||
// user_feature_request: user explicitly asks for a new capability
|
||||
// Look for action verbs + object patterns that indicate a feature request
|
||||
if (/\b(add|implement|create|build|make|develop|write|design)\b[^.?!\n]{3,60}\b(feature|function|module|capability|tool|support|endpoint|command|option|mode)\b/i.test(corpus)) {
|
||||
signals.push('user_feature_request');
|
||||
}
|
||||
// Also catch direct "I want/need X" patterns
|
||||
if (/\b(i want|i need|we need|please add|can you add|could you add|let'?s add)\b/i.test(lower)) {
|
||||
signals.push('user_feature_request');
|
||||
}
|
||||
|
||||
// user_improvement_suggestion: user suggests making something better
|
||||
if (/\b(should be|could be better|improve|enhance|upgrade|refactor|clean up|simplify|streamline)\b/i.test(lower)) {
|
||||
// Only fire if there is no active error (to distinguish from repair requests)
|
||||
if (!errorHit) signals.push('user_improvement_suggestion');
|
||||
}
|
||||
|
||||
// perf_bottleneck: performance issues detected
|
||||
if (/\b(slow|timeout|timed?\s*out|latency|bottleneck|took too long|performance issue|high cpu|high memory|oom|out of memory)\b/i.test(lower)) {
|
||||
signals.push('perf_bottleneck');
|
||||
}
|
||||
|
||||
// capability_gap: something is explicitly unsupported or missing
|
||||
if (/\b(not supported|cannot|doesn'?t support|no way to|missing feature|unsupported|not available|not implemented|no support for)\b/i.test(lower)) {
|
||||
// Only fire if it is not just a missing file/config signal
|
||||
if (!signals.includes('memory_missing') && !signals.includes('user_missing') && !signals.includes('session_logs_missing')) {
|
||||
signals.push('capability_gap');
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(new Set(signals));
|
||||
}
|
||||
|
||||
module.exports = { extractSignals, hasOpportunitySignal, OPPORTUNITY_SIGNALS };
|
||||
512
archive/inactive-skills/evolver/src/gep/solidify.js
Normal file
512
archive/inactive-skills/evolver/src/gep/solidify.js
Normal file
@@ -0,0 +1,512 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
const { loadGenes, upsertGene, appendEventJsonl, appendCapsule, upsertCapsule, getLastEventId } = require('./assetStore');
|
||||
const { computeSignalKey, memoryGraphPath } = require('./memoryGraph');
|
||||
const { computeCapsuleSuccessStreak, isBlastRadiusSafe } = require('./a2a');
|
||||
const { getRepoRoot, getMemoryDir } = require('./paths');
|
||||
const { extractSignals } = require('./signals');
|
||||
const { selectGene } = require('./selector');
|
||||
const { isValidMutation, normalizeMutation, isHighRiskMutationAllowed, isHighRiskPersonality } = require('./mutation');
|
||||
const {
|
||||
isValidPersonalityState,
|
||||
normalizePersonalityState,
|
||||
personalityKey,
|
||||
updatePersonalityStats,
|
||||
} = require('./personality');
|
||||
const { computeAssetId, SCHEMA_VERSION } = require('./contentHash');
|
||||
const { captureEnvFingerprint } = require('./envFingerprint');
|
||||
const { buildValidationReport } = require('./validationReport');
|
||||
|
||||
function nowIso() {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
||||
function clamp01(x) {
|
||||
const n = Number(x);
|
||||
if (!Number.isFinite(n)) return 0;
|
||||
return Math.max(0, Math.min(1, n));
|
||||
}
|
||||
|
||||
function safeJsonParse(text, fallback) {
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
function readJsonIfExists(filePath, fallback) {
|
||||
try {
|
||||
if (!fs.existsSync(filePath)) return fallback;
|
||||
const raw = fs.readFileSync(filePath, 'utf8');
|
||||
if (!raw.trim()) return fallback;
|
||||
return JSON.parse(raw);
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
function stableHash(input) {
|
||||
const s = String(input || '');
|
||||
let h = 2166136261;
|
||||
for (let i = 0; i < s.length; i++) {
|
||||
h ^= s.charCodeAt(i);
|
||||
h = Math.imul(h, 16777619);
|
||||
}
|
||||
return (h >>> 0).toString(16).padStart(8, '0');
|
||||
}
|
||||
|
||||
function runCmd(cmd, opts = {}) {
|
||||
const cwd = opts.cwd || getRepoRoot();
|
||||
const timeoutMs = Number.isFinite(Number(opts.timeoutMs)) ? Number(opts.timeoutMs) : 120000;
|
||||
return execSync(cmd, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], timeout: timeoutMs });
|
||||
}
|
||||
|
||||
function tryRunCmd(cmd, opts = {}) {
|
||||
try {
|
||||
return { ok: true, out: runCmd(cmd, opts), err: '' };
|
||||
} catch (e) {
|
||||
const stderr = e && e.stderr ? String(e.stderr) : '';
|
||||
const stdout = e && e.stdout ? String(e.stdout) : '';
|
||||
const msg = e && e.message ? String(e.message) : 'command_failed';
|
||||
return { ok: false, out: stdout, err: stderr || msg };
|
||||
}
|
||||
}
|
||||
|
||||
function gitListChangedFiles({ repoRoot }) {
|
||||
const files = new Set();
|
||||
const s1 = tryRunCmd('git diff --name-only', { cwd: repoRoot, timeoutMs: 60000 });
|
||||
if (s1.ok) for (const line of String(s1.out).split('\n').map(l => l.trim()).filter(Boolean)) files.add(line);
|
||||
const s2 = tryRunCmd('git diff --cached --name-only', { cwd: repoRoot, timeoutMs: 60000 });
|
||||
if (s2.ok) for (const line of String(s2.out).split('\n').map(l => l.trim()).filter(Boolean)) files.add(line);
|
||||
const s3 = tryRunCmd('git ls-files --others --exclude-standard', { cwd: repoRoot, timeoutMs: 60000 });
|
||||
if (s3.ok) for (const line of String(s3.out).split('\n').map(l => l.trim()).filter(Boolean)) files.add(line);
|
||||
return Array.from(files);
|
||||
}
|
||||
|
||||
function parseNumstat(text) {
|
||||
const lines = String(text || '').split('\n').map(l => l.trim()).filter(Boolean);
|
||||
let added = 0;
|
||||
let deleted = 0;
|
||||
for (const line of lines) {
|
||||
const parts = line.split('\t');
|
||||
if (parts.length < 3) continue;
|
||||
const a = Number(parts[0]);
|
||||
const d = Number(parts[1]);
|
||||
if (Number.isFinite(a)) added += a;
|
||||
if (Number.isFinite(d)) deleted += d;
|
||||
}
|
||||
return { added, deleted };
|
||||
}
|
||||
|
||||
function countFileLines(absPath) {
|
||||
try {
|
||||
if (!fs.existsSync(absPath)) return 0;
|
||||
const buf = fs.readFileSync(absPath);
|
||||
if (!buf || buf.length === 0) return 0;
|
||||
let n = 1;
|
||||
for (let i = 0; i < buf.length; i++) if (buf[i] === 10) n++;
|
||||
return n;
|
||||
} catch {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function computeBlastRadius({ repoRoot, baselineUntracked }) {
|
||||
let changedFiles = gitListChangedFiles({ repoRoot });
|
||||
if (Array.isArray(baselineUntracked) && baselineUntracked.length > 0) {
|
||||
const baselineSet = new Set(baselineUntracked);
|
||||
changedFiles = changedFiles.filter(f => !baselineSet.has(f));
|
||||
}
|
||||
const filesCount = changedFiles.length;
|
||||
const u = tryRunCmd('git diff --numstat', { cwd: repoRoot, timeoutMs: 60000 });
|
||||
const c = tryRunCmd('git diff --cached --numstat', { cwd: repoRoot, timeoutMs: 60000 });
|
||||
const unstaged = u.ok ? parseNumstat(u.out) : { added: 0, deleted: 0 };
|
||||
const staged = c.ok ? parseNumstat(c.out) : { added: 0, deleted: 0 };
|
||||
const untracked = tryRunCmd('git ls-files --others --exclude-standard', { cwd: repoRoot, timeoutMs: 60000 });
|
||||
let untrackedLines = 0;
|
||||
if (untracked.ok) {
|
||||
const rels = String(untracked.out).split('\n').map(l => l.trim()).filter(Boolean);
|
||||
const baselineSet = new Set(Array.isArray(baselineUntracked) ? baselineUntracked : []);
|
||||
for (const rel of rels) {
|
||||
if (baselineSet.has(rel)) continue;
|
||||
const abs = path.join(repoRoot, rel);
|
||||
untrackedLines += countFileLines(abs);
|
||||
}
|
||||
}
|
||||
const churn = unstaged.added + unstaged.deleted + staged.added + staged.deleted + untrackedLines;
|
||||
return { files: filesCount, lines: churn, changed_files: changedFiles };
|
||||
}
|
||||
|
||||
function isForbiddenPath(relPath, forbiddenPaths) {
|
||||
const rel = String(relPath || '').replace(/\\/g, '/').replace(/^\.\/+/, '');
|
||||
const list = Array.isArray(forbiddenPaths) ? forbiddenPaths : [];
|
||||
for (const fp of list) {
|
||||
const f = String(fp || '').replace(/\\/g, '/').replace(/^\.\/+/, '').replace(/\/+$/, '');
|
||||
if (!f) continue;
|
||||
if (rel === f) return true;
|
||||
if (rel.startsWith(f + '/')) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function checkConstraints({ gene, blast }) {
|
||||
const violations = [];
|
||||
if (!gene || gene.type !== 'Gene') return { ok: true, violations };
|
||||
const constraints = gene.constraints || {};
|
||||
const maxFiles = Number(constraints.max_files);
|
||||
if (Number.isFinite(maxFiles) && maxFiles > 0) {
|
||||
if (Number(blast.files) > maxFiles) violations.push(`max_files exceeded: ${blast.files} > ${maxFiles}`);
|
||||
}
|
||||
const forbidden = Array.isArray(constraints.forbidden_paths) ? constraints.forbidden_paths : [];
|
||||
for (const f of blast.changed_files || []) {
|
||||
if (isForbiddenPath(f, forbidden)) violations.push(`forbidden_path touched: ${f}`);
|
||||
}
|
||||
return { ok: violations.length === 0, violations };
|
||||
}
|
||||
|
||||
function readStateForSolidify() {
|
||||
const memoryDir = getMemoryDir();
|
||||
const statePath = path.join(memoryDir, 'evolution_solidify_state.json');
|
||||
return readJsonIfExists(statePath, { last_run: null });
|
||||
}
|
||||
|
||||
function writeStateForSolidify(state) {
|
||||
const memoryDir = getMemoryDir();
|
||||
const statePath = path.join(memoryDir, 'evolution_solidify_state.json');
|
||||
try {
|
||||
if (!fs.existsSync(memoryDir)) fs.mkdirSync(memoryDir, { recursive: true });
|
||||
} catch {}
|
||||
const tmp = `${statePath}.tmp`;
|
||||
fs.writeFileSync(tmp, JSON.stringify(state, null, 2) + '\n', 'utf8');
|
||||
fs.renameSync(tmp, statePath);
|
||||
}
|
||||
|
||||
function buildEventId(tsIso) {
|
||||
const t = Date.parse(tsIso);
|
||||
return `evt_${Number.isFinite(t) ? t : Date.now()}`;
|
||||
}
|
||||
|
||||
function buildCapsuleId(tsIso) {
|
||||
const t = Date.parse(tsIso);
|
||||
return `capsule_${Number.isFinite(t) ? t : Date.now()}`;
|
||||
}
|
||||
|
||||
// --- Validation command safety ---
|
||||
const VALIDATION_ALLOWED_PREFIXES = ['node ', 'npm ', 'npx '];
|
||||
|
||||
function isValidationCommandAllowed(cmd) {
|
||||
const c = String(cmd || '').trim();
|
||||
if (!c) return false;
|
||||
if (!VALIDATION_ALLOWED_PREFIXES.some(p => c.startsWith(p))) return false;
|
||||
if (/`|\$\(/.test(c)) return false;
|
||||
const stripped = c.replace(/"[^"]*"/g, '').replace(/'[^']*'/g, '');
|
||||
if (/[;&|><]/.test(stripped)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function runValidations(gene, opts = {}) {
|
||||
const repoRoot = opts.repoRoot || getRepoRoot();
|
||||
const timeoutMs = Number.isFinite(Number(opts.timeoutMs)) ? Number(opts.timeoutMs) : 180000;
|
||||
const validation = Array.isArray(gene && gene.validation) ? gene.validation : [];
|
||||
const results = [];
|
||||
const startedAt = Date.now();
|
||||
for (const cmd of validation) {
|
||||
const c = String(cmd || '').trim();
|
||||
if (!c) continue;
|
||||
if (!isValidationCommandAllowed(c)) {
|
||||
results.push({ cmd: c, ok: false, out: '', err: 'BLOCKED: validation command rejected by safety check (allowed prefixes: node/npm/npx; shell operators prohibited)' });
|
||||
return { ok: false, results, startedAt, finishedAt: Date.now() };
|
||||
}
|
||||
const r = tryRunCmd(c, { cwd: repoRoot, timeoutMs });
|
||||
results.push({ cmd: c, ok: r.ok, out: String(r.out || ''), err: String(r.err || '') });
|
||||
if (!r.ok) return { ok: false, results, startedAt, finishedAt: Date.now() };
|
||||
}
|
||||
return { ok: true, results, startedAt, finishedAt: Date.now() };
|
||||
}
|
||||
|
||||
function rollbackTracked(repoRoot) {
|
||||
tryRunCmd('git restore --staged --worktree .', { cwd: repoRoot, timeoutMs: 60000 });
|
||||
tryRunCmd('git reset --hard', { cwd: repoRoot, timeoutMs: 60000 });
|
||||
}
|
||||
|
||||
function gitListUntrackedFiles(repoRoot) {
|
||||
const r = tryRunCmd('git ls-files --others --exclude-standard', { cwd: repoRoot, timeoutMs: 60000 });
|
||||
if (!r.ok) return [];
|
||||
return String(r.out).split('\n').map(l => l.trim()).filter(Boolean);
|
||||
}
|
||||
|
||||
function rollbackNewUntrackedFiles({ repoRoot, baselineUntracked }) {
|
||||
const baseline = new Set((Array.isArray(baselineUntracked) ? baselineUntracked : []).map(String));
|
||||
const current = gitListUntrackedFiles(repoRoot);
|
||||
const toDelete = current.filter(f => !baseline.has(String(f)));
|
||||
for (const rel of toDelete) {
|
||||
const safeRel = String(rel || '').replace(/\\/g, '/').replace(/^\.\/+/, '');
|
||||
if (!safeRel) continue;
|
||||
const abs = path.join(repoRoot, safeRel);
|
||||
const normRepo = path.resolve(repoRoot);
|
||||
const normAbs = path.resolve(abs);
|
||||
if (!normAbs.startsWith(normRepo + path.sep) && normAbs !== normRepo) continue;
|
||||
try {
|
||||
if (fs.existsSync(normAbs) && fs.statSync(normAbs).isFile()) fs.unlinkSync(normAbs);
|
||||
} catch (e) {}
|
||||
}
|
||||
return { deleted: toDelete };
|
||||
}
|
||||
|
||||
function inferCategoryFromSignals(signals) {
|
||||
const list = Array.isArray(signals) ? signals.map(String) : [];
|
||||
if (list.includes('log_error')) return 'repair';
|
||||
if (list.includes('protocol_drift')) return 'optimize';
|
||||
return 'optimize';
|
||||
}
|
||||
|
||||
function buildAutoGene({ signals, intent }) {
|
||||
const sigs = Array.isArray(signals) ? Array.from(new Set(signals.map(String))).filter(Boolean) : [];
|
||||
const signalKey = computeSignalKey(sigs);
|
||||
const id = `gene_auto_${stableHash(signalKey)}`;
|
||||
const category = intent && ['repair', 'optimize', 'innovate'].includes(String(intent))
|
||||
? String(intent)
|
||||
: inferCategoryFromSignals(sigs);
|
||||
const signalsMatch = sigs.length ? sigs.slice(0, 8) : ['(none)'];
|
||||
const gene = {
|
||||
type: 'Gene',
|
||||
schema_version: SCHEMA_VERSION,
|
||||
id,
|
||||
category,
|
||||
signals_match: signalsMatch,
|
||||
preconditions: [`signals_key == ${signalKey}`],
|
||||
strategy: [
|
||||
'Extract structured signals from logs and user instructions',
|
||||
'Select an existing Gene by signals match (no improvisation)',
|
||||
'Estimate blast radius (files, lines) before editing and record it',
|
||||
'Apply smallest reversible patch',
|
||||
'Validate using declared validation steps; rollback on failure',
|
||||
'Solidify knowledge: append EvolutionEvent, update Gene/Capsule store',
|
||||
],
|
||||
constraints: { max_files: 12, forbidden_paths: ['.git', 'node_modules'] },
|
||||
validation: ['node -e "require(\'./src/gep/solidify\'); console.log(\'ok\')"'],
|
||||
};
|
||||
gene.asset_id = computeAssetId(gene);
|
||||
return gene;
|
||||
}
|
||||
|
||||
function ensureGene({ genes, selectedGene, signals, intent, dryRun }) {
|
||||
if (selectedGene && selectedGene.type === 'Gene') return { gene: selectedGene, created: false, reason: 'selected_gene_id_present' };
|
||||
const res = selectGene(Array.isArray(genes) ? genes : [], Array.isArray(signals) ? signals : [], {
|
||||
bannedGeneIds: new Set(), preferredGeneId: null, driftEnabled: false,
|
||||
});
|
||||
if (res && res.selected) return { gene: res.selected, created: false, reason: 'reselected_from_existing' };
|
||||
const auto = buildAutoGene({ signals, intent });
|
||||
if (!dryRun) upsertGene(auto);
|
||||
return { gene: auto, created: true, reason: 'no_match_create_new' };
|
||||
}
|
||||
|
||||
function readRecentSessionInputs() {
|
||||
const repoRoot = getRepoRoot();
|
||||
const memoryDir = getMemoryDir();
|
||||
const rootMemory = path.join(repoRoot, 'MEMORY.md');
|
||||
const dirMemory = path.join(memoryDir, 'MEMORY.md');
|
||||
const memoryFile = fs.existsSync(rootMemory) ? rootMemory : dirMemory;
|
||||
const userFile = path.join(repoRoot, 'USER.md');
|
||||
const todayLog = path.join(memoryDir, new Date().toISOString().split('T')[0] + '.md');
|
||||
const todayLogContent = fs.existsSync(todayLog) ? fs.readFileSync(todayLog, 'utf8') : '';
|
||||
const memorySnippet = fs.existsSync(memoryFile) ? fs.readFileSync(memoryFile, 'utf8').slice(0, 50000) : '';
|
||||
const userSnippet = fs.existsSync(userFile) ? fs.readFileSync(userFile, 'utf8') : '';
|
||||
const recentSessionTranscript = '';
|
||||
return { recentSessionTranscript, todayLog: todayLogContent, memorySnippet, userSnippet };
|
||||
}
|
||||
|
||||
function solidify({ intent, summary, dryRun = false, rollbackOnFailure = true } = {}) {
|
||||
const repoRoot = getRepoRoot();
|
||||
const state = readStateForSolidify();
|
||||
const lastRun = state && state.last_run ? state.last_run : null;
|
||||
const genes = loadGenes();
|
||||
const geneId = lastRun && lastRun.selected_gene_id ? String(lastRun.selected_gene_id) : null;
|
||||
const selectedGene = geneId ? genes.find(g => g && g.type === 'Gene' && g.id === geneId) : null;
|
||||
const parentEventId =
|
||||
lastRun && typeof lastRun.parent_event_id === 'string' ? lastRun.parent_event_id : getLastEventId();
|
||||
const signals =
|
||||
lastRun && Array.isArray(lastRun.signals) && lastRun.signals.length
|
||||
? Array.from(new Set(lastRun.signals.map(String)))
|
||||
: extractSignals(readRecentSessionInputs());
|
||||
const signalKey = computeSignalKey(signals);
|
||||
|
||||
const mutationRaw = lastRun && lastRun.mutation && typeof lastRun.mutation === 'object' ? lastRun.mutation : null;
|
||||
const personalityRaw =
|
||||
lastRun && lastRun.personality_state && typeof lastRun.personality_state === 'object' ? lastRun.personality_state : null;
|
||||
const mutation = mutationRaw && isValidMutation(mutationRaw) ? normalizeMutation(mutationRaw) : null;
|
||||
const personalityState =
|
||||
personalityRaw && isValidPersonalityState(personalityRaw) ? normalizePersonalityState(personalityRaw) : null;
|
||||
const personalityKeyUsed = personalityState ? personalityKey(personalityState) : null;
|
||||
const protocolViolations = [];
|
||||
if (!mutation) protocolViolations.push('missing_or_invalid_mutation');
|
||||
if (!personalityState) protocolViolations.push('missing_or_invalid_personality_state');
|
||||
if (mutation && mutation.risk_level === 'high' && !isHighRiskMutationAllowed(personalityState || null)) {
|
||||
protocolViolations.push('high_risk_mutation_not_allowed_by_personality');
|
||||
}
|
||||
if (mutation && mutation.risk_level === 'high' && !(lastRun && lastRun.personality_known)) {
|
||||
protocolViolations.push('high_risk_mutation_forbidden_under_unknown_personality');
|
||||
}
|
||||
if (mutation && mutation.category === 'innovate' && personalityState && isHighRiskPersonality(personalityState)) {
|
||||
protocolViolations.push('forbidden_innovate_with_high_risk_personality');
|
||||
}
|
||||
|
||||
const ensured = ensureGene({ genes, selectedGene, signals, intent, dryRun: !!dryRun });
|
||||
const geneUsed = ensured.gene;
|
||||
const blast = computeBlastRadius({
|
||||
repoRoot,
|
||||
baselineUntracked: lastRun && Array.isArray(lastRun.baseline_untracked) ? lastRun.baseline_untracked : [],
|
||||
});
|
||||
const constraintCheck = checkConstraints({ gene: geneUsed, blast });
|
||||
|
||||
// Capture environment fingerprint before validation.
|
||||
const envFp = captureEnvFingerprint();
|
||||
|
||||
let validation = { ok: true, results: [], startedAt: null, finishedAt: null };
|
||||
if (geneUsed) {
|
||||
validation = runValidations(geneUsed, { repoRoot, timeoutMs: 180000 });
|
||||
}
|
||||
|
||||
// Build standardized ValidationReport (machine-readable, interoperable).
|
||||
const validationReport = buildValidationReport({
|
||||
geneId: geneUsed && geneUsed.id ? geneUsed.id : null,
|
||||
commands: validation.results.map(function (r) { return r.cmd; }),
|
||||
results: validation.results,
|
||||
envFp: envFp,
|
||||
startedAt: validation.startedAt,
|
||||
finishedAt: validation.finishedAt,
|
||||
});
|
||||
|
||||
const success = constraintCheck.ok && validation.ok && protocolViolations.length === 0;
|
||||
const ts = nowIso();
|
||||
const outcomeStatus = success ? 'success' : 'failed';
|
||||
const score = clamp01(success ? 0.85 : 0.2);
|
||||
|
||||
const selectedCapsuleId =
|
||||
lastRun && typeof lastRun.selected_capsule_id === 'string' && lastRun.selected_capsule_id.trim()
|
||||
? String(lastRun.selected_capsule_id).trim() : null;
|
||||
const capsuleId = success ? selectedCapsuleId || buildCapsuleId(ts) : null;
|
||||
const derivedIntent = intent || (mutation && mutation.category) || (geneUsed && geneUsed.category) || 'repair';
|
||||
const intentMismatch =
|
||||
intent && mutation && typeof mutation.category === 'string' && String(intent) !== String(mutation.category);
|
||||
if (intentMismatch) protocolViolations.push(`intent_mismatch_with_mutation:${String(intent)}!=${String(mutation.category)}`);
|
||||
|
||||
const event = {
|
||||
type: 'EvolutionEvent',
|
||||
schema_version: SCHEMA_VERSION,
|
||||
id: buildEventId(ts),
|
||||
parent: parentEventId || null,
|
||||
intent: derivedIntent,
|
||||
signals,
|
||||
genes_used: geneUsed && geneUsed.id ? [geneUsed.id] : [],
|
||||
mutation_id: mutation && mutation.id ? mutation.id : null,
|
||||
personality_state: personalityState || null,
|
||||
blast_radius: { files: blast.files, lines: blast.lines },
|
||||
outcome: { status: outcomeStatus, score },
|
||||
capsule_id: capsuleId,
|
||||
env_fingerprint: envFp,
|
||||
validation_report_id: validationReport.id,
|
||||
meta: {
|
||||
at: ts,
|
||||
signal_key: signalKey,
|
||||
selector: lastRun && lastRun.selector ? lastRun.selector : null,
|
||||
blast_radius_estimate: lastRun && lastRun.blast_radius_estimate ? lastRun.blast_radius_estimate : null,
|
||||
mutation: mutation || null,
|
||||
personality: {
|
||||
key: personalityKeyUsed,
|
||||
known: !!(lastRun && lastRun.personality_known),
|
||||
mutations: lastRun && Array.isArray(lastRun.personality_mutations) ? lastRun.personality_mutations : [],
|
||||
},
|
||||
gene: {
|
||||
id: geneUsed && geneUsed.id ? geneUsed.id : null,
|
||||
created: !!ensured.created,
|
||||
reason: ensured.reason,
|
||||
},
|
||||
constraints_ok: constraintCheck.ok,
|
||||
constraint_violations: constraintCheck.violations,
|
||||
validation_ok: validation.ok,
|
||||
validation: validation.results.map(r => ({ cmd: r.cmd, ok: r.ok })),
|
||||
validation_report: validationReport,
|
||||
protocol_ok: protocolViolations.length === 0,
|
||||
protocol_violations: protocolViolations,
|
||||
memory_graph: memoryGraphPath(),
|
||||
},
|
||||
};
|
||||
event.asset_id = computeAssetId(event);
|
||||
|
||||
let capsule = null;
|
||||
if (success) {
|
||||
const s = String(summary || '').trim();
|
||||
const autoSummary = geneUsed
|
||||
? `固化:${geneUsed.id} 命中信号 ${signals.join(', ') || '(none)'},变更 ${blast.files} 文件 / ${blast.lines} 行。`
|
||||
: `固化:命中信号 ${signals.join(', ') || '(none)'},变更 ${blast.files} 文件 / ${blast.lines} 行。`;
|
||||
let prevCapsule = null;
|
||||
try {
|
||||
if (selectedCapsuleId) {
|
||||
const list = require('./assetStore').loadCapsules();
|
||||
prevCapsule = Array.isArray(list) ? list.find(c => c && c.type === 'Capsule' && String(c.id) === selectedCapsuleId) : null;
|
||||
}
|
||||
} catch (e) {}
|
||||
capsule = {
|
||||
type: 'Capsule',
|
||||
schema_version: SCHEMA_VERSION,
|
||||
id: capsuleId,
|
||||
trigger: prevCapsule && Array.isArray(prevCapsule.trigger) && prevCapsule.trigger.length ? prevCapsule.trigger : signals,
|
||||
gene: geneUsed && geneUsed.id ? geneUsed.id : prevCapsule && prevCapsule.gene ? prevCapsule.gene : null,
|
||||
summary: s || (prevCapsule && prevCapsule.summary ? String(prevCapsule.summary) : autoSummary),
|
||||
confidence: clamp01(score),
|
||||
blast_radius: { files: blast.files, lines: blast.lines },
|
||||
outcome: { status: 'success', score },
|
||||
success_streak: 1,
|
||||
env_fingerprint: envFp,
|
||||
a2a: { eligible_to_broadcast: false },
|
||||
};
|
||||
capsule.asset_id = computeAssetId(capsule);
|
||||
}
|
||||
|
||||
// Bug fix: dry-run must NOT trigger rollback (it should only observe, not mutate).
|
||||
if (!dryRun && !success && rollbackOnFailure) {
|
||||
rollbackTracked(repoRoot);
|
||||
rollbackNewUntrackedFiles({ repoRoot, baselineUntracked: lastRun && lastRun.baseline_untracked ? lastRun.baseline_untracked : [] });
|
||||
}
|
||||
|
||||
if (!dryRun) {
|
||||
appendEventJsonl(validationReport);
|
||||
if (capsule) upsertCapsule(capsule);
|
||||
appendEventJsonl(event);
|
||||
if (capsule) {
|
||||
const streak = computeCapsuleSuccessStreak({ capsuleId: capsule.id });
|
||||
capsule.success_streak = streak || 1;
|
||||
capsule.a2a = {
|
||||
eligible_to_broadcast:
|
||||
isBlastRadiusSafe(capsule.blast_radius) &&
|
||||
(capsule.outcome.score || 0) >= 0.7 &&
|
||||
(capsule.success_streak || 0) >= 2,
|
||||
};
|
||||
capsule.asset_id = computeAssetId(capsule);
|
||||
upsertCapsule(capsule);
|
||||
}
|
||||
try {
|
||||
if (personalityState) {
|
||||
updatePersonalityStats({ personalityState, outcome: outcomeStatus, score, notes: `event:${event.id}` });
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
const runId = lastRun && lastRun.run_id ? String(lastRun.run_id) : stableHash(`${parentEventId || 'root'}|${geneId || 'none'}|${signalKey}`);
|
||||
state.last_solidify = {
|
||||
run_id: runId, at: ts, event_id: event.id, capsule_id: capsuleId, outcome: event.outcome,
|
||||
};
|
||||
if (!dryRun) writeStateForSolidify(state);
|
||||
|
||||
return { ok: success, event, capsule, gene: geneUsed, constraintCheck, validation, validationReport, blast };
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
solidify,
|
||||
readStateForSolidify,
|
||||
writeStateForSolidify,
|
||||
isValidationCommandAllowed,
|
||||
};
|
||||
55
archive/inactive-skills/evolver/src/gep/validationReport.js
Normal file
55
archive/inactive-skills/evolver/src/gep/validationReport.js
Normal file
@@ -0,0 +1,55 @@
|
||||
// Standardized ValidationReport type for GEP.
|
||||
// Machine-readable, self-contained, and interoperable.
|
||||
// Can be consumed by external Hubs or Judges for automated assessment.
|
||||
|
||||
const { computeAssetId, SCHEMA_VERSION } = require('./contentHash');
|
||||
const { captureEnvFingerprint, envFingerprintKey } = require('./envFingerprint');
|
||||
|
||||
// Build a standardized ValidationReport from raw validation results.
|
||||
function buildValidationReport({ geneId, commands, results, envFp, startedAt, finishedAt }) {
|
||||
const env = envFp || captureEnvFingerprint();
|
||||
const resultsList = Array.isArray(results) ? results : [];
|
||||
const cmdsList = Array.isArray(commands) ? commands : resultsList.map(function (r) { return r && r.cmd ? String(r.cmd) : ''; });
|
||||
const overallOk = resultsList.length > 0 && resultsList.every(function (r) { return r && r.ok; });
|
||||
const durationMs =
|
||||
Number.isFinite(startedAt) && Number.isFinite(finishedAt) ? finishedAt - startedAt : null;
|
||||
|
||||
const report = {
|
||||
type: 'ValidationReport',
|
||||
schema_version: SCHEMA_VERSION,
|
||||
id: 'vr_' + Date.now(),
|
||||
gene_id: geneId || null,
|
||||
env_fingerprint: env,
|
||||
env_fingerprint_key: envFingerprintKey(env),
|
||||
commands: cmdsList.map(function (cmd, i) {
|
||||
const r = resultsList[i] || {};
|
||||
return {
|
||||
command: String(cmd || ''),
|
||||
ok: !!r.ok,
|
||||
stdout: String(r.out || '').slice(0, 4000),
|
||||
stderr: String(r.err || '').slice(0, 4000),
|
||||
};
|
||||
}),
|
||||
overall_ok: overallOk,
|
||||
duration_ms: durationMs,
|
||||
created_at: new Date().toISOString(),
|
||||
};
|
||||
|
||||
report.asset_id = computeAssetId(report);
|
||||
return report;
|
||||
}
|
||||
|
||||
// Validate that an object is a well-formed ValidationReport.
|
||||
function isValidValidationReport(obj) {
|
||||
if (!obj || typeof obj !== 'object') return false;
|
||||
if (obj.type !== 'ValidationReport') return false;
|
||||
if (!obj.id || typeof obj.id !== 'string') return false;
|
||||
if (!Array.isArray(obj.commands)) return false;
|
||||
if (typeof obj.overall_ok !== 'boolean') return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
buildValidationReport,
|
||||
isValidValidationReport,
|
||||
};
|
||||
Reference in New Issue
Block a user