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 ADDED
@@ -0,0 +1,172 @@
1
+ # Progmune Runtime(免序)
2
+
3
+ **程序免疫学:约束引导的程序合成运行时**
4
+
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+ [![MCP](https://img.shields.io/badge/MCP-Compatible-blue)](https://modelcontextprotocol.io)
7
+ [![Stage: Technical Preview](https://img.shields.io/badge/Stage-Technical_Preview-orange)]()
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
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
119
+ [![MCP](https://img.shields.io/badge/MCP-Compatible-blue)](https://modelcontextprotocol.io)
120
+ [![Stage: Technical Preview](https://img.shields.io/badge/Stage-Technical_Preview-orange)]()
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."
@@ -51,6 +51,7 @@ class ActionBuilder {
51
51
  }
52
52
  }
53
53
  let currentVars = {};
54
+ /** @requires ACTION_CODE @produces ACTION_RESULT */
54
55
  function executeActionCode(code) {
55
56
  const root = new ActionBuilder();
56
57
  currentVars = {};
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 = [];
@@ -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");
@@ -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 from the description text
50
- const comment = doc.getComment?.() || "";
51
- if (comment && !comment.startsWith("@")) {
52
- result.purpose = comment.split("\n")[0].trim();
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 (getTags returns @tag annotations)
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)