@hoplogic/hopjit 0.1.0
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/LICENSE +21 -0
- package/README.md +99 -0
- package/dist/act-body-interpreter.d.ts +24 -0
- package/dist/act-body-interpreter.js +159 -0
- package/dist/act-body-parser.d.ts +10 -0
- package/dist/act-body-parser.js +577 -0
- package/dist/act-builtins.d.ts +12 -0
- package/dist/act-builtins.js +104 -0
- package/dist/ast-helpers.d.ts +26 -0
- package/dist/ast-helpers.js +58 -0
- package/dist/ast-runtime.d.ts +49 -0
- package/dist/ast-runtime.js +224 -0
- package/dist/ast-types.d.ts +260 -0
- package/dist/ast-types.js +4 -0
- package/dist/cli-types.d.ts +190 -0
- package/dist/cli-types.js +4 -0
- package/dist/cli.d.ts +26 -0
- package/dist/cli.js +623 -0
- package/dist/dispatcher.d.ts +92 -0
- package/dist/dispatcher.js +692 -0
- package/dist/doc-ref.d.ts +41 -0
- package/dist/doc-ref.js +214 -0
- package/dist/engine-traverse.d.ts +37 -0
- package/dist/engine-traverse.js +385 -0
- package/dist/engine.d.ts +141 -0
- package/dist/engine.js +1792 -0
- package/dist/errors.d.ts +47 -0
- package/dist/errors.js +34 -0
- package/dist/hoplog.d.ts +120 -0
- package/dist/hoplog.js +501 -0
- package/dist/parser.d.ts +7 -0
- package/dist/parser.js +802 -0
- package/dist/persistence.d.ts +60 -0
- package/dist/persistence.js +208 -0
- package/dist/prompt.d.ts +130 -0
- package/dist/prompt.js +1014 -0
- package/dist/provider-types.d.ts +134 -0
- package/dist/provider-types.js +3 -0
- package/dist/runtime-types.d.ts +84 -0
- package/dist/runtime-types.js +4 -0
- package/dist/tools.d.ts +16 -0
- package/dist/tools.js +141 -0
- package/dist/validator.d.ts +23 -0
- package/dist/validator.js +959 -0
- package/driver/codex/AGENTS.md +54 -0
- package/driver/hopskill-build/SKILL.md +137 -0
- package/driver/hopskill-build/references/spec-skeleton.md +132 -0
- package/driver/hopskill-build/references/step-type-cheatsheet.md +56 -0
- package/driver/hopskill-build/references/three-focus-rules.md +148 -0
- package/driver/hopspec-skill.md +187 -0
- package/driver/references/cli-discovery.md +36 -0
- package/driver/references/discovery.md +45 -0
- package/driver/references/driver-subagent.md +50 -0
- package/driver/references/parallel-worker.md +50 -0
- package/driver/references/step-execution-rules.md +47 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lenx Wei
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# HopJIT
|
|
2
|
+
|
|
3
|
+
**HopJIT** 是 HopSpec v3 的执行运行时——一套面向 LLM Agent 的结构化执行引擎。你用 HopSpec markdown 声明任务结构(步骤类型、数据流、循环/分支/并行、人机介入点),HopJIT 负责执行状态管理、变量存储、retry/adaptive 修复、None 传播等流控。
|
|
4
|
+
|
|
5
|
+
两种驱动模式:
|
|
6
|
+
|
|
7
|
+
- **复用模式(reuse)**:外层 LLM(如 Claude Code)当推理引擎,HopJIT 只做纯流控——**不需要 API key**。配套 `hopspec` skill 驱动。
|
|
8
|
+
- **独立模式(standalone)**:HopJIT 内部 StepDispatcher 直调 LLM API 执行——需 `@anthropic-ai/sdk` + `ANTHROPIC_API_KEY`。
|
|
9
|
+
|
|
10
|
+
## 包名、命令名、文件
|
|
11
|
+
|
|
12
|
+
一句话理清三层:装 **包** `@hoplogic/hopjit` → 得到 **命令** `hopjit` → 它执行包内的 `dist/cli.js`。
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# 全局 CLI
|
|
16
|
+
npm install -g @hoplogic/hopjit
|
|
17
|
+
|
|
18
|
+
# 或项目内
|
|
19
|
+
npm install @hoplogic/hopjit
|
|
20
|
+
|
|
21
|
+
# 开发期(克隆本仓库)
|
|
22
|
+
npm install && npm run build && npm link
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
装好后 `hopjit --help` 可用(bin 命令名是 `hopjit`,与包名 scope 无关)。
|
|
26
|
+
|
|
27
|
+
## CLI 用法
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
hopjit validate <spec.md> # 校验 spec 合法性(放行前闸门,验证规则)
|
|
31
|
+
hopjit list <dir> # 列出目录下可执行 spec
|
|
32
|
+
hopjit run <spec.md> [--params '<json>'] # 启动执行(复用模式由 skill 驱动,见下)
|
|
33
|
+
hopjit status --state-dir <dir> [--instance <id>] # 查执行进度
|
|
34
|
+
hopjit resume --state-dir <dir> [--instance <id>] # 从中断恢复
|
|
35
|
+
hopjit install-skill [--dir <目标>] [--carrier cc|codex] # 安装驱动 skill(见下)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
其余子命令(`init` / `submit_and_fetch_next` / `join_parallel` / `fanout-plan` / `fanout-next` / `debug_step` / `vars`)由驱动层(skill / dispatcher)调用,一般不手动跑。
|
|
39
|
+
|
|
40
|
+
**Quickstart**(校验你自己的 spec):
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
hopjit validate my-spec.md
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
> spec 范本见项目仓库 `examples/`(未随 npm 包发布——多为引擎自测范本)。
|
|
47
|
+
|
|
48
|
+
## 复用模式:安装驱动 skill
|
|
49
|
+
|
|
50
|
+
复用模式下外层 LLM(Claude Code / Codex)通过 skill 驱动引擎。**一条命令安装**(自动把包内 `driver/` 源展开到正确布局,免手动 cp):
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# Claude Code(默认,装到 .claude/skills/)
|
|
54
|
+
hopjit install-skill
|
|
55
|
+
|
|
56
|
+
# Codex(装到指定目录,用 AGENTS.md 载体)
|
|
57
|
+
hopjit install-skill --carrier codex --dir .
|
|
58
|
+
|
|
59
|
+
# 覆盖已存在的(改过 skill 想重置时)
|
|
60
|
+
hopjit install-skill --force
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`install-skill` 展开的布局(CC):
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
.claude/skills/
|
|
67
|
+
├── hopspec/
|
|
68
|
+
│ ├── SKILL.md # 主 skill(源 driver/hopspec-skill.md 改名)
|
|
69
|
+
│ └── references/ # 载体中立共享(cli-discovery / driver-subagent / …)
|
|
70
|
+
└── hopskill-build/
|
|
71
|
+
├── SKILL.md
|
|
72
|
+
└── references/
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
skill 启动时**自举探测** hopjit 位置(全局命令 → 项目 node_modules → 包内 dist → 开发期 fallback),无需硬编码路径——见 `references/cli-discovery.md`。
|
|
76
|
+
|
|
77
|
+
之后在 Claude Code 里:
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
/hopspec run <spec.md> # 驱动执行一个 spec
|
|
81
|
+
/hopspec list [dir] # 列出可执行 spec
|
|
82
|
+
/hopskill-build # 把 prose skill 翻译成 hopskill
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Codex 载体见 `driver/codex/AGENTS.md`——同构 CC,只换四处载体原语(触发 / 起 subagent / 问人 / act 工具)。
|
|
86
|
+
|
|
87
|
+
## 依赖说明
|
|
88
|
+
|
|
89
|
+
- **运行时依赖**:`commander`(CLI)+ `@anthropic-ai/sdk`(仅独立模式的 StepDispatcher 用;复用模式不触发)。
|
|
90
|
+
- 复用模式(skill 驱动)**不需要 API key**——推理由外层 LLM 承担。
|
|
91
|
+
|
|
92
|
+
## 文档
|
|
93
|
+
|
|
94
|
+
- 项目仓库 `examples/` — 端到端 spec 范本(data-quality / doc-review / parallel-* / fact-check 等;未随 npm 包发布)。
|
|
95
|
+
- HopSpec v3 语法与执行语义见项目概念层文档(HopSpec V3 核心规范 / 配套 HopJIT 运行时能力)。
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
MIT
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ActBody, OutputDecl } from './ast-types.js';
|
|
2
|
+
import type { ToolProvider } from './provider-types.js';
|
|
3
|
+
/** BodyInterpreter 执行 hop_python body 所需的上下文(scope 初值输入、工具 provider、commit 放行开关、工具调用日志)。见 [[act-body#^anc-exec-act-body-interp]] */
|
|
4
|
+
export interface BodyExecContext {
|
|
5
|
+
inputs: Record<string, unknown>;
|
|
6
|
+
toolProvider: ToolProvider;
|
|
7
|
+
allowCommit: boolean;
|
|
8
|
+
toolCallLog: Array<{
|
|
9
|
+
name: string;
|
|
10
|
+
result: 'success' | 'failure';
|
|
11
|
+
at: string;
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
14
|
+
/** 独立模式的 hop_python 解释器:按 ActBody AST 顺序求值执行、调白名单工具,执行期无 LLM/无推理。见 [[act-body#^anc-exec-act-body-interp]] */
|
|
15
|
+
export declare class BodyInterpreter {
|
|
16
|
+
private ctx;
|
|
17
|
+
constructor(ctx: BodyExecContext);
|
|
18
|
+
run(body: ActBody, outputDecls: OutputDecl[]): Promise<Record<string, unknown>>;
|
|
19
|
+
private execStatements;
|
|
20
|
+
private execStatement;
|
|
21
|
+
private evalExpr;
|
|
22
|
+
private evalBinary;
|
|
23
|
+
private evalCall;
|
|
24
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// @module: act-body ^anc-struct-act-body
|
|
2
|
+
// act body 解释器(独立模式执行器):按 ActBody AST 顺序执行——求值 + 调白名单工具,无 LLM。
|
|
3
|
+
// 设计见 design/exec-engine.md ^anc-exec-act-body-interp;概念 ^anc-step-act-body-lang。
|
|
4
|
+
// 替换 dispatcher.executeActWithTools 的 LLM tool-use 循环(执行期无推理)。
|
|
5
|
+
// @a: anc-exec-act-body-interp
|
|
6
|
+
import { ACT_BUILTINS } from './act-builtins.js';
|
|
7
|
+
import { isTruthy } from './ast-runtime.js';
|
|
8
|
+
/** 独立模式的 hop_python 解释器:按 ActBody AST 顺序求值执行、调白名单工具,执行期无 LLM/无推理。见 [[act-body#^anc-exec-act-body-interp]] */
|
|
9
|
+
export class BodyInterpreter {
|
|
10
|
+
ctx;
|
|
11
|
+
constructor(ctx) {
|
|
12
|
+
this.ctx = ctx;
|
|
13
|
+
}
|
|
14
|
+
// 执行 body,返回 +→ 声明的输出值(从 scope 取声明名)
|
|
15
|
+
async run(body, outputDecls) {
|
|
16
|
+
const scope = new Map(Object.entries(this.ctx.inputs));
|
|
17
|
+
await this.execStatements(body.statements, scope);
|
|
18
|
+
const outputs = {};
|
|
19
|
+
for (const decl of outputDecls) {
|
|
20
|
+
if (scope.has(decl.name))
|
|
21
|
+
outputs[decl.name] = scope.get(decl.name);
|
|
22
|
+
}
|
|
23
|
+
return outputs;
|
|
24
|
+
}
|
|
25
|
+
async execStatements(stmts, scope) {
|
|
26
|
+
for (const stmt of stmts)
|
|
27
|
+
await this.execStatement(stmt, scope);
|
|
28
|
+
}
|
|
29
|
+
async execStatement(stmt, scope) {
|
|
30
|
+
if (stmt.type === 'assign') {
|
|
31
|
+
const a = stmt;
|
|
32
|
+
scope.set(a.target, await this.evalExpr(a.value, scope));
|
|
33
|
+
}
|
|
34
|
+
else if (stmt.type === 'call') {
|
|
35
|
+
await this.evalExpr(stmt.call, scope); // 副作用调用,丢弃返回值
|
|
36
|
+
}
|
|
37
|
+
else if (stmt.type === 'if') {
|
|
38
|
+
const ifs = stmt;
|
|
39
|
+
const cond = await this.evalExpr(ifs.condition, scope);
|
|
40
|
+
if (isTruthy(cond))
|
|
41
|
+
await this.execStatements(ifs.then_body, scope);
|
|
42
|
+
else if (ifs.else_body)
|
|
43
|
+
await this.execStatements(ifs.else_body, scope);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async evalExpr(expr, scope) {
|
|
47
|
+
switch (expr.type) {
|
|
48
|
+
case 'literal':
|
|
49
|
+
return expr.value;
|
|
50
|
+
case 'var':
|
|
51
|
+
if (!scope.has(expr.name))
|
|
52
|
+
throw new Error(`act body 引用未定义变量 "${expr.name}"`);
|
|
53
|
+
return scope.get(expr.name);
|
|
54
|
+
case 'field': {
|
|
55
|
+
const obj = await this.evalExpr(expr.object, scope);
|
|
56
|
+
if (obj === null || obj === undefined) {
|
|
57
|
+
throw new Error(`act body 在 null/undefined 上取字段 "${expr.field}"`);
|
|
58
|
+
}
|
|
59
|
+
return obj[expr.field];
|
|
60
|
+
}
|
|
61
|
+
case 'unary': {
|
|
62
|
+
const v = await this.evalExpr(expr.operand, scope);
|
|
63
|
+
if (expr.op === 'not')
|
|
64
|
+
return !isTruthy(v);
|
|
65
|
+
return -toNumber(v); // '-'
|
|
66
|
+
}
|
|
67
|
+
case 'binary':
|
|
68
|
+
return this.evalBinary(expr.op, expr.left, expr.right, scope);
|
|
69
|
+
case 'call':
|
|
70
|
+
return this.evalCall(expr, scope);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async evalBinary(op, leftE, rightE, scope) {
|
|
74
|
+
// and/or 短路
|
|
75
|
+
if (op === 'and') {
|
|
76
|
+
const l = await this.evalExpr(leftE, scope);
|
|
77
|
+
if (!isTruthy(l))
|
|
78
|
+
return l;
|
|
79
|
+
return this.evalExpr(rightE, scope);
|
|
80
|
+
}
|
|
81
|
+
if (op === 'or') {
|
|
82
|
+
const l = await this.evalExpr(leftE, scope);
|
|
83
|
+
if (isTruthy(l))
|
|
84
|
+
return l;
|
|
85
|
+
return this.evalExpr(rightE, scope);
|
|
86
|
+
}
|
|
87
|
+
const l = await this.evalExpr(leftE, scope);
|
|
88
|
+
const r = await this.evalExpr(rightE, scope);
|
|
89
|
+
switch (op) {
|
|
90
|
+
case '+':
|
|
91
|
+
// 仿 JS:任一为字符串则拼接,否则数加
|
|
92
|
+
if (typeof l === 'string' || typeof r === 'string')
|
|
93
|
+
return String(l) + String(r);
|
|
94
|
+
return toNumber(l) + toNumber(r);
|
|
95
|
+
case '-': return toNumber(l) - toNumber(r);
|
|
96
|
+
case '*': return toNumber(l) * toNumber(r);
|
|
97
|
+
case '/': return toNumber(l) / toNumber(r);
|
|
98
|
+
case '==': return looseEq(l, r);
|
|
99
|
+
case '!=': return !looseEq(l, r);
|
|
100
|
+
case '<': return toNumber(l) < toNumber(r);
|
|
101
|
+
case '>': return toNumber(l) > toNumber(r);
|
|
102
|
+
case '<=': return toNumber(l) <= toNumber(r);
|
|
103
|
+
case '>=': return toNumber(l) >= toNumber(r);
|
|
104
|
+
default: throw new Error(`act body 未知运算符 "${op}"`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async evalCall(call, scope) {
|
|
108
|
+
// 内置函数:位置参数
|
|
109
|
+
const builtin = ACT_BUILTINS.get(call.callee);
|
|
110
|
+
if (builtin) {
|
|
111
|
+
const args = await Promise.all(call.args.map(a => this.evalExpr(a.value, scope)));
|
|
112
|
+
return builtin.fn(args);
|
|
113
|
+
}
|
|
114
|
+
// 否则视为 ToolProvider 工具:命名参数 → Record;先查 requires_commit 拦截
|
|
115
|
+
const toolDef = this.ctx.toolProvider.list().find(t => t.name === call.callee);
|
|
116
|
+
if (!toolDef) {
|
|
117
|
+
throw new Error(`TOOL_EXEC_ERROR: act body 调用未知工具/函数 "${call.callee}"(不在内置白名单也不在 ToolProvider 清单)`);
|
|
118
|
+
}
|
|
119
|
+
if (!this.ctx.allowCommit && toolDef.requires_commit) {
|
|
120
|
+
throw new Error(`COMMIT_REQUIRED: tool "${call.callee}" requires commit step`);
|
|
121
|
+
}
|
|
122
|
+
const argObj = {};
|
|
123
|
+
for (const a of call.args) {
|
|
124
|
+
if (!a.name)
|
|
125
|
+
throw new Error(`act body 工具 "${call.callee}" 调用须用命名参数(如 ${call.callee}(arg: value))`);
|
|
126
|
+
argObj[a.name] = await this.evalExpr(a.value, scope);
|
|
127
|
+
}
|
|
128
|
+
const result = await this.ctx.toolProvider.execute(call.callee, argObj);
|
|
129
|
+
// 工具瞬时失败的重试归 ToolProvider 内部职责(execute 自己实现重试/超时)——
|
|
130
|
+
// 解释器只调一次拿结果,与工具命名空间同归宿主。失败即抛,转容器级 retry。
|
|
131
|
+
this.ctx.toolCallLog.push({
|
|
132
|
+
name: call.callee,
|
|
133
|
+
result: result.success ? 'success' : 'failure',
|
|
134
|
+
at: new Date().toISOString(),
|
|
135
|
+
});
|
|
136
|
+
if (!result.success) {
|
|
137
|
+
throw new Error(`TOOL_EXEC_ERROR: tool "${call.callee}" failed: ${String(result.result)}`);
|
|
138
|
+
}
|
|
139
|
+
return result.result;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function toNumber(v) {
|
|
143
|
+
const n = typeof v === 'number' ? v : Number(v);
|
|
144
|
+
if (Number.isNaN(n))
|
|
145
|
+
throw new Error(`act body 期望数字,实际: ${JSON.stringify(v)}`);
|
|
146
|
+
return n;
|
|
147
|
+
}
|
|
148
|
+
// 宽松相等:数字与数字字符串可比("3" == 3),其余按 ===
|
|
149
|
+
function looseEq(l, r) {
|
|
150
|
+
if (l === r)
|
|
151
|
+
return true;
|
|
152
|
+
if ((typeof l === 'number' || typeof r === 'number') && typeof l !== 'object' && typeof r !== 'object') {
|
|
153
|
+
const ln = Number(l);
|
|
154
|
+
const rn = Number(r);
|
|
155
|
+
if (!Number.isNaN(ln) && !Number.isNaN(rn))
|
|
156
|
+
return ln === rn;
|
|
157
|
+
}
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ActBody } from './ast-types.js';
|
|
2
|
+
import type { ParseError } from './errors.js';
|
|
3
|
+
export declare function extractHopPythonFence(instructionLines: string[]): {
|
|
4
|
+
bodyLines: string[] | null;
|
|
5
|
+
bodyStartIndex: number;
|
|
6
|
+
narrative: string[];
|
|
7
|
+
};
|
|
8
|
+
export declare function parseActBody(bodyLines: string[], baseLine: number, errors: ParseError[]): ActBody | null;
|
|
9
|
+
export declare function serializeActBody(body: ActBody): string;
|
|
10
|
+
export declare function bodyHasToolCall(body: ActBody): boolean;
|