openfeel 1.5.2 → 1.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/commands/flow.d.ts.map +1 -1
- package/dist/commands/flow.js +28 -0
- package/dist/commands/flow.js.map +1 -1
- package/dist/core/config.js +2 -2
- package/dist/core/dev-status.d.ts +2 -1
- package/dist/core/dev-status.d.ts.map +1 -1
- package/dist/core/dev-status.js +20 -3
- package/dist/core/dev-status.js.map +1 -1
- package/dist/core/flow-manager.d.ts +24 -0
- package/dist/core/flow-manager.d.ts.map +1 -1
- package/dist/core/flow-manager.js +43 -0
- package/dist/core/flow-manager.js.map +1 -1
- package/dist/core/template-loader.d.ts.map +1 -1
- package/dist/core/template-loader.js +37 -6
- package/dist/core/template-loader.js.map +1 -1
- package/dist/core/update.d.ts.map +1 -1
- package/dist/core/update.js +33 -2
- package/dist/core/update.js.map +1 -1
- package/dsh/ADAPTER.md +4 -2
- package/dsh/README.md +3 -1
- package/dsh/lib/adapters/dashboard.js +29 -0
- package/dsh/lib/adapters/dispatch.js +36 -0
- package/dsh/lib/adapters/recovery.js +120 -0
- package/dsh/lib/surface/prompt.js +12 -0
- package/dsh/lib/tools/index.js +16 -1
- package/dsh/package.json +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 恢复包(v1.5.3)
|
|
3
|
+
*
|
|
4
|
+
* 只读聚合:active stage/phase + 最近一次阶段转换 + `dev_last` 的交接摘要。
|
|
5
|
+
* 目标:新会话一次 `feel_status` 就能拿到恢复所需的最小信息,不必先读全文状态文件。
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
|
|
10
|
+
const HANDOFF_HEADINGS = ['## 交接摘要', '## 公共交接区'];
|
|
11
|
+
const MAX_HANDOFF_CHARS = 600;
|
|
12
|
+
|
|
13
|
+
/** @param {string} projectRoot */
|
|
14
|
+
function username(projectRoot) {
|
|
15
|
+
try {
|
|
16
|
+
const info = JSON.parse(readFileSync(join(projectRoot, '.openfeel', '.info.json'), 'utf-8'));
|
|
17
|
+
if (typeof info.user === 'string' && info.user.trim() !== '') return info.user.trim();
|
|
18
|
+
} catch {
|
|
19
|
+
/* fallthrough */
|
|
20
|
+
}
|
|
21
|
+
return process.env.OPENFEEL_USER ?? 'user';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 读取 `dev_last.md` 的交接摘要段(优先「交接摘要」,否则「公共交接区」)。
|
|
26
|
+
* @param {string} projectRoot
|
|
27
|
+
* @returns {string|null}
|
|
28
|
+
*/
|
|
29
|
+
export function readHandoff(projectRoot) {
|
|
30
|
+
const file = join(projectRoot, '.openfeel', 'users', username(projectRoot), 'dev_last.md');
|
|
31
|
+
let content;
|
|
32
|
+
try {
|
|
33
|
+
if (!existsSync(file)) return null;
|
|
34
|
+
content = readFileSync(file, 'utf-8');
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
let start = -1;
|
|
39
|
+
for (const heading of HANDOFF_HEADINGS) {
|
|
40
|
+
const idx = content.indexOf(heading);
|
|
41
|
+
if (idx !== -1 && (start === -1 || idx < start)) start = idx;
|
|
42
|
+
}
|
|
43
|
+
if (start === -1) return null;
|
|
44
|
+
const rest = content.slice(start);
|
|
45
|
+
const nextHeading = rest.indexOf('\n## ', 1);
|
|
46
|
+
const section = nextHeading === -1 ? rest : rest.slice(0, nextHeading);
|
|
47
|
+
const text = section.trim();
|
|
48
|
+
if (text === '') return null;
|
|
49
|
+
return text.length > MAX_HANDOFF_CHARS ? `${text.slice(0, MAX_HANDOFF_CHARS)}…` : text;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param {string} projectRoot
|
|
54
|
+
* @returns {{
|
|
55
|
+
* stage: string|null, phase: string|null, requirement: string|null,
|
|
56
|
+
* lastTransition: {action:string,time:string,stage:string|null,from:string|null,to:string|null}|null,
|
|
57
|
+
* handoff: string|null, hasDevLast: boolean
|
|
58
|
+
* }}
|
|
59
|
+
*/
|
|
60
|
+
export function readRecovery(projectRoot) {
|
|
61
|
+
const result = {
|
|
62
|
+
stage: null,
|
|
63
|
+
phase: null,
|
|
64
|
+
requirement: null,
|
|
65
|
+
lastTransition: null,
|
|
66
|
+
lastError: null,
|
|
67
|
+
resumeCount: 0,
|
|
68
|
+
handoff: readHandoff(projectRoot),
|
|
69
|
+
hasDevLast: existsSync(join(projectRoot, '.openfeel', 'users', username(projectRoot), 'dev_last.md')),
|
|
70
|
+
};
|
|
71
|
+
try {
|
|
72
|
+
const flow = JSON.parse(readFileSync(join(projectRoot, '.openfeel', 'flow.json'), 'utf-8'));
|
|
73
|
+
const stages = flow.stages ?? {};
|
|
74
|
+
const active = Object.entries(stages).find(([, stage]) => stage.phase !== 'done');
|
|
75
|
+
if (active) {
|
|
76
|
+
result.stage = active[0];
|
|
77
|
+
result.phase = active[1]?.phase ?? null;
|
|
78
|
+
result.requirement = active[1]?.requirement_id ?? null;
|
|
79
|
+
}
|
|
80
|
+
const log = Array.isArray(flow.log) ? flow.log : [];
|
|
81
|
+
// v1.5.4:活跃阶段的最近失败 / resume 次数(从 200 条日志派生,不新增存储)
|
|
82
|
+
if (result.stage !== null) {
|
|
83
|
+
for (const entry of log) {
|
|
84
|
+
const detail = entry?.detail ?? {};
|
|
85
|
+
if (detail.stageName !== result.stage && detail.stage !== result.stage) continue;
|
|
86
|
+
if (entry.action === 'advance_stage_phase' && (detail.to === 'review_failed' || detail.to === 'test_failed')) {
|
|
87
|
+
result.lastError = { phase: detail.to, time: entry.time ?? '' };
|
|
88
|
+
}
|
|
89
|
+
if (detail.resumed === true) result.resumeCount += 1;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
for (let i = log.length - 1; i >= 0; i -= 1) {
|
|
93
|
+
const entry = log[i];
|
|
94
|
+
if (entry?.action === 'advance_stage_phase') {
|
|
95
|
+
const detail = entry.detail ?? {};
|
|
96
|
+
result.lastTransition = {
|
|
97
|
+
action: entry.action,
|
|
98
|
+
time: entry.time ?? '',
|
|
99
|
+
stage: typeof detail.stageName === 'string' ? detail.stageName : null,
|
|
100
|
+
from: typeof detail.from === 'string' ? detail.from : null,
|
|
101
|
+
to: typeof detail.to === 'string' ? detail.to : null,
|
|
102
|
+
};
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (result.lastTransition === null && log.length > 0) {
|
|
107
|
+
const last = log[log.length - 1];
|
|
108
|
+
result.lastTransition = {
|
|
109
|
+
action: last.action ?? '',
|
|
110
|
+
time: last.time ?? '',
|
|
111
|
+
stage: null,
|
|
112
|
+
from: null,
|
|
113
|
+
to: null,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
} catch {
|
|
117
|
+
/* 损坏时降级为 handoff only */
|
|
118
|
+
}
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
@@ -141,6 +141,11 @@ Feel(你)只做编排、决策与门禁判断。
|
|
|
141
141
|
- **归档判据不可协商**:版本归档要求该版本**全部阶段 done** 且**无 blocking open REV**,没有 force 覆盖。归档由 CLI 执行,调度侧只提示不代劳。
|
|
142
142
|
- **不主动读归档**:常规调度不要读 \`.openfeel/flow-archive/**\`(防上下文污染);需要历史时用 \`feel_archive action=show\`。
|
|
143
143
|
|
|
144
|
+
## 派发契约(v1.5.3)
|
|
145
|
+
- 派发子 agent 时用 \`feel_next\` 返回的 \`dispatch\` 模板:goal / context / boundaries / returnFormat。
|
|
146
|
+
- 子 agent **只读派发 prompt 与指定文件**;禁读 \`dev_last*\`、\`dev/current.md\`、\`status.md\`、\`flow.json\`。
|
|
147
|
+
- 返回 **≤10 行**(结论 / 改动文件 / 验证 / 阻塞);不贴全文 diff、日志或大段代码;超标内容留在子 agent,不进入主会话。
|
|
148
|
+
|
|
144
149
|
## 流程入口(v1.5.2)
|
|
145
150
|
- **先 \`feel_status\`**:任何会产出仓库文件(含 \`docs/research/**\`、方案、计划)或需要多步调查/实现的任务,先读状态;无活跃阶段 → \`feel_req add\` → \`claim\` → \`feel_stage add\`,再开始。
|
|
146
151
|
- **不得以「调研 / 只读」为由跳过阶段**:纯只读问答可直接回答;研究/方案产物写入门禁要求活跃阶段(\`docs/research/**\` 无阶段会被拒)。
|
|
@@ -150,6 +155,7 @@ Feel(你)只做编排、决策与门禁判断。
|
|
|
150
155
|
- **只有 Feel 写状态**:\`dev_last*\`、\`dev/current.md\`、\`status.md\`、\`flow.json\` 只由 Feel 更新;**子 agent 一律不读不写**(上下文由派发 prompt / 交接文档提供)。
|
|
151
156
|
- **日志不强制**:不写流水账;\`flow.json.log\` 由 CLI 自动维护(仅保留最近 200 条)。跨会话信息用一条 ≤5 行交接摘要。
|
|
152
157
|
- **更新节点**:开始 / 转折 / 结束,或 \`statusSync.due=true\`(默认 30 分钟)时更新一次交接摘要,随后运行 \`openfeel dev sync\`(CLI 在节点/到期时也会自动同步)。
|
|
158
|
+
- **恢复**:新会话先看 \`feel_status.recovery\`(lastTransition / handoff / nextAction);需要细节再读当前阶段的 plan/op,**不要**启动即读 \`dev_last\` 全文。
|
|
153
159
|
|
|
154
160
|
## 三种工作流模式
|
|
155
161
|
- \`full\`:plan → plan_review → scheme → exec → review → test → archive,六道关全走。
|
|
@@ -206,6 +212,11 @@ planner / schemer / executor / reviewer / feel-tester / archiver. **The authorit
|
|
|
206
212
|
- **Archive criteria are not negotiable**: a version archive requires every stage of that version to be \`done\` AND no blocking open REV; there is no force override. The CLI performs the archive; you only prompt.
|
|
207
213
|
- **Do not read archives during normal scheduling**: avoid \`.openfeel/flow-archive/**\`; use \`feel_archive action=show\` on demand.
|
|
208
214
|
|
|
215
|
+
## Dispatch contract (v1.5.3)
|
|
216
|
+
- When dispatching a subagent, use the \`dispatch\` template returned by \`feel_next\`: goal / context / boundaries / returnFormat.
|
|
217
|
+
- Subagents read **only the dispatch prompt and the listed files**; never \`dev_last*\`, \`dev/current.md\`, \`status.md\`, \`flow.json\`.
|
|
218
|
+
- Return **<=10 lines** (conclusion / changed files / verification / blockers); no full diffs, logs or large code blocks — keep them in the subagent.
|
|
219
|
+
|
|
209
220
|
## Workflow entry (v1.5.2)
|
|
210
221
|
- **Call \`feel_status\` first**: any task that may produce repo files (including \`docs/research/**\`, proposals, plans) or needs multi-step investigation/implementation must read state first; with no active stage → \`feel_req add\` → \`claim\` → \`feel_stage add\`, then start.
|
|
211
222
|
- **Never skip the stage under the pretext of "research / read-only"**: pure read-only Q&A may answer directly; research/proposal artifacts require an active stage (\`docs/research/**\` writes are rejected without one).
|
|
@@ -215,6 +226,7 @@ planner / schemer / executor / reviewer / feel-tester / archiver. **The authorit
|
|
|
215
226
|
- **Only Feel writes state**: \`dev_last*\`, \`dev/current.md\`, \`status.md\`, \`flow.json\` are updated by Feel only; **subagents never read or write them** (context comes from the dispatch prompt / handoff docs).
|
|
216
227
|
- **No mandatory logs**: do not keep a per-step diary; \`flow.json.log\` is maintained by the CLI (last 200 entries only). For cross-session info, write a <=5-line handoff summary.
|
|
217
228
|
- **Update nodes**: start / turning point / end, or when \`statusSync.due=true\` (default 30 min) — update the handoff summary once, then run \`openfeel dev sync\` (the CLI also auto-syncs on nodes/expiry).
|
|
229
|
+
- **Recovery**: at session start read \`feel_status.recovery\` (lastTransition / handoff / nextAction); read the stage's plan/op only for details — do **not** load the whole \`dev_last\` at startup.
|
|
218
230
|
|
|
219
231
|
## The three workflow modes
|
|
220
232
|
- \`full\`: plan → plan_review → scheme → exec → review → test → archive (all six gates).
|
package/dsh/lib/tools/index.js
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
import { FeelError } from '../adapters/cli.js';
|
|
15
15
|
import { legalNextPhases, planNextStep, readSnapshot } from '../adapters/dashboard.js';
|
|
16
16
|
import { readStatusSync } from '../adapters/dev-status.js';
|
|
17
|
+
import { buildDispatchTemplate } from '../adapters/dispatch.js';
|
|
18
|
+
import { readRecovery } from '../adapters/recovery.js';
|
|
17
19
|
import { computeWorkflowGate } from '../adapters/workflow-gate.js';
|
|
18
20
|
import { resolveWorkspace } from '../adapters/workspace.js';
|
|
19
21
|
import {
|
|
@@ -156,11 +158,14 @@ export function createTools(context) {
|
|
|
156
158
|
snapshot.project.root = resolved.cwd;
|
|
157
159
|
snapshot.statusSync = readStatusSync(resolved.cwd);
|
|
158
160
|
snapshot.workflowGate = computeWorkflowGate(snapshot);
|
|
161
|
+
snapshot.recovery = readRecovery(resolved.cwd);
|
|
162
|
+
snapshot.recovery.nextAction = snapshot.focus.suggestedCommand ?? null;
|
|
159
163
|
const focus = snapshot.focus;
|
|
160
164
|
const gateWarning = snapshot.workflowGate.required ? '⚠ 无活跃阶段:非只读任务先 feel_status→建阶段。' : '';
|
|
165
|
+
const staleWarning = snapshot.stale ? '⚠ 快照为降级(flow next 缺失),以 flow status 为准。' : '';
|
|
161
166
|
return {
|
|
162
167
|
data: snapshot,
|
|
163
|
-
summary: `${gateWarning}pipeline=${snapshot.pipeline.phase || '(空)'} 活跃阶段=${focus.stage || '(无)'} phase=${focus.phase || '(空)'} mode=${snapshot.mode.current}(${snapshot.mode.source}) 负责=${focus.responsibleAgent} 可推进到=[${(focus.legalNextPhases ?? []).join(', ') || '无'}]`,
|
|
168
|
+
summary: `${staleWarning}${gateWarning}pipeline=${snapshot.pipeline.phase || '(空)'} 活跃阶段=${focus.stage || '(无)'} phase=${focus.phase || '(空)'} mode=${snapshot.mode.current}(${snapshot.mode.source}) 负责=${focus.responsibleAgent} 可推进到=[${(focus.legalNextPhases ?? []).join(', ') || '无'}]`,
|
|
164
169
|
next: focus.phase === 'done' || focus.phase === ''
|
|
165
170
|
? null
|
|
166
171
|
: { tool: 'feel_next', arguments: {}, why: '获取「下一步该谁做」的明确建议。' },
|
|
@@ -189,11 +194,21 @@ export function createTools(context) {
|
|
|
189
194
|
snapshot.project.root = resolved.cwd;
|
|
190
195
|
snapshot.statusSync = readStatusSync(resolved.cwd);
|
|
191
196
|
snapshot.workflowGate = computeWorkflowGate(snapshot);
|
|
197
|
+
snapshot.recovery = readRecovery(resolved.cwd);
|
|
192
198
|
const plan = planNextStep(snapshot);
|
|
193
199
|
plan.workflowGate = snapshot.workflowGate;
|
|
200
|
+
snapshot.recovery.nextAction = plan.suggestedCommand ?? null;
|
|
201
|
+
plan.recovery = snapshot.recovery;
|
|
202
|
+
plan.dispatch = buildDispatchTemplate(snapshot);
|
|
194
203
|
if (plan.workflowGate.required) {
|
|
195
204
|
plan.summary = `⚠ ${plan.workflowGate.message} ${plan.summary}`;
|
|
196
205
|
}
|
|
206
|
+
if (snapshot.stale) {
|
|
207
|
+
plan.summary = `⚠ 快照为降级(flow next 缺失),以 flow status 为准。 ${plan.summary}`;
|
|
208
|
+
}
|
|
209
|
+
plan.revision = snapshot.revision ?? null;
|
|
210
|
+
plan.snapshotAt = snapshot.snapshotAt ?? null;
|
|
211
|
+
plan.stale = snapshot.stale === true;
|
|
197
212
|
return {
|
|
198
213
|
data: plan,
|
|
199
214
|
summary: plan.summary,
|
package/dsh/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openfeel-dsh",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.4",
|
|
4
4
|
"description": "OpenFeel × DSH — Feel 模式适配器:把 OpenFeel 的流水线、需求分发、版本归档与知识库接入 DeepSeek Harness",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": true,
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"openfeel": {
|
|
34
34
|
"adapter": "dsh",
|
|
35
|
-
"requiresCli": ">=1.5.
|
|
35
|
+
"requiresCli": ">=1.5.4"
|
|
36
36
|
},
|
|
37
37
|
"keywords": [
|
|
38
38
|
"openfeel",
|