openfeel 1.3.3 → 1.3.5

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.
Files changed (47) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/deploy-check-output.d.ts.map +1 -1
  3. package/dist/cli/deploy-check-output.js +7 -1
  4. package/dist/cli/deploy-check-output.js.map +1 -1
  5. package/dist/commands/setup.d.ts.map +1 -1
  6. package/dist/commands/setup.js +7 -0
  7. package/dist/commands/setup.js.map +1 -1
  8. package/dist/core/adapter-hash.d.ts +7 -0
  9. package/dist/core/adapter-hash.d.ts.map +1 -0
  10. package/dist/core/adapter-hash.js +64 -0
  11. package/dist/core/adapter-hash.js.map +1 -0
  12. package/dist/core/config.js +2 -2
  13. package/dist/core/deployment-check.d.ts +17 -4
  14. package/dist/core/deployment-check.d.ts.map +1 -1
  15. package/dist/core/deployment-check.js +59 -7
  16. package/dist/core/deployment-check.js.map +1 -1
  17. package/dist/core/global-paths.d.ts +5 -0
  18. package/dist/core/global-paths.d.ts.map +1 -1
  19. package/dist/core/global-paths.js +7 -0
  20. package/dist/core/global-paths.js.map +1 -1
  21. package/dist/core/i18n-data/en.d.ts.map +1 -1
  22. package/dist/core/i18n-data/en.js +2 -0
  23. package/dist/core/i18n-data/en.js.map +1 -1
  24. package/dist/core/i18n-data/zh-CN.d.ts.map +1 -1
  25. package/dist/core/i18n-data/zh-CN.js +2 -0
  26. package/dist/core/i18n-data/zh-CN.js.map +1 -1
  27. package/dist/core/setup.d.ts.map +1 -1
  28. package/dist/core/setup.js +8 -3
  29. package/dist/core/setup.js.map +1 -1
  30. package/dist/core/template-loader.d.ts.map +1 -1
  31. package/dist/core/template-loader.js +33 -6
  32. package/dist/core/template-loader.js.map +1 -1
  33. package/dist/core/update-state.d.ts +20 -0
  34. package/dist/core/update-state.d.ts.map +1 -1
  35. package/dist/core/update-state.js +26 -0
  36. package/dist/core/update-state.js.map +1 -1
  37. package/dist/core/update.d.ts.map +1 -1
  38. package/dist/core/update.js +21 -2
  39. package/dist/core/update.js.map +1 -1
  40. package/dsh/ADAPTER.md +9 -3
  41. package/dsh/README.md +9 -3
  42. package/dsh/lib/adapters/host-state.js +111 -0
  43. package/dsh/lib/index.js +21 -0
  44. package/dsh/lib/surface/prompt.js +10 -0
  45. package/dsh/lib/tools/index.js +8 -3
  46. package/dsh/package.json +2 -2
  47. package/package.json +1 -1
