openfeel 1.4.0 → 1.5.2
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/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/dev.d.ts +11 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +47 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/flow.d.ts.map +1 -1
- package/dist/commands/flow.js +12 -0
- package/dist/commands/flow.js.map +1 -1
- package/dist/core/config.js +2 -2
- package/dist/core/dev-status.d.ts +37 -0
- package/dist/core/dev-status.d.ts.map +1 -0
- package/dist/core/dev-status.js +176 -0
- package/dist/core/dev-status.js.map +1 -0
- package/dist/core/flow-manager.d.ts +3 -1
- package/dist/core/flow-manager.d.ts.map +1 -1
- package/dist/core/flow-manager.js +6 -1
- package/dist/core/flow-manager.js.map +1 -1
- package/dist/core/i18n-data/en.d.ts.map +1 -1
- package/dist/core/i18n-data/en.js +9 -0
- package/dist/core/i18n-data/en.js.map +1 -1
- package/dist/core/i18n-data/zh-CN.d.ts.map +1 -1
- package/dist/core/i18n-data/zh-CN.js +9 -0
- package/dist/core/i18n-data/zh-CN.js.map +1 -1
- package/dist/core/template-loader.d.ts.map +1 -1
- package/dist/core/template-loader.js +130 -183
- package/dist/core/template-loader.js.map +1 -1
- package/dist/core/update.d.ts.map +1 -1
- package/dist/core/update.js +67 -140
- package/dist/core/update.js.map +1 -1
- package/dsh/ADAPTER.md +5 -3
- package/dsh/README.md +4 -2
- package/dsh/lib/adapters/dashboard.js +6 -0
- package/dsh/lib/adapters/dev-status.js +40 -0
- package/dsh/lib/adapters/workflow-gate.js +31 -0
- package/dsh/lib/guard.js +35 -4
- package/dsh/lib/surface/prompt.js +20 -0
- package/dsh/lib/tools/index.js +12 -1
- package/dsh/package.json +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dev 状态同步(v1.5.0,适配器侧只读)
|
|
3
|
+
*
|
|
4
|
+
* 与 CLI `openfeel dev status` 同源:读 `.openfeel/dev/.sync.json` 判断是否到期;
|
|
5
|
+
* 只读,不写盘。用于 feel_next / feel_status 暴露 statusSync.due。
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
|
|
10
|
+
/** 默认同步间隔(分钟) */
|
|
11
|
+
export const DEFAULT_SYNC_INTERVAL_MINUTES = 30;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 读取状态同步快照(缺失/损坏 → due=true 且 lastSync=null)。
|
|
15
|
+
* @param {string} projectRoot
|
|
16
|
+
* @param {number} [now]
|
|
17
|
+
* @returns {{ due: boolean, lastSync: string|null, intervalMinutes: number }}
|
|
18
|
+
*/
|
|
19
|
+
export function readStatusSync(projectRoot, now = Date.now()) {
|
|
20
|
+
const file = join(projectRoot, '.openfeel', 'dev', '.sync.json');
|
|
21
|
+
let lastSync = null;
|
|
22
|
+
let intervalMinutes = DEFAULT_SYNC_INTERVAL_MINUTES;
|
|
23
|
+
try {
|
|
24
|
+
if (existsSync(file)) {
|
|
25
|
+
const raw = JSON.parse(readFileSync(file, 'utf-8'));
|
|
26
|
+
if (typeof raw.last_sync === 'string') lastSync = raw.last_sync;
|
|
27
|
+
if (typeof raw.interval_minutes === 'number' && raw.interval_minutes > 0) {
|
|
28
|
+
intervalMinutes = raw.interval_minutes;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
} catch {
|
|
32
|
+
/* 损坏 → 视为到期 */
|
|
33
|
+
}
|
|
34
|
+
let due = true;
|
|
35
|
+
if (lastSync !== null) {
|
|
36
|
+
const last = Date.parse(lastSync);
|
|
37
|
+
due = !Number.isFinite(last) || now - last >= intervalMinutes * 60_000;
|
|
38
|
+
}
|
|
39
|
+
return { due, lastSync, intervalMinutes };
|
|
40
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 流程入口闸(v1.5.2)
|
|
3
|
+
*
|
|
4
|
+
* 目的:把「无活跃阶段时不得开始需要落盘/多步调查的任务」从提示词约定
|
|
5
|
+
* 变成 feel_status / feel_next 的结构化字段,避免模型以「只读调研」为由跳过阶段。
|
|
6
|
+
*
|
|
7
|
+
* 纯函数、无 IO;由工具层在读取快照后调用。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {Record<string, any>} snapshot readSnapshot 的结果
|
|
12
|
+
* @returns {{ required: boolean, reason: string|null, message: string, actions: string[] }}
|
|
13
|
+
*/
|
|
14
|
+
export function computeWorkflowGate(snapshot) {
|
|
15
|
+
const phase = snapshot?.focus?.phase;
|
|
16
|
+
const hasActiveStage = phase !== '' && phase !== null && phase !== undefined;
|
|
17
|
+
if (hasActiveStage) {
|
|
18
|
+
return { required: false, reason: null, message: '', actions: [] };
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
required: true,
|
|
22
|
+
reason: 'no-active-stage',
|
|
23
|
+
message:
|
|
24
|
+
'当前无活跃阶段:任何会产出仓库文件(含 docs/research/**、方案、计划)或需要多步调查/实现的任务,必须先建立阶段;纯只读问答可例外。',
|
|
25
|
+
actions: [
|
|
26
|
+
'feel_req action=add title="<需求>"',
|
|
27
|
+
'feel_req action=claim id=REQ-NNN',
|
|
28
|
+
'feel_stage action=add stage=<vX.Y.Z-stage-N> tasks=[...]',
|
|
29
|
+
],
|
|
30
|
+
};
|
|
31
|
+
}
|
package/dsh/lib/guard.js
CHANGED
|
@@ -78,6 +78,22 @@ function isDocPath(absolute, cwd) {
|
|
|
78
78
|
|| absolute.startsWith(`${docsRoot}/`);
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
/**
|
|
82
|
+
* 是否为研究/方案产物(v1.5.2):工作区根级 `docs/research/**`。
|
|
83
|
+
*
|
|
84
|
+
* 这类文件是**阶段交付物**而非随手笔记:即使 doc 层原本任意相位可写,
|
|
85
|
+
* 无活跃阶段时也要求先建立阶段,防止以「只读调研」为由跳过流程。
|
|
86
|
+
* @param {string} absolute 已归一化绝对路径
|
|
87
|
+
* @param {string} cwd 已归一化工作区
|
|
88
|
+
* @returns {boolean}
|
|
89
|
+
*/
|
|
90
|
+
function isResearchArtifactPath(absolute, cwd) {
|
|
91
|
+
const root = normalizeTarget(cwd, 'docs/research');
|
|
92
|
+
return absolute === root
|
|
93
|
+
|| absolute.startsWith(`${root}\\`)
|
|
94
|
+
|| absolute.startsWith(`${root}/`);
|
|
95
|
+
}
|
|
96
|
+
|
|
81
97
|
/**
|
|
82
98
|
* 是否位于工作区内(bootstrap 例外边界;防 `..`/绝对路径写出工作区)。
|
|
83
99
|
* @param {string} absolute 已归一化绝对路径
|
|
@@ -114,10 +130,10 @@ function isExemptPath(absolute, cwd) {
|
|
|
114
130
|
}
|
|
115
131
|
|
|
116
132
|
/**
|
|
117
|
-
* 写入门禁审计(v1.4.0
|
|
118
|
-
* best-effort
|
|
133
|
+
* 写入门禁审计(v1.4.0 / v1.5.2):放行与拒绝都追加 JSONL 到 `.openfeel/log/guard-audit.jsonl`。
|
|
134
|
+
* best-effort:任何写失败都吞掉,不影响判定结果。
|
|
119
135
|
* @param {string} cwd 已归一化工作区
|
|
120
|
-
* @param {{ tool: string, reason:
|
|
136
|
+
* @param {{ event?: 'guard_allow'|'guard_deny', tool: string, reason: string, phase: string, paths: string[] }} event
|
|
121
137
|
*/
|
|
122
138
|
function recordGuardAudit(cwd, event) {
|
|
123
139
|
try {
|
|
@@ -127,7 +143,7 @@ function recordGuardAudit(cwd, event) {
|
|
|
127
143
|
}
|
|
128
144
|
const entry = {
|
|
129
145
|
time: new Date().toISOString(),
|
|
130
|
-
event: 'guard_allow',
|
|
146
|
+
event: event.event ?? 'guard_allow',
|
|
131
147
|
tool: event.tool,
|
|
132
148
|
reason: event.reason,
|
|
133
149
|
phase: event.phase,
|
|
@@ -483,6 +499,21 @@ export function createOpenFeelGuard(options = {}) {
|
|
|
483
499
|
}
|
|
484
500
|
|
|
485
501
|
const active = activeStages(flow.value);
|
|
502
|
+
// v1.5.2 入口闸:研究/方案产物(docs/research/**)是阶段交付物,无活跃阶段时不得写入;
|
|
503
|
+
// chore 也不能绕过(在下方 chore 放行之前判定)。
|
|
504
|
+
if (active.length === 0 && targets.some((target) => isResearchArtifactPath(target, normalizedCwd))) {
|
|
505
|
+
recordGuardAudit(normalizedCwd, {
|
|
506
|
+
event: 'guard_deny',
|
|
507
|
+
tool: name,
|
|
508
|
+
reason: 'research-needs-stage',
|
|
509
|
+
phase: 'done',
|
|
510
|
+
paths: targets,
|
|
511
|
+
});
|
|
512
|
+
return t(
|
|
513
|
+
'OpenFeel 写入门禁:研究/方案产物(docs/research/**)要求有活跃阶段。请先 feel_req add → claim → feel_stage add 建立阶段,再写调研/方案。纯只读问答无需落盘。',
|
|
514
|
+
'OpenFeel write guard: research/proposal artifacts (docs/research/**) require an active stage. Create one via feel_req add → claim → feel_stage add before writing. Pure read-only Q&A needs no file.',
|
|
515
|
+
);
|
|
516
|
+
}
|
|
486
517
|
if (active.length === 0) {
|
|
487
518
|
if (allowGeneral && hasGeneralRequirement(flow.value)) return undefined;
|
|
488
519
|
// v1.4.0 chore 通道:仅在**无活跃阶段**时生效;受保护路径已在上方拒绝。
|
|
@@ -141,6 +141,16 @@ Feel(你)只做编排、决策与门禁判断。
|
|
|
141
141
|
- **归档判据不可协商**:版本归档要求该版本**全部阶段 done** 且**无 blocking open REV**,没有 force 覆盖。归档由 CLI 执行,调度侧只提示不代劳。
|
|
142
142
|
- **不主动读归档**:常规调度不要读 \`.openfeel/flow-archive/**\`(防上下文污染);需要历史时用 \`feel_archive action=show\`。
|
|
143
143
|
|
|
144
|
+
## 流程入口(v1.5.2)
|
|
145
|
+
- **先 \`feel_status\`**:任何会产出仓库文件(含 \`docs/research/**\`、方案、计划)或需要多步调查/实现的任务,先读状态;无活跃阶段 → \`feel_req add\` → \`claim\` → \`feel_stage add\`,再开始。
|
|
146
|
+
- **不得以「调研 / 只读」为由跳过阶段**:纯只读问答可直接回答;研究/方案产物写入门禁要求活跃阶段(\`docs/research/**\` 无阶段会被拒)。
|
|
147
|
+
- **判据**:只要任务可能改变仓库内容或需要跨轮次承接,就属于流程任务。
|
|
148
|
+
|
|
149
|
+
## 状态与记录(v1.5.0)
|
|
150
|
+
- **只有 Feel 写状态**:\`dev_last*\`、\`dev/current.md\`、\`status.md\`、\`flow.json\` 只由 Feel 更新;**子 agent 一律不读不写**(上下文由派发 prompt / 交接文档提供)。
|
|
151
|
+
- **日志不强制**:不写流水账;\`flow.json.log\` 由 CLI 自动维护(仅保留最近 200 条)。跨会话信息用一条 ≤5 行交接摘要。
|
|
152
|
+
- **更新节点**:开始 / 转折 / 结束,或 \`statusSync.due=true\`(默认 30 分钟)时更新一次交接摘要,随后运行 \`openfeel dev sync\`(CLI 在节点/到期时也会自动同步)。
|
|
153
|
+
|
|
144
154
|
## 三种工作流模式
|
|
145
155
|
- \`full\`:plan → plan_review → scheme → exec → review → test → archive,六道关全走。
|
|
146
156
|
- \`fast\`:跳过 \`scheme_*\` 与 \`test_*\`;**计划审查与代码审查各保留一轮**(fast 不等于免审)。
|
|
@@ -196,6 +206,16 @@ planner / schemer / executor / reviewer / feel-tester / archiver. **The authorit
|
|
|
196
206
|
- **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.
|
|
197
207
|
- **Do not read archives during normal scheduling**: avoid \`.openfeel/flow-archive/**\`; use \`feel_archive action=show\` on demand.
|
|
198
208
|
|
|
209
|
+
## Workflow entry (v1.5.2)
|
|
210
|
+
- **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
|
+
- **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).
|
|
212
|
+
- **Rule of thumb**: if it may change repo content or span multiple turns, it is a workflow task.
|
|
213
|
+
|
|
214
|
+
## State & records (v1.5.0)
|
|
215
|
+
- **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
|
+
- **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
|
+
- **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).
|
|
218
|
+
|
|
199
219
|
## The three workflow modes
|
|
200
220
|
- \`full\`: plan → plan_review → scheme → exec → review → test → archive (all six gates).
|
|
201
221
|
- \`fast\`: skips \`scheme_*\` and \`test_*\`; **plan review and code review each keep one round** (fast is not review-free).
|
package/dsh/lib/tools/index.js
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
|
|
14
14
|
import { FeelError } from '../adapters/cli.js';
|
|
15
15
|
import { legalNextPhases, planNextStep, readSnapshot } from '../adapters/dashboard.js';
|
|
16
|
+
import { readStatusSync } from '../adapters/dev-status.js';
|
|
17
|
+
import { computeWorkflowGate } from '../adapters/workflow-gate.js';
|
|
16
18
|
import { resolveWorkspace } from '../adapters/workspace.js';
|
|
17
19
|
import {
|
|
18
20
|
boolean,
|
|
@@ -152,10 +154,13 @@ export function createTools(context) {
|
|
|
152
154
|
withRequirements: args.with_requirements === true,
|
|
153
155
|
});
|
|
154
156
|
snapshot.project.root = resolved.cwd;
|
|
157
|
+
snapshot.statusSync = readStatusSync(resolved.cwd);
|
|
158
|
+
snapshot.workflowGate = computeWorkflowGate(snapshot);
|
|
155
159
|
const focus = snapshot.focus;
|
|
160
|
+
const gateWarning = snapshot.workflowGate.required ? '⚠ 无活跃阶段:非只读任务先 feel_status→建阶段。' : '';
|
|
156
161
|
return {
|
|
157
162
|
data: snapshot,
|
|
158
|
-
summary:
|
|
163
|
+
summary: `${gateWarning}pipeline=${snapshot.pipeline.phase || '(空)'} 活跃阶段=${focus.stage || '(无)'} phase=${focus.phase || '(空)'} mode=${snapshot.mode.current}(${snapshot.mode.source}) 负责=${focus.responsibleAgent} 可推进到=[${(focus.legalNextPhases ?? []).join(', ') || '无'}]`,
|
|
159
164
|
next: focus.phase === 'done' || focus.phase === ''
|
|
160
165
|
? null
|
|
161
166
|
: { tool: 'feel_next', arguments: {}, why: '获取「下一步该谁做」的明确建议。' },
|
|
@@ -182,7 +187,13 @@ export function createTools(context) {
|
|
|
182
187
|
return envelope('next', async () => {
|
|
183
188
|
const snapshot = await readSnapshot(cli, { cwd: resolved.cwd, signal: exec.signal });
|
|
184
189
|
snapshot.project.root = resolved.cwd;
|
|
190
|
+
snapshot.statusSync = readStatusSync(resolved.cwd);
|
|
191
|
+
snapshot.workflowGate = computeWorkflowGate(snapshot);
|
|
185
192
|
const plan = planNextStep(snapshot);
|
|
193
|
+
plan.workflowGate = snapshot.workflowGate;
|
|
194
|
+
if (plan.workflowGate.required) {
|
|
195
|
+
plan.summary = `⚠ ${plan.workflowGate.message} ${plan.summary}`;
|
|
196
|
+
}
|
|
186
197
|
return {
|
|
187
198
|
data: plan,
|
|
188
199
|
summary: plan.summary,
|
package/dsh/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openfeel-dsh",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.2",
|
|
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.
|
|
35
|
+
"requiresCli": ">=1.5.2"
|
|
36
36
|
},
|
|
37
37
|
"keywords": [
|
|
38
38
|
"openfeel",
|