duaer-spec 0.27.1 → 0.27.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/.duaer/active-job.json +3 -3
- package/.duaer/specs/275-release-0271/delivery.json +9 -0
- package/.duaer/specs/275-release-0271/tasks.md +1 -1
- package/.duaer/specs/276-fde08-perf-budget/delivery.json +9 -0
- package/.duaer/specs/276-fde08-perf-budget/spec.md +38 -0
- package/.duaer/specs/276-fde08-perf-budget/tasks.md +5 -0
- package/.duaer/specs/277-project-dir-reuse/delivery.json +9 -0
- package/.duaer/specs/277-project-dir-reuse/spec.md +31 -0
- package/.duaer/specs/277-project-dir-reuse/tasks.md +5 -0
- package/.duaer/specs/278-project-create-unique/delivery.json +9 -0
- package/.duaer/specs/278-project-create-unique/spec.md +27 -0
- package/.duaer/specs/278-project-create-unique/tasks.md +5 -0
- package/.duaer/specs/279-arch-side-detour/delivery.json +8 -0
- package/.duaer/specs/279-arch-side-detour/spec.md +28 -0
- package/.duaer/specs/279-arch-side-detour/tasks.md +5 -0
- package/.duaer/specs/280-arch-bottom-via-label/delivery.json +8 -0
- package/.duaer/specs/280-arch-bottom-via-label/spec.md +22 -0
- package/.duaer/specs/280-arch-bottom-via-label/tasks.md +5 -0
- package/.duaer/specs/281-release-0272/spec.md +13 -0
- package/.duaer/specs/281-release-0272/tasks.md +4 -0
- package/CHANGELOG.md +34 -0
- package/bin/duaer-live.mjs +93 -19
- package/bin/live-archify.mjs +382 -26
- package/bin/live-deliverables.mjs +4 -0
- package/bin/live-modules.mjs +17 -0
- package/bin/live-project-chat.mjs +2 -0
- package/bin/live-repo-path.mjs +91 -2
- package/docs/agent/e2e-test-plan.md +5 -0
- package/docs/agent/live-desk.md +15 -3
- package/package.json +1 -1
- package/web/live-dev/app.js +28 -2
- package/web/live-dev/baseline.mjs +1 -0
- package/web/live-dev/confirm-card.mjs +1 -0
- package/web/live-dev/i18n-de.js +14 -8
- package/web/live-dev/i18n-es.js +14 -8
- package/web/live-dev/i18n-fr.js +14 -8
- package/web/live-dev/i18n-ja.js +14 -8
- package/web/live-dev/i18n-ko.js +14 -8
- package/web/live-dev/i18n-pt-BR.js +14 -8
- package/web/live-dev/i18n-ru.js +14 -8
- package/web/live-dev/i18n-vi.js +14 -8
- package/web/live-dev/i18n-zh-TW.js +14 -8
- package/web/live-dev/i18n.js +28 -16
- package/web/live-dev/index.html +13 -7
- package/web/live-dev/perf-budget.mjs +38 -0
package/.duaer/active-job.json
CHANGED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"status": "accepted",
|
|
3
|
+
"summary": "0.27.1 is on main as v0.27.1, GitHub Release published, npm Trusted Publishing succeeded.",
|
|
4
|
+
"verification": {
|
|
5
|
+
"publish": "git push origin main develop v0.27.1 — fast-forward 1d6d164..26ede20",
|
|
6
|
+
"release": "https://github.com/Duaer/duaer-spec/releases/tag/v0.27.1",
|
|
7
|
+
"npm": "gh run 35558574069 success — + duaer-spec@0.27.1"
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"status": "accepted",
|
|
3
|
+
"summary": "Feature confirm requires LCP, INP, bundle size, virtualized long lists, a weak-network check, and a large-data check, or a no-page-performance opt-out. A vague speed line fails. Kickoff schedules the budget before implement.",
|
|
4
|
+
"verification": {
|
|
5
|
+
"L0": "node --check bin/duaer-live.mjs bin/live-modules.mjs web/live-dev/app.js web/live-dev/perf-budget.mjs — pass",
|
|
6
|
+
"L1": "npm test — 254 pass",
|
|
7
|
+
"L3": "npm run test:live — pass"
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Feature Specification: FDE-08 performance budget
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
A feature confirm card cannot pass on a vague speed claim. It must record
|
|
6
|
+
LCP, INP, bundle size, virtualized long lists, a weak-network check, and a
|
|
7
|
+
large-data check. Kickoff schedules that budget before implementation.
|
|
8
|
+
|
|
9
|
+
## In scope
|
|
10
|
+
|
|
11
|
+
- Local validate on perfBudget
|
|
12
|
+
- One FDE-08 task per confirmed module that declares page performance,
|
|
13
|
+
placed before implement and after the env probe, data precheck, and
|
|
14
|
+
external-dependency board when those exist
|
|
15
|
+
- Hint, placeholder, and chat/accept prompts
|
|
16
|
+
- Explicit opt-out: no page performance requirement
|
|
17
|
+
|
|
18
|
+
## Out of scope
|
|
19
|
+
|
|
20
|
+
- Bug desk
|
|
21
|
+
- Running a customer performance lab from the desk
|
|
22
|
+
|
|
23
|
+
## Assumptions
|
|
24
|
+
|
|
25
|
+
- The desk records the budget; the digital employee runs the weak-network
|
|
26
|
+
and large-data checks.
|
|
27
|
+
- 「本模块无页面性能要求」 / "no page performance" skips the six items
|
|
28
|
+
and the kickoff task.
|
|
29
|
+
- "挺快的" and other vague speed lines fail.
|
|
30
|
+
|
|
31
|
+
## Acceptance
|
|
32
|
+
|
|
33
|
+
1. A vague speed line, or a line missing any of the six items, fails
|
|
34
|
+
local validate
|
|
35
|
+
2. All six present, or the no-page-performance opt-out, passes
|
|
36
|
+
3. Kickoff adds an FDE-08 task before implement when page performance is
|
|
37
|
+
declared, and skips it on the opt-out
|
|
38
|
+
4. npm test and npm run test:live pass
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"status": "accepted",
|
|
3
|
+
"summary": "Create mode refuses when the target project directory already exists. Open from the list or browse still works. Existing folder contents are left untouched.",
|
|
4
|
+
"verification": {
|
|
5
|
+
"L0": "node --check bin/live-repo-path.mjs bin/duaer-live.mjs web/live-dev/app.js — pass",
|
|
6
|
+
"L1": "npm test — pass",
|
|
7
|
+
"L3": "npm run test:live — pass"
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Feature Specification: Refuse reusing an existing project directory on create
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Creating a new project must not reuse a directory that already exists.
|
|
6
|
+
An existing folder can only be opened from the project list or by browse-open.
|
|
7
|
+
|
|
8
|
+
## In scope
|
|
9
|
+
|
|
10
|
+
- `/api/projects/activate` create mode refuses when the target path already exists
|
|
11
|
+
- Create form sends create mode; list click opens without create
|
|
12
|
+
- Clear Chinese error telling the user to rename or open the existing project
|
|
13
|
+
- Unit / smoke coverage
|
|
14
|
+
|
|
15
|
+
## Out of scope
|
|
16
|
+
|
|
17
|
+
- Deleting or renaming old project folders
|
|
18
|
+
- Changing how desk sessions are keyed
|
|
19
|
+
|
|
20
|
+
## Assumptions
|
|
21
|
+
|
|
22
|
+
- "选为当前项目" on the create form means create when the path is new
|
|
23
|
+
- Clicking an item under 「已有项目」 means open
|
|
24
|
+
- Browse into an existing folder opens it; it does not create over it
|
|
25
|
+
|
|
26
|
+
## Acceptance
|
|
27
|
+
|
|
28
|
+
1. Create with a folder name that already exists under the parent returns an error; disk contents are unchanged
|
|
29
|
+
2. Opening the same path from the project list still works
|
|
30
|
+
3. Create with a never-used name still creates the directory
|
|
31
|
+
4. `npm test` and `npm run test:live` pass
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"status": "accepted",
|
|
3
|
+
"summary": "Create uses only a short folder name under the product parent. Absolute paths, existing folders, and duplicate project titles are refused. Open from the list or browse still works.",
|
|
4
|
+
"verification": {
|
|
5
|
+
"L0": "node --check bin/live-repo-path.mjs bin/duaer-live.mjs — pass",
|
|
6
|
+
"L1": "npm test — pass",
|
|
7
|
+
"L3": "npm run test:live — pass"
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Feature Specification: Unique create — short name under parent only
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Create and open are separate. Creating a project only uses a short folder
|
|
6
|
+
name under the product parent. Absolute paths, duplicate folders, and
|
|
7
|
+
duplicate project titles are refused.
|
|
8
|
+
|
|
9
|
+
## In scope
|
|
10
|
+
|
|
11
|
+
- Create mode: short name only, must have parent set, refuse if folder exists
|
|
12
|
+
- Create mode: refuse if an existing project already uses that path or title
|
|
13
|
+
- Open from list / browse still accepts absolute paths
|
|
14
|
+
- Copy and labels match the rule
|
|
15
|
+
|
|
16
|
+
## Out of scope
|
|
17
|
+
|
|
18
|
+
- Moving or renaming existing project folders on disk
|
|
19
|
+
- Deleting stale list entries
|
|
20
|
+
|
|
21
|
+
## Acceptance
|
|
22
|
+
|
|
23
|
+
1. Create with `/Users/.../pcdemo` fails; asks for a short name under the parent
|
|
24
|
+
2. Create with a short name that already exists on disk or in the project list fails
|
|
25
|
+
3. Create with a title already used by another project fails
|
|
26
|
+
4. Open from the list still works with absolute paths
|
|
27
|
+
5. `npm test` and `npm run test:live` pass
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"status": "accepted",
|
|
3
|
+
"summary": "Corridor-aware Archify edge detours (validated bottom/top/side/gutter) plus vertical labelAt so stacked clean-flow IR renders.",
|
|
4
|
+
"verification": {
|
|
5
|
+
"commands": ["node --test test/live-archify.test.mjs"],
|
|
6
|
+
"result": "16 pass / 0 fail"
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Feature Specification: Architecture corridor detour for clean-flow
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
When Archify rejects edges that cross unrelated components, auto-routing
|
|
6
|
+
must pick a corridor whose segments stay clear of other boxes — not a
|
|
7
|
+
naive bottom exit through column neighbors, and not a side via whose
|
|
8
|
+
horizontal leg clips co-row nodes. Vertical edge labels must sit in the
|
|
9
|
+
gap between stacked boxes.
|
|
10
|
+
|
|
11
|
+
## In scope
|
|
12
|
+
|
|
13
|
+
- `routeCrossingEdges` validates candidate routes (bottom/top wrap, side
|
|
14
|
+
corridor, gutter wrap) and applies the first clear detour
|
|
15
|
+
- Re-route edges that already have a bad bottom via
|
|
16
|
+
- Vertical edge labels get `labelAt` in the inter-box gap
|
|
17
|
+
- Regression test matching the speaker/editor/store failure
|
|
18
|
+
|
|
19
|
+
## Out of scope
|
|
20
|
+
|
|
21
|
+
- Changing Archify itself
|
|
22
|
+
- Full force-directed relayout
|
|
23
|
+
|
|
24
|
+
## Acceptance
|
|
25
|
+
|
|
26
|
+
1. The reproduced IR (speaker→editor, store→gen, 初稿/定稿 labels) renders
|
|
27
|
+
2. Existing same-row reverse-edge test still passes
|
|
28
|
+
3. `npm test` live-archify tests pass
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"status": "accepted",
|
|
3
|
+
"summary": "Bottom/top via edge labels pin labelAt on the via corridor so 改稿 no longer overlaps artifact.",
|
|
4
|
+
"verification": {
|
|
5
|
+
"commands": ["node --check bin/live-archify.mjs", "node --test test/live-archify.test.mjs"],
|
|
6
|
+
"result": "17 pass / 0 fail"
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Feature Specification: Bottom-via edge labels clear mid-row boxes
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
When a same-row edge detours under the floor, its label must stay on the
|
|
6
|
+
via (below) — not be pulled up with `labelDy: -36` onto mid-row components
|
|
7
|
+
such as `artifact`.
|
|
8
|
+
|
|
9
|
+
## In scope
|
|
10
|
+
|
|
11
|
+
- `repairEdgeLabelPlacement` pins `labelAt` under bottom vias / above top vias
|
|
12
|
+
- Overlap nudge when an offset mid still hits a box
|
|
13
|
+
- Regression for speaker→revise「改稿」 overlapping artifact
|
|
14
|
+
|
|
15
|
+
## Out of scope
|
|
16
|
+
|
|
17
|
+
- Changing Archify itself
|
|
18
|
+
|
|
19
|
+
## Acceptance
|
|
20
|
+
|
|
21
|
+
1. Reproduced IR renders via Archify
|
|
22
|
+
2. `node --test test/live-archify.test.mjs` passes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Feature Specification: Release 0.27.2
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Ship 0.27.2: architecture clean-flow corridor detours, bottom-via label
|
|
6
|
+
placement, unique project create paths, and FDE-08 performance budget.
|
|
7
|
+
Promote develop to main, publish a GitHub Release, and let npm Trusted
|
|
8
|
+
Publishing run.
|
|
9
|
+
|
|
10
|
+
## Acceptance
|
|
11
|
+
|
|
12
|
+
1. `package.json` is `0.27.2`; CHANGELOG has `0.27.2` and an empty Unreleased section.
|
|
13
|
+
2. Tag `v0.27.2` is on `main`; GitHub Release is published.
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,40 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.27.2 — 2026-09-23
|
|
6
|
+
|
|
7
|
+
### Fix: bottom-via edge labels stay under the floor
|
|
8
|
+
|
|
9
|
+
- Bottom/top via labels use `labelAt` on the via corridor instead of
|
|
10
|
+
`labelDy: -36`, which pulled「改稿」up onto mid-row boxes like `artifact`
|
|
11
|
+
|
|
12
|
+
### Fix: architecture corridor detour when edges cross stacked nodes
|
|
13
|
+
|
|
14
|
+
- Auto-routing validates candidate corridors (bottom/top wrap, side, gutter)
|
|
15
|
+
so detours stay clear of unrelated boxes — not only avoid bottom exits
|
|
16
|
+
- Vertical edge labels use `labelAt` in the inter-box gap
|
|
17
|
+
- Fixes Archify `clean-flow/edge-through-node` and label-overlap failures on stacked layouts
|
|
18
|
+
|
|
19
|
+
### Fix: create project uses a unique short name under the parent only
|
|
20
|
+
|
|
21
|
+
- Create form no longer accepts absolute paths; folder name is a short name under the product parent
|
|
22
|
+
- Duplicate on-disk folders and duplicate project titles are refused
|
|
23
|
+
- Open existing projects from the list or Browse to open; create and open are separate
|
|
24
|
+
|
|
25
|
+
### Fix: creating a project no longer reuses an existing directory
|
|
26
|
+
|
|
27
|
+
- Create mode refuses when the target folder already exists on disk
|
|
28
|
+
- Open an existing project from the list or browse; do not create over it
|
|
29
|
+
- Hint copy tells the operator to rename or open the existing project
|
|
30
|
+
|
|
31
|
+
### Feat: FDE-08 performance budget before confirm
|
|
32
|
+
|
|
33
|
+
- Feature confirm must name LCP, INP, bundle size, virtualized long lists, a weak-network check, and a large-data check
|
|
34
|
+
- A vague speed line such as "pretty fast" fails local validate
|
|
35
|
+
- Modules with no page performance requirement may write that opt-out
|
|
36
|
+
- Kickoff schedules the budget before implement, after the environment probe, data precheck, and external-dependency board when those exist
|
|
37
|
+
- The desk records the budget; it does not run a customer performance lab
|
|
38
|
+
|
|
5
39
|
## 0.27.1 — 2026-09-21
|
|
6
40
|
|
|
7
41
|
### Feat: FDE-05 external dependency board before kickoff
|
package/bin/duaer-live.mjs
CHANGED
|
@@ -67,13 +67,20 @@ import {
|
|
|
67
67
|
externalDepsDeclaresNone,
|
|
68
68
|
missingExternalDeps,
|
|
69
69
|
} from "../web/live-dev/external-deps.mjs";
|
|
70
|
+
import {
|
|
71
|
+
missingPerfBudget,
|
|
72
|
+
perfBudgetDeclaresNone,
|
|
73
|
+
} from "../web/live-dev/perf-budget.mjs";
|
|
70
74
|
import { allocateUniqueBranch } from "./live-worktree-name.mjs";
|
|
71
75
|
import {
|
|
72
76
|
ensureGitInstalled,
|
|
73
77
|
CURSOR_INSTALL_CMD as TOOLING_CURSOR_INSTALL,
|
|
74
78
|
} from "./live-tooling.mjs";
|
|
75
79
|
import {
|
|
80
|
+
assertProductDirFreeForCreate,
|
|
81
|
+
assertProjectIdentityFreeForCreate,
|
|
76
82
|
ensureProductDir,
|
|
83
|
+
resolveCreateProductRepoPath,
|
|
77
84
|
resolveProductRepoPath,
|
|
78
85
|
} from "./live-repo-path.mjs";
|
|
79
86
|
import {
|
|
@@ -758,6 +765,7 @@ const SYSTEM_PROMPT = `你是「Duaer-spec FDE」需求助手。通过多轮对
|
|
|
758
765
|
- envChecklist = 环境检查:DNS、TLS、CORS、鉴权、第三方可达须全部写明且通过;无客户联调则写「无客户联调环境」。任一未通过不得开工
|
|
759
766
|
- dataPrecheck = 数据预检:字段映射、导入预检失败清单、可导出给业务清洗;无导入则写「本模块无导入」
|
|
760
767
|
- externalDeps = 外部依赖:阻塞项、SLA、备用 Mock、并行路径;无第三方/ERP/SSO 则写「本模块无外部依赖」
|
|
768
|
+
- perfBudget = 性能预算:LCP、INP、包体、长列表虚拟滚动、弱网、大数据压测;写「挺快的」不合格;无页面则写「本模块无页面性能要求」
|
|
761
769
|
用户没提时:先根据产品形态填合理默认并写进 JSON,再在正文用一句话请用户改;不要长期留空。小项目可只有一个模块(id=main)。
|
|
762
770
|
4. acceptance 必须可客观检查(打开何处、看到什么、哪条命令通过);禁止只写「更好用/更好看」。
|
|
763
771
|
5. ready=true 只表示**当前 active 模块**可确认,且上述字段都已非空、验收可检查。不是整系统开工。不要催用户立刻派工。
|
|
@@ -767,7 +775,7 @@ const SYSTEM_PROMPT = `你是「Duaer-spec FDE」需求助手。通过多轮对
|
|
|
767
775
|
- 先写对用户说的纯文本(可多行,不要 JSON;正文里不要再列一遍选项清单)
|
|
768
776
|
- 然后单独一行:<<<JSON>>>
|
|
769
777
|
- 再输出一个 JSON 对象(不要 markdown 围栏):
|
|
770
|
-
{"modules":[{"id":"auth","title":"登录","status":"draft","goal":"...","outOfScope":"...","acceptance":"...","assumptions":"...","deviceMatrix":"...","criticalPaths":"...","exceptionCases":"...","apiContract":"...","envChecklist":"...","dataPrecheck":"...","externalDeps":"..."}],"activeModuleId":"auth","goal":"...","outOfScope":"...","acceptance":"...","assumptions":"...","deviceMatrix":"...","criticalPaths":"...","exceptionCases":"...","apiContract":"...","envChecklist":"...","dataPrecheck":"...","externalDeps":"...","ready":false,"options":["可选A","可选B"]}
|
|
778
|
+
{"modules":[{"id":"auth","title":"登录","status":"draft","goal":"...","outOfScope":"...","acceptance":"...","assumptions":"...","deviceMatrix":"...","criticalPaths":"...","exceptionCases":"...","apiContract":"...","envChecklist":"...","dataPrecheck":"...","externalDeps":"...","perfBudget":"..."}],"activeModuleId":"auth","goal":"...","outOfScope":"...","acceptance":"...","assumptions":"...","deviceMatrix":"...","criticalPaths":"...","exceptionCases":"...","apiContract":"...","envChecklist":"...","dataPrecheck":"...","externalDeps":"...","perfBudget":"...","ready":false,"options":["可选A","可选B"]}
|
|
771
779
|
说明:顶层字段与 ready 对应 activeModuleId 那一模块;modules 为完整清单(可增删改名)。`;
|
|
772
780
|
|
|
773
781
|
const BUG_CHAT_PROMPT = `你是「Duaer-spec FDE」缺陷助手。用户要修 bug,不是做新功能。通过多轮对话整理成一张可派工的缺陷卡,使数字员工能复现、修复并回归。
|
|
@@ -805,6 +813,7 @@ const REVISE_CHAT_PROMPT = `你是「Duaer-spec FDE」改进对话助手。用
|
|
|
805
813
|
- envChecklist = 若触及客户环境则更新探测结果,或写「无客户联调环境」/「同基线」
|
|
806
814
|
- dataPrecheck = 若触及导入则更新映射与失败清单,或写「本模块无导入」/「同基线」
|
|
807
815
|
- externalDeps = 若触及第三方/ERP/SSO 则更新阻塞项、SLA、备用 Mock、并行路径,或写「本模块无外部依赖」/「同基线」
|
|
816
|
+
- perfBudget = 若有页面则更新 LCP、INP、包体、长列表虚拟滚动、弱网、大数据压测,或写「本模块无页面性能要求」/「同基线」
|
|
808
817
|
3. 字段够清楚且可执行时 ready=true(确认前系统会自动校验)。缺基线/契约字段时先填合理默认或用 options 点选。
|
|
809
818
|
4. 不要写代码。不要立刻派工。不要假设仓库路径。
|
|
810
819
|
5. 只要问题是让用户做选择,必须在 options 填 2~5 个短选项(≤20字);界面可点选发送。禁止只让用户手打或「请回复数字」。
|
|
@@ -812,7 +821,7 @@ const REVISE_CHAT_PROMPT = `你是「Duaer-spec FDE」改进对话助手。用
|
|
|
812
821
|
- 先写对用户说的纯文本(正文不要再列选项清单)
|
|
813
822
|
- 然后单独一行:<<<JSON>>>
|
|
814
823
|
- 再输出 JSON(不要 markdown 围栏):
|
|
815
|
-
{"goal":"...","outOfScope":"...","acceptance":"...","assumptions":"...","deviceMatrix":"...","criticalPaths":"...","exceptionCases":"...","apiContract":"...","envChecklist":"...","dataPrecheck":"...","externalDeps":"...","ready":false,"options":["可选A","可选B"]}`;
|
|
824
|
+
{"goal":"...","outOfScope":"...","acceptance":"...","assumptions":"...","deviceMatrix":"...","criticalPaths":"...","exceptionCases":"...","apiContract":"...","envChecklist":"...","dataPrecheck":"...","externalDeps":"...","perfBudget":"...","ready":false,"options":["可选A","可选B"]}`;
|
|
816
825
|
|
|
817
826
|
const ARCHITECTURE_CHAT_PROMPT = `你是「Duaer-spec FDE」架构助手。需求已确认。通过多轮对话设计系统架构图,供数字员工按图开发。系统会把 JSON 自动渲染成图,用户看不到原始 JSON。
|
|
818
827
|
|
|
@@ -843,23 +852,24 @@ const ACCEPT_PROMPT = `你是「Duaer-spec FDE」需求验收官。用户即将
|
|
|
843
852
|
7. envChecklist 是否已填:DNS、TLS、CORS、鉴权、第三方可达全部通过,或明确「无客户联调环境」;存在未通过项则 passed=false
|
|
844
853
|
8. dataPrecheck 是否已填:字段映射、导入预检失败清单、可导出,或明确「本模块无导入」
|
|
845
854
|
9. externalDeps 是否已填:阻塞项、SLA、备用 Mock、并行路径,或明确「本模块无外部依赖」
|
|
846
|
-
10.
|
|
855
|
+
10. perfBudget 是否已填:LCP、INP、包体、长列表虚拟滚动、弱网、大数据压测,或明确「本模块无页面性能要求」;禁止只写「挺快的」
|
|
856
|
+
11. 按该验收标准做完后,用户是否有理由满意(成品可核对,而非过程叙事)
|
|
847
857
|
|
|
848
858
|
规则:
|
|
849
859
|
- 若小改即可通过:修订确认卡字段(尤其把 acceptance 改成可检查句子,并补全基线与契约),passed=true
|
|
850
860
|
- 若缺关键信息:passed=false,issues 列出缺什么(中文,短句)
|
|
851
861
|
- 不要写代码。不要假设仓库路径。不要催派工。
|
|
852
862
|
- 只输出一个 JSON,不要 markdown 围栏:
|
|
853
|
-
{"passed":false,"summary":"一句话结论","issues":["问题1"],"goal":"...","outOfScope":"...","acceptance":"...","assumptions":"...","deviceMatrix":"...","criticalPaths":"...","exceptionCases":"...","apiContract":"...","envChecklist":"...","dataPrecheck":"...","externalDeps":"..."}`;
|
|
863
|
+
{"passed":false,"summary":"一句话结论","issues":["问题1"],"goal":"...","outOfScope":"...","acceptance":"...","assumptions":"...","deviceMatrix":"...","criticalPaths":"...","exceptionCases":"...","apiContract":"...","envChecklist":"...","dataPrecheck":"...","externalDeps":"...","perfBudget":"..."}`;
|
|
854
864
|
|
|
855
|
-
const FIX_ACCEPT_PROMPT = `你是「Duaer-spec FDE」需求修正助手。自动验收未通过,请根据 issues 修订确认卡。优先把 acceptance 改成可客观检查的句子(打开何处、看到什么、哪条命令通过),并补全 deviceMatrix / criticalPaths / exceptionCases / apiContract / envChecklist / dataPrecheck / externalDeps;不要编造用户没提过的大功能。
|
|
865
|
+
const FIX_ACCEPT_PROMPT = `你是「Duaer-spec FDE」需求修正助手。自动验收未通过,请根据 issues 修订确认卡。优先把 acceptance 改成可客观检查的句子(打开何处、看到什么、哪条命令通过),并补全 deviceMatrix / criticalPaths / exceptionCases / apiContract / envChecklist / dataPrecheck / externalDeps / perfBudget;不要编造用户没提过的大功能。
|
|
856
866
|
|
|
857
867
|
规则:
|
|
858
|
-
1. 针对每条 issue 修改 goal / outOfScope / acceptance / assumptions / deviceMatrix / criticalPaths / exceptionCases / apiContract / envChecklist / dataPrecheck / externalDeps
|
|
859
|
-
2. 保持用户原意;缺信息时写合理、可检查的默认(如 Chrome 最近两版、手机 Safari,云测截图放 compat/,旧壳降级提示升级;主路径对齐 goal;异常态须同时写上空态、失败、权限不足、超时、重试,有接口则补错误码;无接口则写「本模块无 HTTP API」;无客户联调则 envChecklist 写「无客户联调环境」,否则 DNS、TLS、CORS、鉴权、第三方可达全部写通过;无导入则 dataPrecheck 写「本模块无导入」,否则写字段映射、导入预检失败清单、可导出;无外部依赖则 externalDeps 写「本模块无外部依赖」,否则写阻塞项、SLA、备用 Mock
|
|
868
|
+
1. 针对每条 issue 修改 goal / outOfScope / acceptance / assumptions / deviceMatrix / criticalPaths / exceptionCases / apiContract / envChecklist / dataPrecheck / externalDeps / perfBudget
|
|
869
|
+
2. 保持用户原意;缺信息时写合理、可检查的默认(如 Chrome 最近两版、手机 Safari,云测截图放 compat/,旧壳降级提示升级;主路径对齐 goal;异常态须同时写上空态、失败、权限不足、超时、重试,有接口则补错误码;无接口则写「本模块无 HTTP API」;无客户联调则 envChecklist 写「无客户联调环境」,否则 DNS、TLS、CORS、鉴权、第三方可达全部写通过;无导入则 dataPrecheck 写「本模块无导入」,否则写字段映射、导入预检失败清单、可导出;无外部依赖则 externalDeps 写「本模块无外部依赖」,否则写阻塞项、SLA、备用 Mock、并行路径;无页面则 perfBudget 写「本模块无页面性能要求」,否则写 LCP、INP、包体、长列表虚拟滚动、弱网、大数据压测,禁止只写「挺快的」),并写进 assumptions
|
|
860
870
|
3. 不要写代码。不要假设仓库路径。
|
|
861
871
|
4. 只输出一个 JSON,不要 markdown 围栏:
|
|
862
|
-
{"summary":"一句话说明改了什么","goal":"...","outOfScope":"...","acceptance":"...","assumptions":"...","deviceMatrix":"...","criticalPaths":"...","exceptionCases":"...","apiContract":"...","envChecklist":"...","dataPrecheck":"...","externalDeps":"..."}`;
|
|
872
|
+
{"summary":"一句话说明改了什么","goal":"...","outOfScope":"...","acceptance":"...","assumptions":"...","deviceMatrix":"...","criticalPaths":"...","exceptionCases":"...","apiContract":"...","envChecklist":"...","dataPrecheck":"...","externalDeps":"...","perfBudget":"..."}`;
|
|
863
873
|
|
|
864
874
|
const BUG_ACCEPT_PROMPT = `你是「Duaer-spec FDE」缺陷验收官。用户即将锁定缺陷卡(尚未派工)。目标是:规范缺陷描述,使数字员工能复现、修复并回归。
|
|
865
875
|
|
|
@@ -1138,6 +1148,7 @@ function parseChatResult(content) {
|
|
|
1138
1148
|
envChecklist: flat(m.envChecklist ?? m.card?.envChecklist),
|
|
1139
1149
|
dataPrecheck: flat(m.dataPrecheck ?? m.card?.dataPrecheck),
|
|
1140
1150
|
externalDeps: flat(m.externalDeps ?? m.card?.externalDeps),
|
|
1151
|
+
perfBudget: flat(m.perfBudget ?? m.card?.perfBudget),
|
|
1141
1152
|
dependsOn: Array.isArray(m.dependsOn)
|
|
1142
1153
|
? m.dependsOn.map((x) => flat(x)).filter(Boolean).slice(0, 20)
|
|
1143
1154
|
: [],
|
|
@@ -1159,6 +1170,7 @@ function parseChatResult(content) {
|
|
|
1159
1170
|
envChecklist: flat(obj.envChecklist),
|
|
1160
1171
|
dataPrecheck: flat(obj.dataPrecheck),
|
|
1161
1172
|
externalDeps: flat(obj.externalDeps),
|
|
1173
|
+
perfBudget: flat(obj.perfBudget),
|
|
1162
1174
|
ready: Boolean(obj.ready),
|
|
1163
1175
|
activeModuleId: flat(obj.activeModuleId) || undefined,
|
|
1164
1176
|
modules,
|
|
@@ -1184,6 +1196,7 @@ function parseChatResult(content) {
|
|
|
1184
1196
|
envChecklist: "",
|
|
1185
1197
|
dataPrecheck: "",
|
|
1186
1198
|
externalDeps: "",
|
|
1199
|
+
perfBudget: "",
|
|
1187
1200
|
ready: false,
|
|
1188
1201
|
options: [],
|
|
1189
1202
|
};
|
|
@@ -1206,6 +1219,7 @@ function chatDoneSsePayload(parsed, { includeJsonBlock = true } = {}) {
|
|
|
1206
1219
|
envChecklist: String(parsed?.envChecklist || ""),
|
|
1207
1220
|
dataPrecheck: String(parsed?.dataPrecheck || ""),
|
|
1208
1221
|
externalDeps: String(parsed?.externalDeps || ""),
|
|
1222
|
+
perfBudget: String(parsed?.perfBudget || ""),
|
|
1209
1223
|
ready: Boolean(parsed?.ready),
|
|
1210
1224
|
activeModuleId: parsed?.activeModuleId
|
|
1211
1225
|
? String(parsed.activeModuleId).slice(0, 80)
|
|
@@ -1226,6 +1240,7 @@ function chatDoneSsePayload(parsed, { includeJsonBlock = true } = {}) {
|
|
|
1226
1240
|
envChecklist: String(m.envChecklist || "").slice(0, 4000),
|
|
1227
1241
|
dataPrecheck: String(m.dataPrecheck || "").slice(0, 4000),
|
|
1228
1242
|
externalDeps: String(m.externalDeps || "").slice(0, 4000),
|
|
1243
|
+
perfBudget: String(m.perfBudget || "").slice(0, 4000),
|
|
1229
1244
|
dependsOn: Array.isArray(m.dependsOn)
|
|
1230
1245
|
? m.dependsOn.map(String).slice(0, 20)
|
|
1231
1246
|
: [],
|
|
@@ -1392,6 +1407,7 @@ function parseAcceptResult(content, fallback) {
|
|
|
1392
1407
|
envChecklist: pick("envChecklist"),
|
|
1393
1408
|
dataPrecheck: pick("dataPrecheck"),
|
|
1394
1409
|
externalDeps: pick("externalDeps"),
|
|
1410
|
+
perfBudget: pick("perfBudget"),
|
|
1395
1411
|
};
|
|
1396
1412
|
}
|
|
1397
1413
|
|
|
@@ -1504,6 +1520,18 @@ function localAcceptCheck(card, { kind = "feature" } = {}) {
|
|
|
1504
1520
|
: "「外部依赖」必填(阻塞项、SLA、备用 Mock、并行路径,或写「本模块无外部依赖」)",
|
|
1505
1521
|
);
|
|
1506
1522
|
}
|
|
1523
|
+
const perfBudget = String(card.perfBudget || "").trim();
|
|
1524
|
+
if (
|
|
1525
|
+
!perfBudgetDeclaresNone(perfBudget) &&
|
|
1526
|
+
(perfBudget.length < 4 || missingPerfBudget(perfBudget).length)
|
|
1527
|
+
) {
|
|
1528
|
+
const missing = missingPerfBudget(perfBudget);
|
|
1529
|
+
issues.push(
|
|
1530
|
+
missing.length
|
|
1531
|
+
? `「性能预算」未写清:${missing.join("、")}(须含 LCP、INP、包体、长列表、弱网、大数据压测,或写「本模块无页面性能要求」)`
|
|
1532
|
+
: "「性能预算」必填(LCP、INP、包体、长列表、弱网、大数据压测,或写「本模块无页面性能要求」)",
|
|
1533
|
+
);
|
|
1534
|
+
}
|
|
1507
1535
|
}
|
|
1508
1536
|
return issues;
|
|
1509
1537
|
}
|
|
@@ -1526,6 +1554,7 @@ async function autoAcceptCard(cfg, card, { kind = "feature", projectContext = nu
|
|
|
1526
1554
|
envChecklist: card.envChecklist,
|
|
1527
1555
|
dataPrecheck: card.dataPrecheck,
|
|
1528
1556
|
externalDeps: card.externalDeps,
|
|
1557
|
+
perfBudget: card.perfBudget,
|
|
1529
1558
|
};
|
|
1530
1559
|
}
|
|
1531
1560
|
const ctxNote =
|
|
@@ -1559,6 +1588,7 @@ async function validateCardOnly(cfg, card, { kind = "feature", projectContext =
|
|
|
1559
1588
|
envChecklist: String(card.envChecklist || "").trim(),
|
|
1560
1589
|
dataPrecheck: String(card.dataPrecheck || "").trim(),
|
|
1561
1590
|
externalDeps: String(card.externalDeps || "").trim(),
|
|
1591
|
+
perfBudget: String(card.perfBudget || "").trim(),
|
|
1562
1592
|
};
|
|
1563
1593
|
if (kind === "bug" && projectContext) {
|
|
1564
1594
|
normalized = {
|
|
@@ -1620,6 +1650,7 @@ async function validateCardOnly(cfg, card, { kind = "feature", projectContext =
|
|
|
1620
1650
|
envChecklist: review.envChecklist || normalized.envChecklist,
|
|
1621
1651
|
dataPrecheck: review.dataPrecheck || normalized.dataPrecheck,
|
|
1622
1652
|
externalDeps: review.externalDeps || normalized.externalDeps,
|
|
1653
|
+
perfBudget: review.perfBudget || normalized.perfBudget,
|
|
1623
1654
|
};
|
|
1624
1655
|
}
|
|
1625
1656
|
|
|
@@ -1670,6 +1701,7 @@ async function autoFixConfirmCard(cfg, card, issues, { kind = "feature", project
|
|
|
1670
1701
|
envChecklist: String(obj.envChecklist || card.envChecklist || "").trim(),
|
|
1671
1702
|
dataPrecheck: String(obj.dataPrecheck || card.dataPrecheck || "").trim(),
|
|
1672
1703
|
externalDeps: String(obj.externalDeps || card.externalDeps || "").trim(),
|
|
1704
|
+
perfBudget: String(obj.perfBudget || card.perfBudget || "").trim(),
|
|
1673
1705
|
};
|
|
1674
1706
|
}
|
|
1675
1707
|
|
|
@@ -2516,20 +2548,53 @@ function activateProject(body = {}) {
|
|
|
2516
2548
|
const cfg = readConfig();
|
|
2517
2549
|
let raw = String(body.path || body.name || "").trim();
|
|
2518
2550
|
if (!raw) {
|
|
2519
|
-
const e = new Error(
|
|
2551
|
+
const e = new Error(
|
|
2552
|
+
body.create === true || body.mode === "create"
|
|
2553
|
+
? "请填写要新建的目录短名"
|
|
2554
|
+
: "请填写目录名或绝对路径",
|
|
2555
|
+
);
|
|
2520
2556
|
e.code = "EMPTY_PATH";
|
|
2521
2557
|
throw e;
|
|
2522
2558
|
}
|
|
2523
2559
|
const titleIn = String(body.title || "").trim();
|
|
2524
2560
|
const descriptionIn = String(body.description || "").trim();
|
|
2525
|
-
const
|
|
2526
|
-
|
|
2527
|
-
|
|
2561
|
+
const create = body.create === true || body.mode === "create";
|
|
2562
|
+
if (create) {
|
|
2563
|
+
if (!titleIn) {
|
|
2564
|
+
const e = new Error("请填写项目名称");
|
|
2565
|
+
e.code = "NEED_TITLE";
|
|
2566
|
+
throw e;
|
|
2567
|
+
}
|
|
2568
|
+
if (!descriptionIn) {
|
|
2569
|
+
const e = new Error("请填写简单的背景描述");
|
|
2570
|
+
e.code = "NEED_DESCRIPTION";
|
|
2571
|
+
throw e;
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
const abs = create
|
|
2575
|
+
? resolveCreateProductRepoPath(raw, {
|
|
2576
|
+
projectsRoot: cfg.projectsRoot,
|
|
2577
|
+
})
|
|
2578
|
+
: resolveProductRepoPath(raw, {
|
|
2579
|
+
projectsRoot: cfg.projectsRoot,
|
|
2580
|
+
});
|
|
2581
|
+
if (create) {
|
|
2582
|
+
assertProductDirFreeForCreate(abs);
|
|
2583
|
+
assertProjectIdentityFreeForCreate(
|
|
2584
|
+
{ path: abs, title: titleIn },
|
|
2585
|
+
readRepos(),
|
|
2586
|
+
);
|
|
2587
|
+
} else if (!fs.existsSync(abs)) {
|
|
2588
|
+
const e = new Error(`项目目录不存在:${abs}`);
|
|
2589
|
+
e.code = "PATH_MISSING";
|
|
2590
|
+
e.path = abs;
|
|
2591
|
+
throw e;
|
|
2592
|
+
}
|
|
2528
2593
|
const ensured = ensureProductDir(abs);
|
|
2529
2594
|
const existing = readRepos().find(
|
|
2530
2595
|
(r) => normalizeRepoPath(r.path) === normalizeRepoPath(abs),
|
|
2531
2596
|
);
|
|
2532
|
-
const isNew = Boolean(ensured.created) || !existing;
|
|
2597
|
+
const isNew = Boolean(ensured.created) || (create && !existing);
|
|
2533
2598
|
const needMeta = Boolean(body.requireMeta) || isNew;
|
|
2534
2599
|
if (needMeta && !titleIn) {
|
|
2535
2600
|
const e = new Error("请填写项目名称");
|
|
@@ -2546,12 +2611,17 @@ function activateProject(body = {}) {
|
|
|
2546
2611
|
exact: true,
|
|
2547
2612
|
ensureDuaer: false,
|
|
2548
2613
|
});
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2614
|
+
// Opening an existing project keeps prior title/description unless the
|
|
2615
|
+
// caller sends non-empty replacements. Create always writes the new meta.
|
|
2616
|
+
const savedTitle = create
|
|
2617
|
+
? titleIn || path.basename(probe.path)
|
|
2618
|
+
: titleIn ||
|
|
2619
|
+
existing?.title ||
|
|
2620
|
+
existing?.name ||
|
|
2621
|
+
path.basename(probe.path);
|
|
2622
|
+
const savedDesc = create
|
|
2623
|
+
? descriptionIn
|
|
2624
|
+
: descriptionIn || existing?.description || "";
|
|
2555
2625
|
rememberRepo(probe.path, {
|
|
2556
2626
|
title: savedTitle,
|
|
2557
2627
|
name: savedTitle,
|
|
@@ -6485,6 +6555,7 @@ async function handleApi(req, res) {
|
|
|
6485
6555
|
envChecklist: body.envChecklist,
|
|
6486
6556
|
dataPrecheck: body.dataPrecheck,
|
|
6487
6557
|
externalDeps: body.externalDeps,
|
|
6558
|
+
perfBudget: body.perfBudget,
|
|
6488
6559
|
});
|
|
6489
6560
|
const deskKind =
|
|
6490
6561
|
String(body.deskKind || body.kind || "").trim() === "bug"
|
|
@@ -6541,6 +6612,7 @@ async function handleApi(req, res) {
|
|
|
6541
6612
|
envChecklist: body.envChecklist,
|
|
6542
6613
|
dataPrecheck: body.dataPrecheck,
|
|
6543
6614
|
externalDeps: body.externalDeps,
|
|
6615
|
+
perfBudget: body.perfBudget,
|
|
6544
6616
|
});
|
|
6545
6617
|
const issues = Array.isArray(body.issues) ? body.issues : [];
|
|
6546
6618
|
const deskKind =
|
|
@@ -6605,6 +6677,7 @@ async function handleApi(req, res) {
|
|
|
6605
6677
|
envChecklist: body.envChecklist,
|
|
6606
6678
|
dataPrecheck: body.dataPrecheck,
|
|
6607
6679
|
externalDeps: body.externalDeps,
|
|
6680
|
+
perfBudget: body.perfBudget,
|
|
6608
6681
|
});
|
|
6609
6682
|
if (!card.goal || !card.acceptance) {
|
|
6610
6683
|
send(res, 400, { error: "goal and acceptance are required" });
|
|
@@ -6717,6 +6790,7 @@ async function handleApi(req, res) {
|
|
|
6717
6790
|
envChecklist: body.envChecklist,
|
|
6718
6791
|
dataPrecheck: body.dataPrecheck,
|
|
6719
6792
|
externalDeps: body.externalDeps,
|
|
6793
|
+
perfBudget: body.perfBudget,
|
|
6720
6794
|
});
|
|
6721
6795
|
const issues = Array.isArray(body.issues) ? body.issues : [];
|
|
6722
6796
|
if (!card.goal && !card.acceptance) {
|