@@ -0,0 +1,111 @@
1
+ /**
2
+ * 宿主已加载适配器标记(v1.3.4)
3
+ *
4
+ * 目的:让 `openfeel setup --check` 能区分
5
+ * - 「磁盘适配器旧」→ mismatch(应重跑 setup)
6
+ * - 「磁盘已新,但运行中的宿主仍加载旧模块」→ restart-required(应完全重启宿主)
7
+ *
8
+ * 本文件在 DSH 宿主 `apply()`(启动)时由适配器调用 `writeHostMarker()` 写入;
9
+ * 指纹算法必须与 CLI 侧 `src/core/adapter-hash.ts` **逐字节一致**(见该文件注释):
10
+ * `lib/**` 全部文件按相对 POSIX 路径排序 → 逐文件 CRLF→LF 后 sha256 → 拼接 `${rel}\n${hex}\n` → 再 sha256。
11
+ *
12
+ * 零依赖、只读/写一个 JSON;任何异常都吞掉(标记缺失时 CLI 会落到 restart-required,
13
+ * 属可操作提示,不应让宿主挂载失败)。
14
+ *
15
+ * @module openfeel-dsh/host-state
16
+ */
17
+ import { createHash } from 'node:crypto';
18
+ import { mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
19
+ import { homedir } from 'node:os';
20
+ import { dirname, join, relative, sep } from 'node:path';
21
+
22
+ /**
23
+ * 默认标记路径:`~/.openfeel/adapter-host-dsh.json`。
24
+ * `OPENFEEL_ADAPTER_HOST_PATH` 可覆盖(测试隔离用)。
25
+ * @returns {string}
26
+ */
27
+ export function defaultMarkerPath() {
28
+ const override = process.env.OPENFEEL_ADAPTER_HOST_PATH;
29
+ if (typeof override === 'string' && override.trim() !== '') return override;
30
+ return join(homedir(), '.openfeel', 'adapter-host-dsh.json');
31
+ }
32
+
33
+ /** CRLF / CR → LF */
34
+ function normalizeEol(content) {
35
+ return content.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
36
+ }
37
+
38
+ /**
39
+ * 递归收集普通文件(不跟随符号链接目录)。
40
+ * @param {string} dir
41
+ * @param {string[]} out
42
+ */
43
+ function collectFiles(dir, out) {
44
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
45
+ const full = join(dir, entry.name);
46
+ if (entry.isDirectory()) collectFiles(full, out);
47
+ else if (entry.isFile()) out.push(full);
48
+ }
49
+ }
50
+
51
+ /**
52
+ * 计算适配器 `lib/` 目录指纹(与 CLI 侧 `hashAdapterFiles` 同算法)。
53
+ * @param {string} adapterRoot 适配器根目录(含 `lib/`)
54
+ * @returns {string} 64 位 hex;不可读时返回空串
55
+ */
56
+ export function hashLoadedAdapter(adapterRoot) {
57
+ try {
58
+ const libDir = join(adapterRoot, 'lib');
59
+ const files = [];
60
+ collectFiles(libDir, files);
61
+ if (files.length === 0) return '';
62
+ const entries = files
63
+ .map((file) => ({ rel: relative(adapterRoot, file).split(sep).join('/'), abs: file }))
64
+ .sort((a, b) => (a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0));
65
+ const sha = createHash('sha256');
66
+ for (const entry of entries) {
67
+ const content = normalizeEol(readFileSync(entry.abs, 'utf-8'));
68
+ const fileHash = createHash('sha256').update(content, 'utf-8').digest('hex');
69
+ sha.update(`${entry.rel}\n${fileHash}\n`, 'utf-8');
70
+ }
71
+ return sha.digest('hex');
72
+ } catch {
73
+ return '';
74
+ }
75
+ }
76
+
77
+ /**
78
+ * 写入宿主已加载标记(best-effort)。
79
+ * @param {{ adapterRoot: string, version: string, markerPath?: string }} options
80
+ * @returns {{ adapter: string, version: string, loaded_hash: string, started_at: string } | null}
81
+ */
82
+ export function writeHostMarker(options) {
83
+ try {
84
+ const markerPath = options.markerPath ?? defaultMarkerPath();
85
+ const marker = {
86
+ adapter: 'dsh',
87
+ version: options.version,
88
+ loaded_hash: hashLoadedAdapter(options.adapterRoot),
89
+ started_at: new Date().toISOString(),
90
+ };
91
+ mkdirSync(dirname(markerPath), { recursive: true });
92
+ writeFileSync(markerPath, JSON.stringify(marker, null, 2) + '\n', 'utf-8');
93
+ return marker;
94
+ } catch {
95
+ return null;
96
+ }
97
+ }
98
+
99
+ /**
100
+ * 读取宿主已加载标记(只读;缺失/损坏 → null)。
101
+ * @param {string} [markerPath]
102
+ * @returns {{ adapter?: string, version?: string, loaded_hash?: string, started_at?: string } | null}
103
+ */
104
+ export function readHostMarker(markerPath) {
105
+ try {
106
+ const raw = JSON.parse(readFileSync(markerPath ?? defaultMarkerPath(), 'utf-8'));
107
+ return typeof raw === 'object' && raw !== null ? raw : null;
108
+ } catch {
109
+ return null;
110
+ }
111
+ }
package/dsh/lib/index.js CHANGED
@@ -22,8 +22,13 @@
22
22
  * @module openfeel-dsh
23
23
  */
24
24
 
25
+ import { readFileSync } from 'node:fs';
26
+ import { dirname } from 'node:path';
27
+ import { fileURLToPath } from 'node:url';
28
+
25
29
  import { FeelCli } from './adapters/cli.js';
26
30
  import { resolveWorkspace } from './adapters/workspace.js';
31
+ import { writeHostMarker } from './adapters/host-state.js';
27
32
  import { createOpenFeelGuard } from './guard.js';
28
33
  import { createFeelCommandHandler } from './surface/command.js';
29
34
  import { FEEL_PROMPT_NAME, FEEL_PROMPT_ORDER, renderBootstrapPrompt, renderFeelPrompt } from './surface/prompt.js';
