ims-flow-dashboard 1.2.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/bin/imsflow.mjs +37 -0
- package/bin/preview.mjs +92 -0
- package/package.json +36 -0
- package/projects.example.json +5 -0
- package/scan.mjs +779 -0
- package/template.html +1699 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 booksongs
|
|
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
|
+
# IMS Flow 控制台
|
|
2
|
+
|
|
3
|
+
可视化 OpenSpec 变更的**生命周期阶段、产物完整性、任务进度与产物全文**——零依赖单文件 Node 工具,为 ims-flow 工作流量身打造。
|
|
4
|
+
|
|
5
|
+
## 快速开始
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# 1. 安装(任选其一)
|
|
9
|
+
npm install -g ims-flow-dashboard # npm 安装,拿到 imsflow 全局命令
|
|
10
|
+
# 源码 clone 场景(写进 PowerShell profile / .bashrc,幂等可重复执行):
|
|
11
|
+
# node install.mjs (卸载用 node install.mjs --uninstall)
|
|
12
|
+
|
|
13
|
+
# 2. 配置要扫描的工作区根
|
|
14
|
+
mkdir ~/.imsflow && cp projects.example.json ~/.imsflow/projects.json
|
|
15
|
+
# 编辑 projects.json,填入你的 openspec 工作区根路径
|
|
16
|
+
# (serve 模式下也可以直接在页面「▤ 工作区」面板里增删,自动写入配置)
|
|
17
|
+
|
|
18
|
+
# 3. 重新打开终端,打开控制台
|
|
19
|
+
imsflow --open # 快照模式:生成 dashboard.html 并打开
|
|
20
|
+
imsflow serve # 服务模式:localhost:17771,页面内可重扫/管理工作区
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
> 源码安装器会往 `Documents\WindowsPowerShell\profile.ps1` 和 `~/.bashrc` 写入带 `>>> imsflow >>>` 标记块的函数,重跑即更新,卸载整块移除。Node 路径取自当前运行 install.mjs 的 node(process.execPath),不依赖 npm link / PATH。npm 全局安装不需要 install.mjs——npm 自动生成 `imsflow` / `imsflow.cmd` / `imsflow.ps1`。
|
|
24
|
+
|
|
25
|
+
## 给同事安装
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install -g ims-flow-dashboard # 需 Node ≥ 18
|
|
29
|
+
mkdir ~/.imsflow # 配置目录
|
|
30
|
+
# 复制 projects.example.json 为 ~/.imsflow/projects.json,
|
|
31
|
+
# 改成对方自己的 openspec 工作区根路径(或用 serve 模式页面内添加)
|
|
32
|
+
imsflow --open
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
说明:
|
|
36
|
+
|
|
37
|
+
- 配置查找顺序:**包目录** `projects.json`(源码 clone 模式)→ **`~/.imsflow/projects.json`**(npm 全局安装模式,升级不丢配置)
|
|
38
|
+
- serve 模式(`imsflow serve`)页面内「重新扫描」「▤ 工作区」(增删扫描根/勾选本次扫描)同样可用——刷新是代码能力,与安装方式无关
|
|
39
|
+
- ⚠️ 不要直接压缩整个项目文件夹分发——dashboard.html(含全部变更数据)和 projects.json(本机路径)会被一起带走
|
|
40
|
+
- 离线场景备选:`npm pack` 出 tgz 传文件,对方 `npm install -g <tgz>`;或 `npm run pack` 出 zip(手动解压 + `node install.mjs` 安装命令)
|
|
41
|
+
|
|
42
|
+
## 功能
|
|
43
|
+
|
|
44
|
+
- **生命周期七阶段**:created → proposing → ready → applying → applied → archived(含 review.md 自建标记)
|
|
45
|
+
- **产物完整性检查**:9 类产物是否存在、模板节是否齐全、gates 检查项是否通过
|
|
46
|
+
- **任务进度**:tasks.md 逐项勾选状态,按组折叠
|
|
47
|
+
- **产物全文**:markdown 渲染,代码块、表格、ER 图
|
|
48
|
+
- **全文搜索**(Ctrl+K):搜全部变更的变更名/标题/产物全文,命中定位 + 高亮
|
|
49
|
+
- **来源溯源**:meta.yaml 的 created-by 区分 Trae / TuanjieAI / WorkBuddy / 人工
|
|
50
|
+
- **单/双模式**:快照(双击 dashboard.html 即看)+ serve(重扫按钮)
|
|
51
|
+
|
|
52
|
+
## 命令
|
|
53
|
+
|
|
54
|
+
| 命令 | 说明 |
|
|
55
|
+
|---|---|
|
|
56
|
+
| `imsflow` | 生成 dashboard.html |
|
|
57
|
+
| `imsflow --open` | 生成并打开浏览器 |
|
|
58
|
+
| `imsflow serve` | 启动本地服务(默认端口 17771) |
|
|
59
|
+
| `imsflow serve --port=17772` | 指定端口 |
|
|
60
|
+
| `imsflow <过滤词>` | 按名称/路径模糊匹配工作区根 |
|
|
61
|
+
| `imsflow --help` | 帮助 |
|
|
62
|
+
|
|
63
|
+
## 配置
|
|
64
|
+
|
|
65
|
+
`projects.json`(各人本地配置,不随包发布):
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"roots": [
|
|
70
|
+
"D:\\project\\my-repo",
|
|
71
|
+
"D:\\work\\openspec-demo"
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
每个 root 是一个 openspec 工作区根(含 `openspec/changes/` 的目录)。控制台扫描每个 root 下 `openspec/changes/` 的活跃变更和 `openspec/changes/archive/` 的归档变更。
|
|
77
|
+
|
|
78
|
+
## 技术栈
|
|
79
|
+
|
|
80
|
+
- **零依赖**:scan.mjs 纯 Node 内置模块,无 node_modules
|
|
81
|
+
- **单文件输出**:dashboard.html 内联全部 CSS/JS,可离线打开
|
|
82
|
+
- **Node ≥ 18**
|
|
83
|
+
|
|
84
|
+
## 文件结构
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
ims-flow-dashboard/
|
|
88
|
+
├── bin/
|
|
89
|
+
│ └── imsflow.mjs # CLI 入口
|
|
90
|
+
├── install.mjs # 命令安装器(写 profile,幂等,支持 --uninstall)
|
|
91
|
+
├── scan.mjs # 零依赖扫描器 + serve 服务
|
|
92
|
+
├── template.html # 控制台模板(单文件 SPA)
|
|
93
|
+
├── projects.example.json # 配置模板
|
|
94
|
+
└── (dashboard.html) # 生成物,gitignore
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
MIT
|
package/bin/imsflow.mjs
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const scanScript = path.join(__dirname, '..', 'scan.mjs');
|
|
8
|
+
const nodeExe = process.execPath;
|
|
9
|
+
const args = process.argv.slice(2);
|
|
10
|
+
|
|
11
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
12
|
+
console.log(`imsflow — IMS Flow 控制台
|
|
13
|
+
|
|
14
|
+
用法:
|
|
15
|
+
imsflow [serve] [过滤词] [--open] [--port=N]
|
|
16
|
+
imsflow --help
|
|
17
|
+
|
|
18
|
+
模式:
|
|
19
|
+
快照模式 (默认) 生成单文件 dashboard.html,--open 自动打开浏览器
|
|
20
|
+
服务模式 (serve) 本地服务,页面内「重新扫描」按钮生效
|
|
21
|
+
imsflow serve [--port=17771]
|
|
22
|
+
|
|
23
|
+
参数:
|
|
24
|
+
serve 启动本地服务
|
|
25
|
+
<过滤词> 按名称/路径模糊匹配工作区根
|
|
26
|
+
--open 生成后自动打开浏览器
|
|
27
|
+
--port=<N> serve 模式端口(默认 17771)
|
|
28
|
+
--help, -h 显示本帮助
|
|
29
|
+
|
|
30
|
+
配置:
|
|
31
|
+
projects.json 注册要扫描的工作区根路径(参考 projects.example.json)
|
|
32
|
+
`);
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const child = spawn(nodeExe, [scanScript, ...args], { stdio: 'inherit' });
|
|
37
|
+
child.on('exit', (code) => process.exit(code ?? 0));
|
package/bin/preview.mjs
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 生成轻量预览 preview.html
|
|
4
|
+
*
|
|
5
|
+
* 为什么需要它:dashboard.html 是全量快照(~5.3MB / 11 工作区),在 IDE 预览里很重;
|
|
6
|
+
* preview.html 是只扫演示仓库的小快照(~95KB),看设计最快。
|
|
7
|
+
*
|
|
8
|
+
* 坑(真实踩过):演示仓库不在 projects.json 里,`node scan.mjs ims-flow-demo` 会直接报
|
|
9
|
+
* 「没有匹配的根路径」,于是 preview.html 就停在了旧模板上 —— 打开它看到的是**旧设计**,
|
|
10
|
+
* 而 dashboard.html 早已更新,排查时极易误判成"改了没生效"。
|
|
11
|
+
*
|
|
12
|
+
* 用法:node bin/preview.mjs [演示仓库路径]
|
|
13
|
+
* 未给路径时,按 projects.local.json(本地私有, gitignore+不发布)→ projects.example.json
|
|
14
|
+
* 的顺序挑一个「存在且未被 projects.json 收录」的根。
|
|
15
|
+
* published 的 example 只放通用示例路径,本机演示仓库路径放 local。
|
|
16
|
+
* 生成后会重新跑一次全量扫描,把 dashboard.html 还原成全量数据。
|
|
17
|
+
*/
|
|
18
|
+
import fs from 'fs';
|
|
19
|
+
import path from 'path';
|
|
20
|
+
import { execFileSync } from 'child_process';
|
|
21
|
+
import { fileURLToPath } from 'url';
|
|
22
|
+
|
|
23
|
+
const DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
24
|
+
const CFG = path.join(DIR, 'projects.json');
|
|
25
|
+
const LOCAL = path.join(DIR, 'projects.local.json');
|
|
26
|
+
const EXAMPLE = path.join(DIR, 'projects.example.json');
|
|
27
|
+
const OUT = path.join(DIR, 'dashboard.html');
|
|
28
|
+
const PREVIEW = path.join(DIR, 'preview.html');
|
|
29
|
+
|
|
30
|
+
const readRoots = f => {
|
|
31
|
+
try {
|
|
32
|
+
const j = JSON.parse(fs.readFileSync(f, 'utf8'));
|
|
33
|
+
return Array.isArray(j.roots) ? j.roots.filter(r => typeof r === 'string' && r.trim()) : [];
|
|
34
|
+
} catch { return []; }
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const configured = readRoots(CFG);
|
|
38
|
+
|
|
39
|
+
// 1) 选一个演示根:显式参数 > projects.local.json > projects.example.json(都要求存在且未配置)
|
|
40
|
+
let demo = process.argv[2];
|
|
41
|
+
if (!demo) {
|
|
42
|
+
demo = readRoots(LOCAL).find(r => fs.existsSync(r) && !configured.includes(r))
|
|
43
|
+
|| readRoots(EXAMPLE).find(r => fs.existsSync(r) && !configured.includes(r));
|
|
44
|
+
}
|
|
45
|
+
if (!demo) {
|
|
46
|
+
console.error('未找到可用的演示仓库。用法: node bin/preview.mjs <演示仓库路径>');
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
if (!fs.existsSync(demo)) {
|
|
50
|
+
console.error(`演示仓库不存在: ${demo}`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const filter = path.basename(demo);
|
|
55
|
+
// 子进程输出先捕获再转发 —— scan.mjs 早期失败(配置读不到等)时会直接 exit(1) 且几乎不打印,
|
|
56
|
+
// 只看 status 无从判断原因, 所以这里把 stdout/stderr 原样回显出来。
|
|
57
|
+
// ⚠️ args 必须由调用方决定:还原配置之后再用 demo 过滤词跑全量, 会报「没有匹配的根路径」。
|
|
58
|
+
const exec = (args) => {
|
|
59
|
+
try {
|
|
60
|
+
const out = execFileSync(process.execPath, ['scan.mjs', ...args], {
|
|
61
|
+
cwd: DIR, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'],
|
|
62
|
+
});
|
|
63
|
+
if (out) process.stdout.write(out);
|
|
64
|
+
return;
|
|
65
|
+
} catch (e) {
|
|
66
|
+
if (e.stdout) process.stdout.write(e.stdout);
|
|
67
|
+
if (e.stderr) process.stderr.write(e.stderr);
|
|
68
|
+
throw new Error(`scan.mjs ${args.join(' ')} 退出码 ${e.status}${e.stderr ? '' : '(无 stderr 输出)'}`);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// 2) 临时挂上演示根 -> 扫 -> 存成 preview.html
|
|
73
|
+
const original = fs.readFileSync(CFG, 'utf8');
|
|
74
|
+
let failed = null;
|
|
75
|
+
try {
|
|
76
|
+
fs.writeFileSync(CFG, JSON.stringify({ roots: [...configured, demo] }, null, 2) + '\n', 'utf8');
|
|
77
|
+
console.log(`[preview] 临时挂载演示根: ${demo}`);
|
|
78
|
+
exec([filter]);
|
|
79
|
+
fs.copyFileSync(OUT, PREVIEW);
|
|
80
|
+
console.log(`[preview] 已生成 ${path.relative(DIR, PREVIEW)} (${(fs.statSync(PREVIEW).size / 1024).toFixed(0)} KB)`);
|
|
81
|
+
} catch (e) {
|
|
82
|
+
failed = e;
|
|
83
|
+
} finally {
|
|
84
|
+
// 3) 无论如何都还原配置,别把用户的 projects.json 留在脏状态
|
|
85
|
+
fs.writeFileSync(CFG, original, 'utf8');
|
|
86
|
+
console.log(`[preview] projects.json 已还原 (${configured.length} 个根)`);
|
|
87
|
+
}
|
|
88
|
+
if (failed) { console.error('[preview] 失败:', failed.message); process.exit(1); }
|
|
89
|
+
|
|
90
|
+
// 4) dashboard.html 刚被 demo 数据覆盖了,重新跑全量还原
|
|
91
|
+
console.log('[preview] 重新生成全量 dashboard.html …');
|
|
92
|
+
exec([]);
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ims-flow-dashboard",
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "IMS Flow 控制台 — 可视化 OpenSpec 变更生命周期、产物完整性与任务进度",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"files": [
|
|
7
|
+
"bin/",
|
|
8
|
+
"scan.mjs",
|
|
9
|
+
"template.html",
|
|
10
|
+
"projects.example.json"
|
|
11
|
+
],
|
|
12
|
+
"bin": {
|
|
13
|
+
"imsflow": "./bin/imsflow.mjs"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"scan": "node scan.mjs",
|
|
17
|
+
"preview": "node bin/preview.mjs",
|
|
18
|
+
"serve": "node scan.mjs serve",
|
|
19
|
+
"open": "node scan.mjs --open",
|
|
20
|
+
"setup": "node install.mjs",
|
|
21
|
+
"uninstall": "node install.mjs --uninstall",
|
|
22
|
+
"pack": "git archive --format=zip -o ims-flow-dashboard.zip HEAD"
|
|
23
|
+
},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=18"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"openspec",
|
|
29
|
+
"spec-driven",
|
|
30
|
+
"dashboard",
|
|
31
|
+
"cli",
|
|
32
|
+
"workflow-visualization"
|
|
33
|
+
],
|
|
34
|
+
"author": "booksongs",
|
|
35
|
+
"license": "MIT"
|
|
36
|
+
}
|