progmune-runtime 2.1.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +172 -0
- package/dist/action-runtime.js +1 -0
- package/dist/audit.js +4 -0
- package/dist/branch-ledger.js +28 -0
- package/dist/deterministic-replay.js +6 -0
- package/dist/emitter.js +31 -0
- package/dist/execute.js +9 -0
- package/dist/extract-ir.js +48 -6
- package/dist/failure-collector.js +20 -0
- package/dist/failure-corpus.js +26 -0
- package/dist/feedback.js +4 -0
- package/dist/immune-reporter.js +1 -0
- package/dist/ir-utils.js +18 -0
- package/dist/ledger-registry.js +11 -0
- package/dist/llm.js +3 -0
- package/dist/memory-layer.js +3 -0
- package/dist/planner.js +109 -18
- package/dist/protocol-registry.js +7 -0
- package/dist/repair-proposal.js +10 -0
- package/dist/runtime-invariants.js +9 -0
- package/dist/runtime.js +1 -0
- package/dist/search-planner.js +1 -0
- package/dist/semantic-snapshot.js +2 -0
- package/dist/session-utils.js +19 -0
- package/dist/ssg-validator.js +8 -0
- package/dist/utils.js +2 -0
- package/dist/validator.js +2 -0
- package/package.json +1 -1
- package/readme.md +0 -829
package/README.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Progmune Runtime(免序)
|
|
2
|
+
|
|
3
|
+
**程序免疫学:约束引导的程序合成运行时**
|
|
4
|
+
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](https://modelcontextprotocol.io)
|
|
7
|
+
[]()
|
|
8
|
+
|
|
9
|
+
Progmune(免序)不是一个 AI 编程助手,而是一个面向生成式程序的免疫系统。它将大语言模型(LLM)从开放世界的代码生成器,降级为在程序真相层(IR)严格约束下的启发式搜索器,确保生成的代码不仅在符号和类型上正确,更在行为协议上合法。
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 目录
|
|
14
|
+
|
|
15
|
+
- [核心命题](#核心命题ai-生成的程序必须具备免疫系统)
|
|
16
|
+
- [架构概览](#架构概览一个会学习会记忆会防御的运行时)
|
|
17
|
+
- [语义有效性级别 (SVL)](#语义有效性级别-svl)
|
|
18
|
+
- [v2.1.0 新特性:抗体与快照](#v210-新特性抗体与快照)
|
|
19
|
+
- [Semantic Observatory](#semantic-observatory语义观测台)
|
|
20
|
+
- [快速开始](#快速开始)
|
|
21
|
+
- [CLI 命令](#cli-命令)
|
|
22
|
+
- [MCP 工具](#mcp-工具)
|
|
23
|
+
- [全球免疫网络](#全球免疫网络-global-immune-network)
|
|
24
|
+
- [许可证](#许可证)
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 核心命题:AI 生成的程序必须具备免疫系统
|
|
29
|
+
|
|
30
|
+
LLM 在生成代码时会产生“幻觉”——调用不存在的函数、违反类型约束、跳过关键的业务步骤。传统的提示工程和事后校验无法根除这些问题,因为它们将 LLM 置于系统的中心,缺乏第一性原理的约束。
|
|
31
|
+
|
|
32
|
+
Progmune 提出**程序免疫学(Program Immunology)**范式,为生成式程序建立一套可识别、可记忆、可进化的防御体系:
|
|
33
|
+
|
|
34
|
+
1. **天然免疫**:快速识别并拒绝违反符号存在性、类型兼容性和数据流规则的代码。
|
|
35
|
+
2. **获得性免疫**:从过去的失败案例中学习,生成特异性的防御规则,主动预防未来同类错误。
|
|
36
|
+
3. **免疫记忆**:将成功和失败的模式沉淀为结构化的知识,使系统随着使用持续进化,越用越可靠。
|
|
37
|
+
|
|
38
|
+
**详细理论框架请参阅《[Program Immunology 白皮书](./WHITEPAPER.md)》。**
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 架构概览:一个会学习、会记忆、会防御的运行时
|
|
43
|
+
|
|
44
|
+
Progmune 的架构受生物免疫系统启发,分为核心防御层:
|
|
45
|
+
|
|
46
|
+
| 生物免疫系统 | 程序免疫 (Progmune) | 核心职责 |
|
|
47
|
+
|:-------------|:--------------------|:---------|
|
|
48
|
+
| **天然免疫** | **约束引擎** (IR + SVL-1~SVL-3) | 快速、自动地拒绝调用不存在的函数、类型错误和数据流问题。 |
|
|
49
|
+
| **获得性免疫** | **语义状态图 (SSG)** | 通过可编程的状态机,精确拦截非法业务逻辑跃迁(如“未认证即签发令牌”)。 |
|
|
50
|
+
| **免疫记忆** | **三层记忆 + Failure Corpus** | 工作记忆、情景记忆和语义记忆协同;失败基因组记录每次语义异常、修复路径和适应轨迹。 |
|
|
51
|
+
| **抗体生成** | **Antibody Registry** | **v2.1.0 新增**:从失败中自动挖掘修复模式,生成 ACL-1~4 置信度分级的免疫规则。 |
|
|
52
|
+
| **免疫观测** | **Semantic Observatory** | 终端原生语义观测工具——时间线、认知回放、状态机追踪、基因组热力图。 |
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## v2.1.0 新特性:抗体与快照
|
|
57
|
+
|
|
58
|
+
在 v2.1.0 版本中,Progmune 实现了从“被动拦截”到“主动防御”的跨越:
|
|
59
|
+
|
|
60
|
+
* **抗体注册表 (Antibody Registry)**:系统自动从 `Failure Corpus` 中提取修复模式。高置信度(ACL-4)的抗体可触发“免疫快跑”,绕过 LLM 直接应用验证过的修复路径。
|
|
61
|
+
* **语义快照引擎 (Snapshot Engine)**:在规划时自动捕获 IR 状态。支持通过 `diff` 命令对比不同时间点的 IR 差异,解决因环境漂移导致的生成失败。
|
|
62
|
+
* **BFS 协议修复**:SSG 验证器现在使用广度优先搜索寻找多步修复路径,能够自动补全复杂的协议缺失(如 `INIT` -> `EMAIL_OK` -> `PWD_HASHED`)。
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 语义有效性级别 (SVL)
|
|
67
|
+
|
|
68
|
+
Progmune 定义了 AI 生成代码正确性的分层标准:
|
|
69
|
+
|
|
70
|
+
| 级别 | 名称 | 保证 |
|
|
71
|
+
|:-----|:-----|:-----|
|
|
72
|
+
| SVL-1 | 符号存在性 | 绝不调用项目中不存在的函数 |
|
|
73
|
+
| SVL-2 | 类型有效性 | 参数数量和类型严格匹配 |
|
|
74
|
+
| SVL-3 | 数据流正确性 | 变量先声明后使用,无循环引用 |
|
|
75
|
+
| SVL-4 | 协议合法性 | 业务步骤顺序必须遵守状态迁移规则 |
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## 快速开始
|
|
80
|
+
|
|
81
|
+
### 前置条件
|
|
82
|
+
|
|
83
|
+
* [Node.js](https://nodejs.org/) >= 18
|
|
84
|
+
* 一个有效的 LLM API 密钥(DeepSeek 或 OpenAI 兼容接口)
|
|
85
|
+
|
|
86
|
+
### 1. 安装
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
npm install -g progmune-runtime
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### 2. 配置
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
npx progmune-runtime setup "你的API密钥"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 3. 验证
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
npx progmune-runtime test
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## 许可证
|
|
107
|
+
|
|
108
|
+
MIT License。
|
|
109
|
+
|
|
110
|
+
Progmune 正在重新定义 AI 辅助编程——不是“让模型更聪明”,而是“让程序真相主导生成”。
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
# Progmune Runtime
|
|
115
|
+
|
|
116
|
+
**Program Immunology: Constraint-Guided Program Synthesis Runtime**
|
|
117
|
+
|
|
118
|
+
[](https://opensource.org/licenses/MIT)
|
|
119
|
+
[](https://modelcontextprotocol.io)
|
|
120
|
+
[]()
|
|
121
|
+
|
|
122
|
+
Progmune is not an AI programming assistant, but an immune system for generative programs. It demotes LLMs from open-world code generators to heuristic searchers strictly constrained by the Program Truth Layer (IR).
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## v2.1.0 New Features: Antibodies & Snapshots
|
|
127
|
+
|
|
128
|
+
v2.1.0 marks a major leap from "passive interception" to "active defense":
|
|
129
|
+
|
|
130
|
+
* **Antibody Registry**: Automatically extracts repair patterns from the `Failure Corpus`. High-confidence (ACL-4) antibodies trigger "Immune Fast-Path," bypassing the LLM to apply validated fixes directly.
|
|
131
|
+
* **Semantic Snapshot Engine**: Captures the exact IR state during planning. Supports `diff` commands to track IR evolution and debug environment drift.
|
|
132
|
+
* **BFS Protocol Repair**: The SSG validator now uses Breadth-First Search to find multi-hop repair paths, automatically filling complex protocol gaps.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Architecture Overview
|
|
137
|
+
|
|
138
|
+
| Biological Immune System | Program Immunology (Progmune) | Core Responsibility |
|
|
139
|
+
|:-------------------------|:------------------------------|:--------------------|
|
|
140
|
+
| **Innate Immunity** | **Constraint Engine** (IR + SVL-1~3) | Rejects non-existent functions, type errors, and dataflow issues. |
|
|
141
|
+
| **Adaptive Immunity** | **Semantic State Graph (SSG)** | Intercepts illegal business logic transitions (e.g., "issue token before auth"). |
|
|
142
|
+
| **Immune Memory** | **Three-Layer Memory** | Working, episodic, and semantic memory collaborate to make the system smarter with use. |
|
|
143
|
+
| **Antibody Generation** | **Antibody Registry** | **New in v2.1.0**: Mines repair patterns and generates ACL-1~4 graded immune rules. |
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Semantic Validity Levels (SVL)
|
|
148
|
+
|
|
149
|
+
| Level | Name | Guarantee |
|
|
150
|
+
|:------|:---------------------|:-----------------------------------------------|
|
|
151
|
+
| SVL-1 | Symbolic Existence | Never calls functions that do not exist in the project |
|
|
152
|
+
| SVL-2 | Type Validity | Parameter count and types strictly match |
|
|
153
|
+
| SVL-3 | Dataflow Correctness | Variables are declared before use, no circular references |
|
|
154
|
+
| SVL-4 | Protocol Legality | Business step order must adhere to state transition rules |
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Quick Start
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
npm install -g progmune-runtime
|
|
162
|
+
npx progmune-runtime setup "YOUR_API_KEY"
|
|
163
|
+
npx progmune-runtime test
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## License
|
|
169
|
+
|
|
170
|
+
MIT License.
|
|
171
|
+
|
|
172
|
+
Progmune is redefining AI-assisted programming—not by "making models smarter," but by "letting program truth govern generation."
|
package/dist/action-runtime.js
CHANGED
package/dist/audit.js
CHANGED
|
@@ -46,6 +46,8 @@ const fs = __importStar(require("fs"));
|
|
|
46
46
|
const path = __importStar(require("path"));
|
|
47
47
|
/** Scan a directory recursively for TypeScript files and check for @progmune-generated markers. */
|
|
48
48
|
const DEFAULT_THRESHOLD = 0.8;
|
|
49
|
+
/** Audit a directory for @progmune-generated markers and report coverage. */
|
|
50
|
+
/** @requires DIRECTORY @produces AUDIT_RESULT */
|
|
49
51
|
function auditDirectory(dir, threshold = DEFAULT_THRESHOLD) {
|
|
50
52
|
const result = {
|
|
51
53
|
directory: dir,
|
|
@@ -127,6 +129,8 @@ function scanDir(rootDir, currentDir, result) {
|
|
|
127
129
|
}
|
|
128
130
|
}
|
|
129
131
|
/** Format audit result as human-readable text. */
|
|
132
|
+
/** Format audit result as human-readable text with coverage statistics. */
|
|
133
|
+
/** @requires AUDIT_RESULT @produces FORMATTED_REPORT */
|
|
130
134
|
function formatAuditResult(result) {
|
|
131
135
|
const pct = (result.coverage * 100).toFixed(1);
|
|
132
136
|
const lines = [];
|
package/dist/branch-ledger.js
CHANGED
|
@@ -32,10 +32,13 @@ const runtime_invariants_1 = require("./runtime-invariants");
|
|
|
32
32
|
const protocol_registry_1 = require("./protocol-registry");
|
|
33
33
|
// ── Pure Functions ──
|
|
34
34
|
/** Generate a unique branch ID. */
|
|
35
|
+
/** @requires BRANCH_TREE @produces BRANCH_ID */
|
|
35
36
|
function generateBranchId() {
|
|
36
37
|
return `br_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
|
|
37
38
|
}
|
|
38
39
|
/** Create the root branch of a new execution tree. */
|
|
40
|
+
/** Create the root branch of an execution tree. */
|
|
41
|
+
/** @requires TRANSITIONS @produces ROOT_BRANCH */
|
|
39
42
|
function createRootBranch(transitions = []) {
|
|
40
43
|
const id = generateBranchId();
|
|
41
44
|
return {
|
|
@@ -64,6 +67,8 @@ function createRootBranch(transitions = []) {
|
|
|
64
67
|
* - The child has no shared history with the parent beyond the parent's
|
|
65
68
|
* final state
|
|
66
69
|
*/
|
|
70
|
+
/** Create a child branch from a parent branch. */
|
|
71
|
+
/** @requires PARENT_BRANCH @produces CHILD_BRANCH */
|
|
67
72
|
function createBranch(parent, reason = "alternative", initialTransitions = []) {
|
|
68
73
|
const id = generateBranchId();
|
|
69
74
|
return {
|
|
@@ -103,6 +108,8 @@ function createBranch(parent, reason = "alternative", initialTransitions = []) {
|
|
|
103
108
|
* Use createBranch when:
|
|
104
109
|
* - Starting a completely fresh alternative from the parent's final state
|
|
105
110
|
*/
|
|
111
|
+
/** Fork a branch at a split index creating a sibling repair branch. */
|
|
112
|
+
/** @requires BRANCH @produces FORKED_BRANCHES */
|
|
106
113
|
function forkBranch(branch, splitIndex, reason = "repair_attempt") {
|
|
107
114
|
const sharedTransitions = branch.transitions.slice(0, splitIndex + 1);
|
|
108
115
|
const remainingTransitions = branch.transitions.slice(splitIndex + 1);
|
|
@@ -133,6 +140,8 @@ function forkBranch(branch, splitIndex, reason = "repair_attempt") {
|
|
|
133
140
|
* Takes the successful path: concatenates transitions from each branch
|
|
134
141
|
* in the order they appear in the tree path.
|
|
135
142
|
* Returns null if no branches have transitions. */
|
|
143
|
+
/** Merge multiple branches into one unified branch. */
|
|
144
|
+
/** @requires BRANCH_LIST @produces MERGED_BRANCH */
|
|
136
145
|
function mergeBranches(branches) {
|
|
137
146
|
const validBranches = branches.filter(b => b.transitions.length > 0);
|
|
138
147
|
if (validBranches.length === 0)
|
|
@@ -164,6 +173,8 @@ function mergeBranches(branches) {
|
|
|
164
173
|
/** Flatten a branch and all its ancestors into a single linear transition sequence.
|
|
165
174
|
* This is the bridge between the tree model and existing linear-only consumers
|
|
166
175
|
* (checkLedgerConsistency, hashLedger, etc.). */
|
|
176
|
+
/** Flatten a branch tree into a linear transition sequence. */
|
|
177
|
+
/** @requires BRANCH_TREE @produces TRANSITIONS */
|
|
167
178
|
function flattenBranch(branch, allBranches) {
|
|
168
179
|
const path = getBranchPath(branch, allBranches);
|
|
169
180
|
const allTransitions = [];
|
|
@@ -187,6 +198,8 @@ function flattenBranch(branch, allBranches) {
|
|
|
187
198
|
return allTransitions.sort((a, b) => a.actionIndex - b.actionIndex);
|
|
188
199
|
}
|
|
189
200
|
/** Get the path from root to a given branch (inclusive). */
|
|
201
|
+
/** Get the path from root to a target branch. */
|
|
202
|
+
/** @requires BRANCH @produces BRANCH_PATH */
|
|
190
203
|
function getBranchPath(branch, allBranches) {
|
|
191
204
|
const path = [];
|
|
192
205
|
let current = branch;
|
|
@@ -203,6 +216,8 @@ function getBranchPath(branch, allBranches) {
|
|
|
203
216
|
}
|
|
204
217
|
/** Replay a branch tree: rebuild state across all ancestor branches
|
|
205
218
|
* and verify every transition is valid. */
|
|
219
|
+
/** Replay a branch tree verifying all transitions. */
|
|
220
|
+
/** @requires BRANCH_TREE @produces REPLAY_RESULT */
|
|
206
221
|
function replayBranch(branch, allBranches, namespaceInitialStates = (0, protocol_registry_1.getNsInit)()) {
|
|
207
222
|
const path = getBranchPath(branch, allBranches);
|
|
208
223
|
const pathIds = path.map(b => b.id);
|
|
@@ -240,6 +255,8 @@ function replayBranch(branch, allBranches, namespaceInitialStates = (0, protocol
|
|
|
240
255
|
};
|
|
241
256
|
}
|
|
242
257
|
/** Build a branch lookup map from an array of branches. */
|
|
258
|
+
/** Build a lookup map from a branch array. */
|
|
259
|
+
/** @requires BRANCH_LIST @produces BRANCH_MAP */
|
|
243
260
|
function buildBranchMap(branches) {
|
|
244
261
|
const map = new Map();
|
|
245
262
|
for (const b of branches) {
|
|
@@ -248,10 +265,13 @@ function buildBranchMap(branches) {
|
|
|
248
265
|
return map;
|
|
249
266
|
}
|
|
250
267
|
/** Find the root branch of a tree. */
|
|
268
|
+
/** Find the root branch of a tree. */
|
|
269
|
+
/** @requires BRANCH_LIST @produces ROOT_BRANCH */
|
|
251
270
|
function findRootBranch(branches) {
|
|
252
271
|
return branches.find(b => !b.parentId);
|
|
253
272
|
}
|
|
254
273
|
/** Find all child branches of a given parent. */
|
|
274
|
+
/** @requires PARENT_BRANCH @produces CHILD_BRANCHES */
|
|
255
275
|
function findChildBranches(parent, allBranches) {
|
|
256
276
|
const children = [];
|
|
257
277
|
for (const b of allBranches.values()) {
|
|
@@ -262,6 +282,8 @@ function findChildBranches(parent, allBranches) {
|
|
|
262
282
|
return children;
|
|
263
283
|
}
|
|
264
284
|
/** Get the full branch tree as a human-readable structure. */
|
|
285
|
+
/** Format a branch tree as human-readable text. */
|
|
286
|
+
/** @requires BRANCH_TREE @produces DESCRIPTION */
|
|
265
287
|
function describeBranchTree(root, allBranches, indent = 0) {
|
|
266
288
|
const prefix = " ".repeat(indent);
|
|
267
289
|
let result = `${prefix}${root.id.slice(0, 12)} [${root.reason}] (${root.transitions.length} tx, outcome: ${root.outcome || "open"})\n`;
|
|
@@ -274,6 +296,8 @@ function describeBranchTree(root, allBranches, indent = 0) {
|
|
|
274
296
|
/** Evaluate all branches in a tree, score them, and return the recommended winner.
|
|
275
297
|
* Scoring: replay +50, zero violations +30, success outcome +20.
|
|
276
298
|
* Highest score wins. Ties go to the smaller branch (fewer transitions). */
|
|
299
|
+
/** Score all branches in a tree and return the recommended winner. */
|
|
300
|
+
/** @requires BRANCH_TREE @produces BRANCH_SCORES */
|
|
277
301
|
function evaluateBranches(branches, namespaceInitialStates = new Map([["_global", "UNAUTHENTICATED"]])) {
|
|
278
302
|
if (branches.length === 0)
|
|
279
303
|
return { scores: [], winner: null };
|
|
@@ -326,6 +350,8 @@ function evaluateBranches(branches, namespaceInitialStates = new Map([["_global"
|
|
|
326
350
|
return { scores, winner };
|
|
327
351
|
}
|
|
328
352
|
/** Wrap a linear transition array as a single root branch (backward compat). */
|
|
353
|
+
/** Wrap linear transitions as a single root branch. */
|
|
354
|
+
/** @requires TRANSITIONS @produces ROOT_BRANCH */
|
|
329
355
|
function wrapAsBranch(transitions) {
|
|
330
356
|
const b = createRootBranch(transitions);
|
|
331
357
|
b.outcome = transitions.length > 0
|
|
@@ -335,6 +361,8 @@ function wrapAsBranch(transitions) {
|
|
|
335
361
|
}
|
|
336
362
|
/** Unwrap a branch tree to the linear transition list.
|
|
337
363
|
* For backward compatibility: flattens the "winning" path (first successful leaf). */
|
|
364
|
+
/** Unwrap a branch tree to a flat transition list. */
|
|
365
|
+
/** @requires BRANCH_LIST @produces TRANSITIONS */
|
|
338
366
|
function unwrapBranchTree(branches) {
|
|
339
367
|
if (branches.length === 0)
|
|
340
368
|
return [];
|
|
@@ -53,6 +53,8 @@ const ssg_validator_1 = require("./ssg-validator");
|
|
|
53
53
|
const branch_ledger_1 = require("./branch-ledger");
|
|
54
54
|
// ── Core Replay ──
|
|
55
55
|
/** Replay a session from disk, comparing against its stored fingerprint. */
|
|
56
|
+
/** Deterministically replay a session ledger and verify against stored fingerprint. */
|
|
57
|
+
/** @requires SESSION_DATA @produces REPLAY_RESULT */
|
|
56
58
|
function replaySession(sessionId, currentRules, namespaceInitialStates = (0, protocol_registry_1.getNsInit)()) {
|
|
57
59
|
// Load session
|
|
58
60
|
const sessionsDir = path.resolve(process.env.PROGMUNE_PROJECT_DIR || process.cwd(), ".progmune_corpus/sessions");
|
|
@@ -112,6 +114,8 @@ function replaySession(sessionId, currentRules, namespaceInitialStates = (0, pro
|
|
|
112
114
|
return replayLedger(sessionId, transitions, sessionRuleHash, storedHash, currentRuleHash, currentRules, namespaceInitialStates);
|
|
113
115
|
}
|
|
114
116
|
/** Core replay logic: replay transitions against (optional) current rules. */
|
|
117
|
+
/** Replay a ledger of transitions against current rules and verify consistency. */
|
|
118
|
+
/** @requires LEDGER_DATA @produces REPLAY_RESULT */
|
|
115
119
|
function replayLedger(sessionId, transitions, storedRuleHash, storedLedgerHash, currentRuleHash, currentRules, namespaceInitialStates = (0, protocol_registry_1.getNsInit)()) {
|
|
116
120
|
const replayedHash = transitions.length > 0 ? (0, ssg_validator_1.hashLedger)(transitions) : "";
|
|
117
121
|
const ruleHashMatch = !currentRuleHash || !storedRuleHash
|
|
@@ -200,6 +204,8 @@ function replayLedger(sessionId, transitions, storedRuleHash, storedLedgerHash,
|
|
|
200
204
|
};
|
|
201
205
|
}
|
|
202
206
|
/** Replay with per-transition detail — for debugging and UI. */
|
|
207
|
+
/** Replay transitions with per-step detail for debugging. */
|
|
208
|
+
/** @requires TRANSITIONS @produces DETAIL_RESULT */
|
|
203
209
|
function replayWithDetail(transitions, currentRules, namespaceInitialStates = (0, protocol_registry_1.getNsInit)()) {
|
|
204
210
|
if (!currentRules || currentRules.size === 0) {
|
|
205
211
|
return transitions.map(t => ({
|
package/dist/emitter.js
CHANGED
|
@@ -72,6 +72,8 @@ function getImportPath(file) {
|
|
|
72
72
|
* 将动作序列编译为目标语言代码。
|
|
73
73
|
* @protocol namespace=dev_pipeline pre_states=["SEQUENCE_VALIDATED"] post_states=["CODE_EMITTED"] invalidate=["SEQUENCE_VALIDATED"]
|
|
74
74
|
*/
|
|
75
|
+
/** @requires ACTIONS @produces TYPESCRIPT_CODE */
|
|
76
|
+
/** @requires ACTIONS @produces TYPESCRIPT_CODE */
|
|
75
77
|
function emitCode(actions, meta) {
|
|
76
78
|
const irRaw = JSON.parse(fs.readFileSync("ir.json", "utf-8"));
|
|
77
79
|
// Support both old format (array) and new format ({typeMap, functions})
|
|
@@ -205,6 +207,22 @@ function emitCode(actions, meta) {
|
|
|
205
207
|
return "false";
|
|
206
208
|
return `""`;
|
|
207
209
|
}
|
|
210
|
+
// String enum defaults — match param type to sensible value
|
|
211
|
+
const STRING_ENUMS = {
|
|
212
|
+
"SVL": '"SVL-4"', "RootCause": '"F01"', "BranchReason": '"repair_attempt"',
|
|
213
|
+
"RepairStrategy": '"insert"', "ConstraintType": '"protocol"',
|
|
214
|
+
"SVLString": '"SVL-4"', "BranchOutcome": '"success"',
|
|
215
|
+
};
|
|
216
|
+
if (STRING_ENUMS[paramType])
|
|
217
|
+
return STRING_ENUMS[paramType];
|
|
218
|
+
// Array types: use empty array
|
|
219
|
+
if (paramType.endsWith("[]"))
|
|
220
|
+
return "[]";
|
|
221
|
+
// Generic Map/Set types: use empty constructor
|
|
222
|
+
if (paramType.startsWith("Map<"))
|
|
223
|
+
return `new Map()`;
|
|
224
|
+
if (paramType.startsWith("Set<"))
|
|
225
|
+
return `new Set()`;
|
|
208
226
|
if (paramType === "UserPayload")
|
|
209
227
|
return `{ id: 1, role: "user" } as UserPayload`;
|
|
210
228
|
if (paramType === "PasswordHash")
|
|
@@ -253,6 +271,19 @@ function emitCode(actions, meta) {
|
|
|
253
271
|
};
|
|
254
272
|
for (const a of actions)
|
|
255
273
|
code += convert(a) + "\n";
|
|
274
|
+
// Ensure last action is a return — inject one if LLM forgot
|
|
275
|
+
const lastAction = actions[actions.length - 1];
|
|
276
|
+
if (lastAction && lastAction.kind !== "return") {
|
|
277
|
+
const allCalls = actions.filter(a => a.kind === "call" && a.assignTo);
|
|
278
|
+
if (allCalls.length === 1) {
|
|
279
|
+
code += ` return ${allCalls[0].assignTo};\n`;
|
|
280
|
+
}
|
|
281
|
+
else if (allCalls.length > 1) {
|
|
282
|
+
// Multiple calls: return all results as an object
|
|
283
|
+
const vars = allCalls.map(c => c.assignTo).join(", ");
|
|
284
|
+
code += ` return { ${vars} };\n`;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
256
287
|
code += "}\n";
|
|
257
288
|
// Call main with params if it has inputs, otherwise no-arg call
|
|
258
289
|
if (inputs.length > 0) {
|
package/dist/execute.js
CHANGED
|
@@ -69,6 +69,7 @@ function saveMetrics(m) {
|
|
|
69
69
|
fs.writeFileSync(METRICS_FILE, JSON.stringify(m, null, 2), "utf-8");
|
|
70
70
|
}
|
|
71
71
|
/** Record a generation event. Called automatically by execute(). */
|
|
72
|
+
/** @requires GENERATION_EVENT @produces METRICS_DATA */
|
|
72
73
|
function recordGeneration(record) {
|
|
73
74
|
const m = loadMetrics();
|
|
74
75
|
m.generated++;
|
|
@@ -82,6 +83,8 @@ function recordGeneration(record) {
|
|
|
82
83
|
saveMetrics(m);
|
|
83
84
|
}
|
|
84
85
|
/** Get current execution metrics. */
|
|
86
|
+
/** @requires METRICS_DATA @produces EXECUTION_METRICS */
|
|
87
|
+
/** @requires METRICS_DATA @produces EXECUTION_METRICS */
|
|
85
88
|
function getExecutionMetrics() {
|
|
86
89
|
return loadMetrics();
|
|
87
90
|
}
|
|
@@ -92,6 +95,8 @@ function getExecutionMetrics() {
|
|
|
92
95
|
* @param projectPath - Absolute path to project root
|
|
93
96
|
* @param filePath - Optional: write generated code to this file
|
|
94
97
|
*/
|
|
98
|
+
/** @requires INTENT @produces CODE */
|
|
99
|
+
/** @requires INTENT @produces CODE */
|
|
95
100
|
async function execute(intent, projectPath, filePath) {
|
|
96
101
|
// 1. IR extraction
|
|
97
102
|
let ir;
|
|
@@ -208,6 +213,9 @@ async function execute(intent, projectPath, filePath) {
|
|
|
208
213
|
}
|
|
209
214
|
/** Verify a file compiles without errors. Returns {pass, errors[]}.
|
|
210
215
|
* Uses tsc directly — no grep tricks, no pipefail ambiguity. */
|
|
216
|
+
/** @requires FILE_PATH @produces COMPILE_RESULT */
|
|
217
|
+
/** @requires FILE_PATH @produces COMPILE_RESULT */
|
|
218
|
+
/** @requires FILE_PATH @produces COMPILE_RESULT */
|
|
211
219
|
function verifyCompiles(filePath) {
|
|
212
220
|
try {
|
|
213
221
|
const { execSync } = require("child_process");
|
|
@@ -228,6 +236,7 @@ function verifyCompiles(filePath) {
|
|
|
228
236
|
}
|
|
229
237
|
}
|
|
230
238
|
/** Quick audit: check whether a file has the @progmune-generated marker. */
|
|
239
|
+
/** @requires FILE_PATH @produces MARKER_STATUS */
|
|
231
240
|
function verifyFileMarker(filePath) {
|
|
232
241
|
try {
|
|
233
242
|
const content = fs.readFileSync(filePath, "utf-8");
|
package/dist/extract-ir.js
CHANGED
|
@@ -39,19 +39,31 @@ const ts_morph_1 = require("ts-morph");
|
|
|
39
39
|
const path = __importStar(require("path"));
|
|
40
40
|
const fs = __importStar(require("fs"));
|
|
41
41
|
const ts = __importStar(require("typescript"));
|
|
42
|
-
/** 从 JSDoc 注释中解析 capability 注解 (@purpose, @tags) */
|
|
42
|
+
/** 从 JSDoc 注释中解析 capability 注解 (@purpose, @tags, @requires, @produces) */
|
|
43
43
|
function parseCapabilityFromJSDoc(node) {
|
|
44
44
|
const jsdocs = node.getJsDocs?.();
|
|
45
45
|
if (!jsdocs || jsdocs.length === 0)
|
|
46
46
|
return {};
|
|
47
47
|
const result = {};
|
|
48
48
|
for (const doc of jsdocs) {
|
|
49
|
-
// @purpose
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
// @purpose: full description text (all lines before any @tag)
|
|
50
|
+
const fullText = doc.getFullText?.() || "";
|
|
51
|
+
// Extract description: everything between "/**" and the first "@tag"
|
|
52
|
+
const descMatch = fullText.match(/\/\*\*\s*\n?\s*\*?\s*([^@]*)/);
|
|
53
|
+
if (descMatch) {
|
|
54
|
+
const desc = descMatch[1].replace(/\n\s*\*\s*/g, " ").trim();
|
|
55
|
+
if (desc && !desc.startsWith("@")) {
|
|
56
|
+
result.purpose = desc;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Fallback: use getComment()
|
|
60
|
+
if (!result.purpose) {
|
|
61
|
+
const comment = doc.getComment?.() || "";
|
|
62
|
+
if (comment && !comment.startsWith("@")) {
|
|
63
|
+
result.purpose = comment.split("\n")[0].trim();
|
|
64
|
+
}
|
|
53
65
|
}
|
|
54
|
-
// @tags from ts-morph tag system
|
|
66
|
+
// @tags from ts-morph tag system
|
|
55
67
|
const tsTags = doc.getTags?.();
|
|
56
68
|
if (tsTags) {
|
|
57
69
|
for (const t of tsTags) {
|
|
@@ -60,11 +72,29 @@ function parseCapabilityFromJSDoc(node) {
|
|
|
60
72
|
const val = t.getCommentText?.() || "";
|
|
61
73
|
result.tags = val.split(/[,\s]+/).map((s) => s.trim()).filter(Boolean);
|
|
62
74
|
}
|
|
75
|
+
if (tn === "requires") {
|
|
76
|
+
const val = t.getCommentText?.() || "";
|
|
77
|
+
if (!result.requires)
|
|
78
|
+
result.requires = [];
|
|
79
|
+
result.requires.push(...val.split(/[,\s]+/).map((s) => s.trim()).filter(Boolean));
|
|
80
|
+
}
|
|
81
|
+
if (tn === "produces") {
|
|
82
|
+
const val = t.getCommentText?.() || "";
|
|
83
|
+
if (!result.produces)
|
|
84
|
+
result.produces = [];
|
|
85
|
+
result.produces.push(...val.split(/[,\s]+/).map((s) => s.trim()).filter(Boolean));
|
|
86
|
+
}
|
|
63
87
|
}
|
|
64
88
|
}
|
|
65
89
|
}
|
|
66
90
|
return result;
|
|
67
91
|
}
|
|
92
|
+
/** Auto-derive tags from function's source file name */
|
|
93
|
+
function deriveTagsFromFile(filePath) {
|
|
94
|
+
const name = filePath.replace(/\.ts$/, "").replace(/^src\//, "");
|
|
95
|
+
const tags = name.split(/[\/\-]/).filter(t => t.length > 2 && t !== "src");
|
|
96
|
+
return [...new Set(tags)];
|
|
97
|
+
}
|
|
68
98
|
/** 从 JSDoc 注释中解析 @protocol 注解 */
|
|
69
99
|
function parseProtocolFromJSDoc(node) {
|
|
70
100
|
const jsdocs = node.getJsDocs?.();
|
|
@@ -178,10 +208,14 @@ function extractDirectCalls(func) {
|
|
|
178
208
|
* 从 TypeScript 项目提取 IR(函数签名、参数、返回值、协议注解)。
|
|
179
209
|
* @protocol namespace=dev_pipeline pre_states=[] post_states=["IR_EXTRACTED"] invalidate=["IR_STALE"]
|
|
180
210
|
*/
|
|
211
|
+
/** @requires PROJECT_PATH @produces IR_FUNCTIONS */
|
|
212
|
+
/** @requires PROJECT_PATH @produces IR_FUNCTIONS */
|
|
181
213
|
function extractIR(projectRoot) {
|
|
182
214
|
return extractIRWithTypes(projectRoot).functions;
|
|
183
215
|
}
|
|
184
216
|
/** Extract both functions and type→file mapping. */
|
|
217
|
+
/** @requires PROJECT_PATH @produces IR_WITH_TYPES */
|
|
218
|
+
/** @requires PROJECT_PATH @produces IR_WITH_TYPES */
|
|
185
219
|
function extractIRWithTypes(projectRoot) {
|
|
186
220
|
const absRoot = path.resolve(projectRoot);
|
|
187
221
|
const project = new ts_morph_1.Project({
|
|
@@ -605,6 +639,14 @@ function extractIRWithTypes(projectRoot) {
|
|
|
605
639
|
}
|
|
606
640
|
const totalExternal = dynamicCount + fallbackCount + externalCount;
|
|
607
641
|
console.error(`📦 外部函数: ${totalExternal} (动态=${dynamicCount} 回退=${fallbackCount} 未签名=${externalCount}, from ${allCalls.size} total calls)`);
|
|
642
|
+
// Post-process: auto-derive tags from file names
|
|
643
|
+
for (const f of funcs) {
|
|
644
|
+
if (!f.tags || f.tags.length === 0) {
|
|
645
|
+
const derived = deriveTagsFromFile(f.file);
|
|
646
|
+
if (derived.length > 0)
|
|
647
|
+
f.tags = derived;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
608
650
|
// Build type→module map for emitter
|
|
609
651
|
const _typeMap = {};
|
|
610
652
|
for (const _sf of project.getSourceFiles()) {
|
|
@@ -48,6 +48,9 @@ exports.formatFailureStats = formatFailureStats;
|
|
|
48
48
|
const fs = __importStar(require("fs"));
|
|
49
49
|
const CORPUS_DIR = "failure-corpus";
|
|
50
50
|
/** Classify a compile error string into a root cause. */
|
|
51
|
+
/** Classify a compile error into a root cause category. */
|
|
52
|
+
/** @requires ERROR_STRING @produces ROOT_CAUSE */
|
|
53
|
+
/** @requires ERROR_STRING @produces ROOT_CAUSE */
|
|
51
54
|
function classifyError(error) {
|
|
52
55
|
if (!error)
|
|
53
56
|
return "F10";
|
|
@@ -68,6 +71,9 @@ function classifyError(error) {
|
|
|
68
71
|
return "F10";
|
|
69
72
|
}
|
|
70
73
|
/** Classify a planning failure. */
|
|
74
|
+
/** Classify a planning failure into a root cause category. */
|
|
75
|
+
/** @requires ERROR_STRING @produces ROOT_CAUSE */
|
|
76
|
+
/** @requires ERROR_STRING @produces ROOT_CAUSE */
|
|
71
77
|
function classifyPlanError(error) {
|
|
72
78
|
if (!error)
|
|
73
79
|
return "F10";
|
|
@@ -78,6 +84,8 @@ function classifyPlanError(error) {
|
|
|
78
84
|
return "F07"; // most plan failures are parsing issues
|
|
79
85
|
}
|
|
80
86
|
/** Record a failure and save to disk. */
|
|
87
|
+
/** Record a generation failure to the failure corpus. */
|
|
88
|
+
/** @requires FAILURE_EVENT @produces FAILURE_ID */
|
|
81
89
|
function recordFailure(record) {
|
|
82
90
|
const id = `F-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
83
91
|
const entry = {
|
|
@@ -94,6 +102,10 @@ function recordFailure(record) {
|
|
|
94
102
|
return id;
|
|
95
103
|
}
|
|
96
104
|
/** Load all recorded failures. */
|
|
105
|
+
/** Load all recorded failures from the failure corpus. */
|
|
106
|
+
/** @requires FAILURE_CORPUS @produces FAILURE_LIST */
|
|
107
|
+
/** @requires FAILURE_CORPUS @produces FAILURE_LIST */
|
|
108
|
+
/** @requires FAILURE_CORPUS @produces FAILURE_LIST */
|
|
97
109
|
function loadFailures() {
|
|
98
110
|
if (!fs.existsSync(CORPUS_DIR))
|
|
99
111
|
return [];
|
|
@@ -109,6 +121,10 @@ function loadFailures() {
|
|
|
109
121
|
return failures.sort((a, b) => b.timestamp - a.timestamp);
|
|
110
122
|
}
|
|
111
123
|
/** Get failure statistics grouped by root cause. */
|
|
124
|
+
/** Get failure statistics grouped by root cause. */
|
|
125
|
+
/** @requires FAILURE_LIST @produces FAILURE_STATS */
|
|
126
|
+
/** @requires FAILURE_LIST @produces FAILURE_STATS */
|
|
127
|
+
/** @requires FAILURE_LIST @produces FAILURE_STATS */
|
|
112
128
|
function failureStats() {
|
|
113
129
|
const failures = loadFailures();
|
|
114
130
|
const byRootCause = {};
|
|
@@ -123,6 +139,10 @@ function failureStats() {
|
|
|
123
139
|
};
|
|
124
140
|
}
|
|
125
141
|
/** Format failure stats as readable text. */
|
|
142
|
+
/** Format failure statistics as a human-readable report. */
|
|
143
|
+
/** @requires FAILURE_STATS @produces FORMATTED_REPORT */
|
|
144
|
+
/** @requires FAILURE_STATS @produces FORMATTED_REPORT */
|
|
145
|
+
/** @requires FAILURE_STATS @produces FORMATTED_REPORT */
|
|
126
146
|
function formatFailureStats() {
|
|
127
147
|
const stats = failureStats();
|
|
128
148
|
if (stats.total === 0)
|