@@ -110,6 +115,22 @@ function currentAgent(ctx) {
110
115
  */
111
116
  export function apply(ctx, rawConfig) {
112
117
  const config = normalizeConfig(rawConfig);
118
+
119
+ // v1.3.4:记录「本宿主实际加载的适配器指纹」,供 `openfeel setup --check` 区分
120
+ // 「文件旧」与「文件已新但宿主未重启」。best-effort:失败不影响挂载。
121
+ try {
122
+ const adapterRoot = dirname(dirname(fileURLToPath(import.meta.url)));
123
+ let adapterVersion = '0.0.0';
124
+ try {
125
+ const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
126
+ if (typeof pkg.version === 'string' && pkg.version !== '') adapterVersion = pkg.version;
127
+ } catch {
128
+ /* package.json 不可读时保持占位版本 */
129
+ }
130
+ writeHostMarker({ adapterRoot, version: adapterVersion });
131
+ } catch {
132
+ /* 标记写入失败 → CLI check 会提示 restart-required,可操作,不阻塞 */
133
+ }
113
134
  const log = typeof ctx.logger === 'function'
114
135
  ? ctx.logger('openfeel-dsh')
115
136
  : {
@@ -146,6 +146,11 @@ Feel(你)只做编排、决策与门禁判断。
146
146
  - \`fast\`:跳过 \`scheme_*\` 与 \`test_*\`;**计划审查与代码审查各保留一轮**(fast 不等于免审)。
147
147
  - \`general\`:无流水线(空转移表),按需直接调度通用工作者;**仅需求级合法**,阶段级/部署级会拒绝。
148
148
 
149
+ ## 命令执行
150
+ - **Windows:优先 \`bash\`(Git Bash),其次 \`pwsh\`(PowerShell 7)**;默认不用 \`cmd.exe\`。
151
+ - macOS / Linux:\`bash\`。
152
+ - 同一任务内保持同一 shell;路径与转义按所选 shell 书写。
153
+
149
154
  ## 需求分发
150
155
  需求是排期层(\`REQ-NNN\`,7 状态:draft/scheduled/in_progress/done/blocked/cancelled/rejected)。
151
156
  接取流程:\`feel_req action=next\` → \`claim\` → \`link\` 到阶段 → 阶段推进驱动聚合 → 全部关联阶段 done 时需求自动收口。
@@ -196,6 +201,11 @@ planner / schemer / executor / reviewer / feel-tester / archiver. **The authorit
196
201
  - \`fast\`: skips \`scheme_*\` and \`test_*\`; **plan review and code review each keep one round** (fast is not review-free).
197
202
  - \`general\`: no pipeline (empty transition table), dispatch general workers on demand; requirement-level only.
198
203
 
204
+ ## Command execution
205
+ - **Windows: prefer \`bash\` (Git Bash), then \`pwsh\` (PowerShell 7)**; do not default to \`cmd.exe\`.
206
+ - macOS / Linux: \`bash\`.
207
+ - Keep one shell per task; write paths/escaping for that shell.
208
+
199
209
  ## Requirement distribution
200
210
  Requirements are the scheduling layer (\`REQ-NNN\`, 7 statuses: draft/scheduled/in_progress/done/blocked/cancelled/rejected).
201
211
  Flow: \`feel_req action=next\` → \`claim\` → \`link\` to a stage → stage advancement drives aggregation → auto-close when every linked stage is done.
@@ -207,9 +207,9 @@ export function createTools(context) {
207
207
  '操作。',
208
208
  { required: true },
209
209
  ),
210
- stage: string('阶段 ID(advance/retry 必需;phases/mode 可选用于按阶段派生模式)。'),
210
+ stage: string('阶段 ID(advance/retry 必需;review_add 与 op 二选一;phases/mode 可选用于按阶段派生模式)。'),
211
211
  to: stringEnum(PHASES, 'advance 的目标相位。'),
212
- op: string('操作 ID(如 v1.2.1-stage-93.op-001)。'),
212
+ op: string('操作 ID(如 v1.2.1-stage-93.op-001);review_add 与 stage 二选一。'),
213
213
  force: boolean('advance 时跳过相位合法性与跳跃检查(不能绕过 blocking REV 门)。'),
214
214
  dry_run: boolean('仅校验/预览,不写盘。'),
215
215
  review_tier: stringEnum(TIERS, '审查档位:full=零裁剪 / lean=裁 scheme_review / skip=再裁 plan_review。缺省不裁剪(=full)。'),
@@ -294,7 +294,12 @@ export function createTools(context) {
294
294
  }
295
295
  case 'review_add': {
296
296
  const argv = ['flow', 'review', 'add'];
297
- pushOption(argv, '--op', args.op);
297
+ // v1.3.4:op / stage 二选一透传(fast 无 op 场景必须能挂阶段级 REV)
298
+ if (!args.op && !args.stage) {
299
+ throw new FeelError('review_add 需要 op 或 stage 之一', { kind: 'usage', code: 'FEEL_BAD_USAGE' });
300
+ }
301
+ if (args.op) pushOption(argv, '--op', args.op);
302
+ else pushOption(argv, '--stage', args.stage);
298
303
  pushOption(argv, '--title', requireArg(args, 'title', action));
299
304
  pushOption(argv, '--auto-fix', args.auto_fix);
300
305
  if (args.blocking !== undefined) argv.push('--blocking', args.blocking === true ? 'true' : 'false');
package/dsh/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openfeel-dsh",
3
- "version": "1.3.3",
3
+ "version": "1.3.5",
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.3.3"
35
+ "requiresCli": ">=1.3.5"
36
36
  },
37
37
  "keywords": [
38
38
  "openfeel",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openfeel",
3
- "version": "1.3.3",
3
+ "version": "1.3.5",
4
4
  "description": "AI Agent 开发流程治理 CLI 工具",
5
5
  "type": "module",
6
6
  "private": false,