duaer-spec 0.21.0 → 0.22.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/.claude/rules/duaer-spec.md +5 -2
- package/.cursor/rules/duaer-spec.mdc +5 -2
- package/.cursor/skills/duaer-do/SKILL.md +7 -1
- package/.cursor/skills/duaer-tasks/SKILL.md +8 -0
- package/.duaer/active-job.json +3 -3
- package/.duaer/memory/constitution.md +13 -0
- package/.duaer/specs/202-release-021/delivery.json +18 -0
- package/.duaer/specs/202-release-021/tasks.md +2 -2
- package/.duaer/specs/203-atomic-monitorable-tasks/delivery.json +13 -0
- package/.duaer/specs/203-atomic-monitorable-tasks/spec.md +44 -0
- package/.duaer/specs/203-atomic-monitorable-tasks/tasks.md +8 -0
- package/.duaer/specs/204-live-i18n-ja/delivery.json +14 -0
- package/.duaer/specs/204-live-i18n-ja/spec.md +34 -0
- package/.duaer/specs/204-live-i18n-ja/tasks.md +7 -0
- package/.duaer/specs/205-release-022/spec.md +12 -0
- package/.duaer/specs/205-release-022/tasks.md +6 -0
- package/.duaer/templates/tasks-template.md +4 -0
- package/CHANGELOG.md +14 -0
- package/bin/duaer-live.mjs +7 -4
- package/bin/live-deliverables.mjs +116 -36
- package/bin/live-modules.mjs +37 -7
- package/bin/live-progress.mjs +7 -38
- package/docs/agent/e2e-test-plan.md +5 -1
- package/docs/agent/live-desk.md +4 -1
- package/package.json +1 -1
- package/web/live-dev/acceptance-lines.mjs +42 -0
- package/web/live-dev/app.js +24 -5
- package/web/live-dev/i18n-ja.js +483 -0
- package/web/live-dev/i18n.js +6 -3
- package/web/live-dev/index.html +1 -0
- package/web/live-dev/task-graph.mjs +28 -7
|
@@ -28,9 +28,12 @@ For new behavior, fixes, refactors that change product behavior, or architecture
|
|
|
28
28
|
3. Do **not** wait for `/duaer-do` or any other skill invocation.
|
|
29
29
|
4. Do **not** say “please run `/duaer-specify`” — you do it.
|
|
30
30
|
5. Do **not** dump a methodology tutorial unless they ask how Duaer works.
|
|
31
|
-
6.
|
|
31
|
+
6. **Break down to atomic, monitorable tasks:** each `tasks.md` item is one
|
|
32
|
+
independently verifiable atom with a `- [ ]` / `- [x]` checkbox; mark done
|
|
33
|
+
as you go so progress is visible. Do not leave only coarse mega-tasks.
|
|
34
|
+
7. Merge to **`develop`** when done (hotfix: **`main`**, then back-merge
|
|
32
35
|
**`develop`**). Promote to **`main`** only if the user asks to go online.
|
|
33
|
-
|
|
36
|
+
8. **Handoff (mandatory):** stop processes bound to `.worktree/<id>/`, remove
|
|
34
37
|
the worktree, then restart services from the primary checkout on `develop`
|
|
35
38
|
using `.duaer/handoff.json` / `duaer handoff [--run]`. Never leave the user
|
|
36
39
|
on a dead worktree server.
|
|
@@ -32,9 +32,12 @@ For new behavior, fixes, refactors that change product behavior, or architecture
|
|
|
32
32
|
3. Do **not** wait for `/duaer-do` or any other skill invocation.
|
|
33
33
|
4. Do **not** say “please run `/duaer-specify`” — you do it.
|
|
34
34
|
5. Do **not** dump a methodology tutorial unless they ask how Duaer works.
|
|
35
|
-
6.
|
|
35
|
+
6. **Break down to atomic, monitorable tasks:** each `tasks.md` item is one
|
|
36
|
+
independently verifiable atom with a `- [ ]` / `- [x]` checkbox; mark done
|
|
37
|
+
as you go so progress is visible. Do not leave only coarse mega-tasks.
|
|
38
|
+
7. Merge to **`develop`** when done (hotfix: **`main`**, then back-merge
|
|
36
39
|
**`develop`**). Promote to **`main`** only if the user asks to go online.
|
|
37
|
-
|
|
40
|
+
8. **Handoff (mandatory):** stop processes bound to `.worktree/<id>/`, remove
|
|
38
41
|
the worktree, then restart services from the primary checkout on `develop`
|
|
39
42
|
using `.duaer/handoff.json` / `duaer handoff [--run]`. Never leave the user
|
|
40
43
|
on a dead worktree server.
|
|
@@ -54,7 +54,13 @@ Do **not** lecture about methodology. Do **not** ask them to run commands.
|
|
|
54
54
|
- Deep ambiguity after a full Spec still uses `duaer-clarify` (up to 5
|
|
55
55
|
questions). Understand is the **lightweight front door** on every ask.
|
|
56
56
|
|
|
57
|
-
3. **Break down** — If `tasks.md` missing, write a
|
|
57
|
+
3. **Break down** — If `tasks.md` missing, write a checklist from the Spec.
|
|
58
|
+
**Atomic + monitorable (mandatory):**
|
|
59
|
+
- Split until each item is **one** independently verifiable acceptance /
|
|
60
|
+
deliverable atom (not a mega-phase).
|
|
61
|
+
- Every item is a markdown checkbox (`- [ ]` / `- [x]`) so progress can be
|
|
62
|
+
polled; check off immediately when that atom is done.
|
|
63
|
+
- Prefer one checkbox per Acceptance line; no artificial upper count.
|
|
58
64
|
Skip a heavy plan unless architecture or contracts change.
|
|
59
65
|
|
|
60
66
|
4. **Work** — Implement open tasks; mark `[x]` as you go. No scope beyond Spec.
|
|
@@ -139,6 +139,14 @@ The tasks.md should be immediately executable - each task must be specific enoug
|
|
|
139
139
|
|
|
140
140
|
**CRITICAL**: Tasks MUST be organized by user story to enable independent implementation and testing.
|
|
141
141
|
|
|
142
|
+
**Atomic + monitorable (NON-NEGOTIABLE)**:
|
|
143
|
+
- Every task is **one** independently verifiable atom (one acceptance point,
|
|
144
|
+
one file-scoped change, or one verification slice) — not a bundled mega-task.
|
|
145
|
+
- Every task is a markdown checkbox (`- [ ]` / `- [x]`) so progress is
|
|
146
|
+
monitorable; agents mark `[x]` as each atom completes.
|
|
147
|
+
- Prefer one task per Acceptance / independent-test criterion. No artificial
|
|
148
|
+
upper count; refine if still too coarse.
|
|
149
|
+
|
|
142
150
|
**Verification is DEFAULT**: For each user story, generate verification tasks
|
|
143
151
|
from `.duaer/memory/testing.md` (L0–L3 as the risk table requires). Do **not**
|
|
144
152
|
omit tests just because the Spec did not say “TDD”. Only skip a level when the
|
package/.duaer/active-job.json
CHANGED
|
@@ -41,3 +41,16 @@ cannot cover the path.
|
|
|
41
41
|
**Job handoff (not a git lock):** the agent reports accept/open itself.
|
|
42
42
|
Default policy `coach` — do not claim "done" while the active job is unfinished.
|
|
43
43
|
Do not send the human to run `duaer status` for routine work.
|
|
44
|
+
|
|
45
|
+
### V. Atomic verifiable tasks; progress must be monitorable (NON-NEGOTIABLE)
|
|
46
|
+
|
|
47
|
+
When breaking down work (`tasks.md`, kickoff task pool, or mid-job re-split):
|
|
48
|
+
|
|
49
|
+
1. **Atomic + verifiable:** each task covers **one** independently checkable
|
|
50
|
+
acceptance point or deliverable slice. Do not bundle unrelated acceptance
|
|
51
|
+
into a mega-task.
|
|
52
|
+
2. **Monitorable progress:** every task is a markdown checkbox
|
|
53
|
+
(`- [ ]` / `- [x]`). Mark `[x]` as soon as that atom is done so FDE / humans
|
|
54
|
+
can see progress. Do not replace checkboxes with prose-only status.
|
|
55
|
+
3. Prefer splitting Acceptance lines (and Goal slices) over inventing coarse
|
|
56
|
+
phases. There is **no** artificial upper count; refine if still too coarse.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"status": "accepted",
|
|
3
|
+
"acceptedAt": "2026-09-19T04:20:00.000Z",
|
|
4
|
+
"summary": "0.21.0 shipped: develop+main pushed, tag v0.21.0, GitHub Release, npm publish success.",
|
|
5
|
+
"verification": {
|
|
6
|
+
"commands": [
|
|
7
|
+
"git push origin develop main v0.21.0",
|
|
8
|
+
"gh release create v0.21.0",
|
|
9
|
+
"gh run watch 35420973848 → success",
|
|
10
|
+
"npm view duaer-spec version → 0.21.0"
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
"preview": {
|
|
14
|
+
"url": "https://www.npmjs.com/package/duaer-spec/v/0.21.0",
|
|
15
|
+
"label": "npm 0.21.0"
|
|
16
|
+
},
|
|
17
|
+
"release": "https://github.com/fujiezee/duaer-spec/releases/tag/v0.21.0"
|
|
18
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"status": "accepted",
|
|
3
|
+
"acceptedAt": "2026-09-19T04:50:00.000Z",
|
|
4
|
+
"verification": {
|
|
5
|
+
"levels": ["L0", "L1"],
|
|
6
|
+
"commands": [
|
|
7
|
+
"node --check bin/live-modules.mjs bin/live-progress.mjs web/live-dev/task-graph.mjs web/live-dev/acceptance-lines.mjs",
|
|
8
|
+
"npm test (165 pass; atomic pool + preview split covered)"
|
|
9
|
+
],
|
|
10
|
+
"result": "pass"
|
|
11
|
+
},
|
|
12
|
+
"summary": "Atomic verifiable tasks with checkbox-monitorable progress: constitution, skills, kickoff pool + preview graph split acceptance lines."
|
|
13
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Feature Specification: Atomic verifiable tasks with monitorable progress
|
|
2
|
+
|
|
3
|
+
**Feature Branch**: `feat/atomic-tasks`
|
|
4
|
+
|
|
5
|
+
## Goal
|
|
6
|
+
|
|
7
|
+
When decomposing work (Break down / kickoff task pool / `tasks.md`), every
|
|
8
|
+
task MUST be an **atomic, independently verifiable** unit, and every task's
|
|
9
|
+
progress MUST be **monitorable** via a markdown checkbox that the FDE desk
|
|
10
|
+
(and agents) can poll.
|
|
11
|
+
|
|
12
|
+
## Why
|
|
13
|
+
|
|
14
|
+
Coarse mega-tasks hide progress and block wave orchestration. Acceptance
|
|
15
|
+
bundled into one line cannot be checked off piecemeal.
|
|
16
|
+
|
|
17
|
+
## In scope
|
|
18
|
+
|
|
19
|
+
- Constitution + duaer-do / duaer-tasks / template wording for the rule
|
|
20
|
+
- Kickoff `buildTaskPoolFromModules` splits module acceptance into one
|
|
21
|
+
checkbox task per acceptance line (plus impl / verify / shared steps)
|
|
22
|
+
- Preview task graph uses the same atomic split so path matches pool
|
|
23
|
+
- `tasks.md` footer / dispatch prompt reinforce: checkbox = progress signal
|
|
24
|
+
- Tests + E2E catalog row
|
|
25
|
+
|
|
26
|
+
## Out of scope
|
|
27
|
+
|
|
28
|
+
- Changing orchestration wave algorithm beyond richer ready sets
|
|
29
|
+
- Dedicated cloud / npm release
|
|
30
|
+
|
|
31
|
+
## Acceptance
|
|
32
|
+
|
|
33
|
+
1. Constitution (or equivalent always-on rule) states: decompose to atomic
|
|
34
|
+
verifiable tasks; each task progress monitorable via `- [ ]` / `- [x]`
|
|
35
|
+
2. Module with multi-line acceptance yields one task per acceptance line in
|
|
36
|
+
the task pool / `tasks.md`
|
|
37
|
+
3. Preview pool for the same modules has matching atomic acceptance tasks
|
|
38
|
+
4. Unit tests cover the split; E2E catalog mentions the rule
|
|
39
|
+
5. Agents following duaer-do Break down must not leave only coarse mega-tasks
|
|
40
|
+
|
|
41
|
+
## Assumptions
|
|
42
|
+
|
|
43
|
+
- Prefer splitting on acceptance lines / `;` / `;` (existing
|
|
44
|
+
`splitAcceptanceLines`) rather than LLM rephrasing at kickoff
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Tasks
|
|
2
|
+
|
|
3
|
+
- [x] T001 Add constitution + duaer-do / duaer-tasks / template rule
|
|
4
|
+
- [x] T002 Split acceptance into atomic tasks in live-modules pool
|
|
5
|
+
- [x] T003 Mirror atomic split in task-graph preview pool
|
|
6
|
+
- [x] T004 Strengthen tasks.md footer + dispatch prompt wording
|
|
7
|
+
- [x] T005 Unit tests + E2E catalog
|
|
8
|
+
- [x] T006 Stamp delivery accepted
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"status": "accepted",
|
|
3
|
+
"acceptedAt": "2026-09-19T07:40:00.000Z",
|
|
4
|
+
"verification": {
|
|
5
|
+
"levels": ["L0", "L1", "L3"],
|
|
6
|
+
"commands": [
|
|
7
|
+
"node --check web/live-dev/i18n.js web/live-dev/i18n-ja.js web/live-dev/app.js bin/live-deliverables.mjs",
|
|
8
|
+
"npm test (168 pass incl. live-i18n-ja)",
|
|
9
|
+
"npm run test:live"
|
|
10
|
+
],
|
|
11
|
+
"result": "pass"
|
|
12
|
+
},
|
|
13
|
+
"summary": "Added Japanese (ja) live desk locale with full catalog parity, language select, and deliverables lang=ja."
|
|
14
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Feature Specification: Live desk Japanese locale (ja)
|
|
2
|
+
|
|
3
|
+
**Feature Branch**: `feat/live-i18n-ja`
|
|
4
|
+
|
|
5
|
+
## Goal
|
|
6
|
+
|
|
7
|
+
Add **Japanese (`ja`)** as a third FDE live-desk language beside zh-CN and en.
|
|
8
|
+
Users can switch to 日本語; reload keeps the choice; desk copy updates.
|
|
9
|
+
|
|
10
|
+
## In scope
|
|
11
|
+
|
|
12
|
+
- `LOCALES` includes `ja`; full catalog for all i18n keys
|
|
13
|
+
- Language `<select>` option; `detectLocale` for `ja*` navigator
|
|
14
|
+
- Deliverables `lang=ja` + date/`html lang` handling
|
|
15
|
+
- app.js list join / toLocaleString for ja
|
|
16
|
+
- task-graph Archify locale passthrough
|
|
17
|
+
- E2E catalog + unit/smoke markers; CHANGELOG
|
|
18
|
+
|
|
19
|
+
## Out of scope
|
|
20
|
+
|
|
21
|
+
- Translating README / intro site / worker-models.md into Japanese
|
|
22
|
+
- Other locales beyond `ja`
|
|
23
|
+
|
|
24
|
+
## Acceptance
|
|
25
|
+
|
|
26
|
+
1. Language select shows 日本語 (`ja`); switching updates visible desk copy
|
|
27
|
+
2. Reload keeps `ja` via localStorage
|
|
28
|
+
3. Every key present in `en` exists in `ja` (no missing-key gaps in catalog parity test)
|
|
29
|
+
4. Deliverables HTML accepts `lang=ja`
|
|
30
|
+
5. `npm test` / required live smoke still pass
|
|
31
|
+
|
|
32
|
+
## Assumptions
|
|
33
|
+
|
|
34
|
+
- Default remains zh-CN when unset; `ja` browser language can auto-detect to ja
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Tasks
|
|
2
|
+
|
|
3
|
+
- [x] T001 Add ja catalog + LOCALES/detectLocale/setLocale wiring
|
|
4
|
+
- [x] T002 Language select option in index.html
|
|
5
|
+
- [x] T003 app.js / deliverables / task-graph ja handling
|
|
6
|
+
- [x] T004 Tests + E2E + docs + CHANGELOG
|
|
7
|
+
- [x] T005 Stamp delivery accepted
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# 205 — Release 0.22.0
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Ship 0.22.0: bump version + CHANGELOG, promote develop → main, push,
|
|
6
|
+
GitHub Release `v0.22.0`, trigger npm Trusted Publishing.
|
|
7
|
+
|
|
8
|
+
## Acceptance
|
|
9
|
+
|
|
10
|
+
1. `package.json` is `0.22.0`; CHANGELOG has `0.22.0` section (Unreleased cleared).
|
|
11
|
+
2. `develop` and `main` pushed with release commits.
|
|
12
|
+
3. GitHub Release `v0.22.0` published; npm shows `duaer-spec@0.22.0`.
|
|
@@ -15,6 +15,10 @@ TDD-style tests-before-code remain encouraged when useful.
|
|
|
15
15
|
|
|
16
16
|
**Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story.
|
|
17
17
|
|
|
18
|
+
**Atomic + monitorable**: Each checkbox is one independently verifiable atom.
|
|
19
|
+
Progress is only via `- [ ]` / `- [x]` — no prose-only status. Split Acceptance
|
|
20
|
+
into one task per criterion; no artificial upper count.
|
|
21
|
+
|
|
18
22
|
## Format: `[ID] [P?] [Story] Description`
|
|
19
23
|
|
|
20
24
|
- **[P]**: Can run in parallel (different files, no dependencies)
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.22.0 — 2026-09-19
|
|
6
|
+
|
|
7
|
+
### Feat: Japanese (ja) live desk locale
|
|
8
|
+
|
|
9
|
+
- Language select adds 日本語; catalog covers all en keys
|
|
10
|
+
- Deliverables API accepts `lang=ja`; dates / HTML `lang` use ja-JP
|
|
11
|
+
|
|
12
|
+
### Feat: atomic verifiable tasks with monitorable progress
|
|
13
|
+
|
|
14
|
+
- Constitution + duaer-do / duaer-tasks: decompose to one independently
|
|
15
|
+
verifiable atom per checkbox; progress only via `- [ ]` / `- [x]`
|
|
16
|
+
- Kickoff task pool and preview graph split multi-line acceptance into
|
|
17
|
+
one task per criterion so FDE can monitor and release waves
|
|
18
|
+
|
|
5
19
|
## 0.21.0 — 2026-09-19
|
|
6
20
|
|
|
7
21
|
### Feat: click architecture diagram for fullscreen present view
|
package/bin/duaer-live.mjs
CHANGED
|
@@ -3625,7 +3625,7 @@ Brief: ${featureDir}
|
|
|
3625
3625
|
3. 按 .duaer/memory/testing.md(若有)做风险验证
|
|
3626
3626
|
4. 每完成 tasks.md 中的一步,立刻把该行改成 - [x](Duaer-spec FDE 靠此显示细粒度进度与编排放行)
|
|
3627
3627
|
4a. 只做当前编排波次已放行的任务;未满足 dependsOn / 未放行的任务不要开工;本波勾完后:若还有后续波次则静默停等编排器 continue(不要写 Job not accepted yet);若已无后续波次则继续完成 stamp accepted
|
|
3628
|
-
4b.
|
|
3628
|
+
4b. 拆任务(强制):必须拆到原子任务且每条可独立验证;每个勾选项只覆盖一个验收点;进度只能用 tasks.md 的 - [ ]/- [x] 监控(立刻勾选,不要攒到最后);不要把多项验收揉进同一条;不要人为限制条数。若仍偏粗,先按 Acceptance 扩成「一条验收一勾选」(仍用 T00x),保存后再做
|
|
3629
3629
|
5. 对照 Acceptance 全部满足后,才 stamp ${path.join(featureDir, "delivery.json")} 为 accepted——tasks.md 全部勾完还不够,必须 stamp;禁止停在 Job not accepted yet
|
|
3630
3630
|
5b. 交付前必须更新产品仓 README(说明文档):与本次交付一致——做什么、模块/验收要点、如何运行或打开;需求变了就改 README,不要只改代码。英文 README 不得出现中文;若项目是中文说明则用 README.zh-CN.md(或项目既有约定),可夹英文术语
|
|
3631
3631
|
6. 必须在 delivery.json 写入 preview.url(满意交付的必填证据):必须是可打开的成品入口——HTTP 服务用 http://localhost:…;静态页用 index.html 等 HTML。禁止把 docs/**/*.md 等说明文档当作 preview.url——不要因「没有页面」而省略
|
|
@@ -4243,7 +4243,7 @@ ${restated.keep}
|
|
|
4243
4243
|
0. 本轮 Revision 已在 Duaer-spec FDE 自动验收通过。直接改;不要进入 Confirming intent;不要让用户从多个风格/方向选项里再选一次;不要反复确认需求
|
|
4244
4244
|
1. 只做本轮 Revision ${revN} 范围,不要重做无关功能
|
|
4245
4245
|
2. 立刻把 tasks.md 里 R${revN}-* 勾成 - [x](Duaer-spec FDE 靠此显示细粒度进度)
|
|
4246
|
-
2b.
|
|
4246
|
+
2b. 拆任务(强制):必须拆到原子任务且每条可独立验证;每个 R${revN}-* 只覆盖一个改动;进度用 - [ ]/- [x] 立刻勾选监控;不要把多项验收揉进同一条;不要人为限制条数。若仍偏粗,先按本轮 acceptance 扩成「一条改动一勾选」(仍用 R${revN}-*),保存后再做
|
|
4247
4247
|
3. 对照本轮 Revision acceptance 全部满足后,才 stamp delivery.json 为 accepted,并必须更新 preview.url 为可打开的成品入口(http://localhost:… 或 HTML 页面,必填;禁止用 docs/**/*.md 当预览);若是服务须先启动并可打开。禁止以 Job not accepted yet 收尾
|
|
4248
4248
|
3b. 交付前必须更新产品仓 README(说明文档)以反映本轮改动后的行为/用法;需求变了就改 README,不要只改代码。英文 README 不得出现中文;中文说明用 README.zh-CN.md(或项目既有约定)
|
|
4249
4249
|
4. 按 testing.md 做风险验证(若有)
|
|
@@ -6578,10 +6578,13 @@ async function handleApi(req, res) {
|
|
|
6578
6578
|
send(res, 400, { error: "path required", code: "EMPTY_PATH" });
|
|
6579
6579
|
return;
|
|
6580
6580
|
}
|
|
6581
|
+
const langRaw = String(url.searchParams.get("lang") || "").toLowerCase();
|
|
6581
6582
|
const lang =
|
|
6582
|
-
|
|
6583
|
+
langRaw === "en" || langRaw.startsWith("en")
|
|
6583
6584
|
? "en"
|
|
6584
|
-
: "
|
|
6585
|
+
: langRaw === "ja" || langRaw.startsWith("ja")
|
|
6586
|
+
? "ja"
|
|
6587
|
+
: "zh";
|
|
6585
6588
|
const asJson = String(url.searchParams.get("format") || "") === "json";
|
|
6586
6589
|
const session = readProjectChat(liveRoot(), projectPath);
|
|
6587
6590
|
let job = null;
|
|
@@ -15,12 +15,25 @@ function esc(s) {
|
|
|
15
15
|
.replace(/"/g, """);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
function normalizeDeliverablesLang(lang) {
|
|
19
|
+
const l = String(lang || "").toLowerCase();
|
|
20
|
+
if (l === "en" || l.startsWith("en")) return "en";
|
|
21
|
+
if (l === "ja" || l.startsWith("ja")) return "ja";
|
|
22
|
+
return "zh";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function pickLang(lang, map) {
|
|
26
|
+
const l = normalizeDeliverablesLang(lang);
|
|
27
|
+
return map[l] ?? map.zh;
|
|
28
|
+
}
|
|
29
|
+
|
|
18
30
|
function fmtAt(iso, lang) {
|
|
19
|
-
if (!iso) return
|
|
31
|
+
if (!iso) return "—";
|
|
20
32
|
try {
|
|
21
33
|
const d = new Date(iso);
|
|
22
34
|
if (Number.isNaN(d.getTime())) return String(iso).slice(0, 19);
|
|
23
|
-
|
|
35
|
+
const tag = pickLang(lang, { en: "en-US", ja: "ja-JP", zh: "zh-CN" });
|
|
36
|
+
return d.toLocaleString(tag, {
|
|
24
37
|
year: "numeric",
|
|
25
38
|
month: "2-digit",
|
|
26
39
|
day: "2-digit",
|
|
@@ -279,7 +292,7 @@ function renderTaskPoolHtml(body, L) {
|
|
|
279
292
|
* @param {{ job?: object|null, lang?: string, projectTitle?: string }} [opts]
|
|
280
293
|
*/
|
|
281
294
|
export function buildDeliverablesModel(session, opts = {}) {
|
|
282
|
-
const lang = opts.lang
|
|
295
|
+
const lang = normalizeDeliverablesLang(opts.lang);
|
|
283
296
|
const s = session && typeof session === "object" ? session : {};
|
|
284
297
|
const modules = Array.isArray(s.modules) ? s.modules : [];
|
|
285
298
|
const confirmed = modules.filter((m) => m.status === "confirmed");
|
|
@@ -294,7 +307,7 @@ export function buildDeliverablesModel(session, opts = {}) {
|
|
|
294
307
|
if (allReq || confirmed.length) {
|
|
295
308
|
reqVersions.push({
|
|
296
309
|
id: "v0",
|
|
297
|
-
label: lang
|
|
310
|
+
label: pickLang(lang, { en: "Initial", ja: "初版", zh: "初版" }),
|
|
298
311
|
at: s.updatedAt || null,
|
|
299
312
|
modules: (allReq ? modules : confirmed).map((m) => ({
|
|
300
313
|
id: m.id,
|
|
@@ -309,8 +322,11 @@ export function buildDeliverablesModel(session, opts = {}) {
|
|
|
309
322
|
if (rev < 1) continue;
|
|
310
323
|
reqVersions.push({
|
|
311
324
|
id: `r${rev}`,
|
|
312
|
-
label:
|
|
313
|
-
|
|
325
|
+
label: pickLang(lang, {
|
|
326
|
+
en: `Revision ${rev}`,
|
|
327
|
+
ja: `改訂 ${rev}`,
|
|
328
|
+
zh: `第 ${rev} 次改进`,
|
|
329
|
+
}),
|
|
314
330
|
at: entry.at || s.updatedAt || null,
|
|
315
331
|
revise: {
|
|
316
332
|
goal: entry.goal || "",
|
|
@@ -324,19 +340,27 @@ export function buildDeliverablesModel(session, opts = {}) {
|
|
|
324
340
|
const stages = [
|
|
325
341
|
{
|
|
326
342
|
id: "requirements",
|
|
327
|
-
title: lang
|
|
343
|
+
title: pickLang(lang, { en: "Requirements", ja: "要件", zh: "需求" }),
|
|
328
344
|
status: allReq ? "done" : confirmed.length ? "partial" : "empty",
|
|
329
345
|
artifacts: [
|
|
330
346
|
{
|
|
331
347
|
id: "req-doc",
|
|
332
|
-
title: lang
|
|
348
|
+
title: pickLang(lang, {
|
|
349
|
+
en: "Requirements document",
|
|
350
|
+
ja: "要件ドキュメント",
|
|
351
|
+
zh: "需求文档",
|
|
352
|
+
}),
|
|
333
353
|
kind: "timeline",
|
|
334
354
|
ready: reqVersions.length > 0,
|
|
335
355
|
versions: reqVersions,
|
|
336
356
|
},
|
|
337
357
|
{
|
|
338
358
|
id: "req-confirm",
|
|
339
|
-
title: lang
|
|
359
|
+
title: pickLang(lang, {
|
|
360
|
+
en: "Requirements confirmation",
|
|
361
|
+
ja: "要件確認書",
|
|
362
|
+
zh: "需求确认书",
|
|
363
|
+
}),
|
|
340
364
|
kind: "confirmations",
|
|
341
365
|
ready: confirmed.length > 0,
|
|
342
366
|
confirmations: confirmed.map((m) => ({
|
|
@@ -350,12 +374,16 @@ export function buildDeliverablesModel(session, opts = {}) {
|
|
|
350
374
|
},
|
|
351
375
|
{
|
|
352
376
|
id: "architecture",
|
|
353
|
-
title: lang
|
|
377
|
+
title: pickLang(lang, { en: "Architecture", ja: "アーキテクチャ", zh: "架构" }),
|
|
354
378
|
status: arch.confirmed ? "done" : arch.url ? "partial" : "empty",
|
|
355
379
|
artifacts: [
|
|
356
380
|
{
|
|
357
381
|
id: "arch-diagram",
|
|
358
|
-
title: lang
|
|
382
|
+
title: pickLang(lang, {
|
|
383
|
+
en: "Architecture diagram",
|
|
384
|
+
ja: "アーキテクチャ図",
|
|
385
|
+
zh: "架构图",
|
|
386
|
+
}),
|
|
359
387
|
kind: "architecture",
|
|
360
388
|
ready: Boolean(arch.url),
|
|
361
389
|
url: arch.url || null,
|
|
@@ -364,34 +392,48 @@ export function buildDeliverablesModel(session, opts = {}) {
|
|
|
364
392
|
},
|
|
365
393
|
{
|
|
366
394
|
id: "arch-confirm",
|
|
367
|
-
title: lang
|
|
395
|
+
title: pickLang(lang, {
|
|
396
|
+
en: "Architecture confirmation",
|
|
397
|
+
ja: "アーキテクチャ確認",
|
|
398
|
+
zh: "架构确认",
|
|
399
|
+
}),
|
|
368
400
|
kind: "note",
|
|
369
401
|
ready: Boolean(arch.confirmed),
|
|
370
402
|
body: arch.confirmed
|
|
371
|
-
? lang
|
|
372
|
-
|
|
373
|
-
|
|
403
|
+
? pickLang(lang, {
|
|
404
|
+
en: "Architecture confirmed on the desk.",
|
|
405
|
+
ja: "デスクでアーキテクチャを確認済みです。",
|
|
406
|
+
zh: "已在台面确认架构。",
|
|
407
|
+
})
|
|
374
408
|
: "",
|
|
375
409
|
},
|
|
376
410
|
],
|
|
377
411
|
},
|
|
378
412
|
{
|
|
379
413
|
id: "kickoff",
|
|
380
|
-
title: lang
|
|
414
|
+
title: pickLang(lang, {
|
|
415
|
+
en: "Kickoff & implementation",
|
|
416
|
+
ja: "キックオフと実装",
|
|
417
|
+
zh: "开工与实现",
|
|
418
|
+
}),
|
|
381
419
|
status: s.jobId || s.taskPool ? "done" : "empty",
|
|
382
420
|
artifacts: [
|
|
383
421
|
{
|
|
384
422
|
id: "brief",
|
|
385
|
-
title: lang
|
|
423
|
+
title: pickLang(lang, {
|
|
424
|
+
en: "Brief / job",
|
|
425
|
+
ja: "Brief / ジョブ",
|
|
426
|
+
zh: "Brief / 工单",
|
|
427
|
+
}),
|
|
386
428
|
kind: "note",
|
|
387
429
|
ready: Boolean(s.jobId),
|
|
388
430
|
body: s.jobId
|
|
389
|
-
? `${lang
|
|
431
|
+
? `${pickLang(lang, { en: "Job", ja: "ジョブ", zh: "工单" })}: ${s.jobId}`
|
|
390
432
|
: "",
|
|
391
433
|
},
|
|
392
434
|
{
|
|
393
435
|
id: "task-pool",
|
|
394
|
-
title: lang
|
|
436
|
+
title: pickLang(lang, { en: "Task pool", ja: "タスクプール", zh: "任务池" }),
|
|
395
437
|
kind: "taskpool",
|
|
396
438
|
ready: Boolean(s.taskPool),
|
|
397
439
|
body: s.taskPool
|
|
@@ -399,16 +441,18 @@ export function buildDeliverablesModel(session, opts = {}) {
|
|
|
399
441
|
: "",
|
|
400
442
|
meta:
|
|
401
443
|
Number(s.workerCount) > 1
|
|
402
|
-
? lang
|
|
403
|
-
|
|
404
|
-
|
|
444
|
+
? pickLang(lang, {
|
|
445
|
+
en: `Workers: ${s.workerCount}`,
|
|
446
|
+
ja: `デジタル従業員:${s.workerCount}`,
|
|
447
|
+
zh: `数字员工:${s.workerCount}`,
|
|
448
|
+
})
|
|
405
449
|
: "",
|
|
406
450
|
},
|
|
407
451
|
],
|
|
408
452
|
},
|
|
409
453
|
{
|
|
410
454
|
id: "delivery",
|
|
411
|
-
title: lang
|
|
455
|
+
title: pickLang(lang, { en: "Delivery", ja: "デリバリー", zh: "交付" }),
|
|
412
456
|
status:
|
|
413
457
|
delivery?.status === "accepted" || job?.jobStatus === "accepted"
|
|
414
458
|
? "done"
|
|
@@ -418,22 +462,26 @@ export function buildDeliverablesModel(session, opts = {}) {
|
|
|
418
462
|
artifacts: [
|
|
419
463
|
{
|
|
420
464
|
id: "delivery-stamp",
|
|
421
|
-
title: lang
|
|
465
|
+
title: pickLang(lang, {
|
|
466
|
+
en: "Delivery stamp",
|
|
467
|
+
ja: "デリバリースタンプ",
|
|
468
|
+
zh: "交付戳",
|
|
469
|
+
}),
|
|
422
470
|
kind: "note",
|
|
423
471
|
ready: Boolean(delivery || job?.jobStatus),
|
|
424
472
|
body: delivery
|
|
425
|
-
? `${lang
|
|
473
|
+
? `${pickLang(lang, { en: "Status", ja: "状態", zh: "状态" })}: ${delivery.status || "—"}${
|
|
426
474
|
delivery.acceptedAt
|
|
427
|
-
? `\n${lang
|
|
475
|
+
? `\n${pickLang(lang, { en: "Accepted", ja: "受入", zh: "验收" })}: ${delivery.acceptedAt}`
|
|
428
476
|
: ""
|
|
429
477
|
}`
|
|
430
478
|
: job?.jobStatus
|
|
431
|
-
? `${lang
|
|
479
|
+
? `${pickLang(lang, { en: "Job status", ja: "ジョブ状態", zh: "工单状态" })}: ${job.jobStatus}`
|
|
432
480
|
: "",
|
|
433
481
|
},
|
|
434
482
|
{
|
|
435
483
|
id: "preview",
|
|
436
|
-
title: lang
|
|
484
|
+
title: pickLang(lang, { en: "Preview", ja: "プレビュー", zh: "预览" }),
|
|
437
485
|
kind: "link",
|
|
438
486
|
ready: Boolean(preview?.url),
|
|
439
487
|
url: preview?.url || null,
|
|
@@ -445,14 +493,15 @@ export function buildDeliverablesModel(session, opts = {}) {
|
|
|
445
493
|
if (reviseCards.length || s.reviseLocked || s.lastRevision) {
|
|
446
494
|
stages.push({
|
|
447
495
|
id: "revise",
|
|
448
|
-
title: lang
|
|
496
|
+
title: pickLang(lang, { en: "Revisions", ja: "改訂", zh: "改进" }),
|
|
449
497
|
status: reviseCards.length ? "done" : "partial",
|
|
450
498
|
artifacts: reviseCards.map((entry) => ({
|
|
451
499
|
id: `revise-${entry.revision}`,
|
|
452
|
-
title:
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
500
|
+
title: pickLang(lang, {
|
|
501
|
+
en: `Revision ${entry.revision} plan`,
|
|
502
|
+
ja: `改訂 ${entry.revision} プラン`,
|
|
503
|
+
zh: `第 ${entry.revision} 版改进方案`,
|
|
504
|
+
}),
|
|
456
505
|
kind: "card",
|
|
457
506
|
ready: true,
|
|
458
507
|
card: {
|
|
@@ -474,7 +523,7 @@ export function buildDeliverablesModel(session, opts = {}) {
|
|
|
474
523
|
projectTitle:
|
|
475
524
|
String(opts.projectTitle || "").trim() ||
|
|
476
525
|
path.basename(String(s.projectPath || "")) ||
|
|
477
|
-
(lang
|
|
526
|
+
pickLang(lang, { en: "Project", ja: "プロジェクト", zh: "项目" }),
|
|
478
527
|
updatedAt: s.updatedAt || null,
|
|
479
528
|
stages,
|
|
480
529
|
};
|
|
@@ -512,6 +561,37 @@ function labelsFor(lang) {
|
|
|
512
561
|
jump: "View in requirements",
|
|
513
562
|
};
|
|
514
563
|
}
|
|
564
|
+
if (lang === "ja") {
|
|
565
|
+
return {
|
|
566
|
+
brand: "プロジェクト成果物",
|
|
567
|
+
pageTitle: "成果物ドシエ",
|
|
568
|
+
subtitle: "各段階で何ができたかを整理した記録",
|
|
569
|
+
generated: "生成日時",
|
|
570
|
+
project: "プロジェクト",
|
|
571
|
+
emptyStage: "まだ成果物はありません",
|
|
572
|
+
emptyCard: "(空)",
|
|
573
|
+
goal: "目的",
|
|
574
|
+
out: "対象外",
|
|
575
|
+
accept: "受入基準",
|
|
576
|
+
assume: "前提",
|
|
577
|
+
statusDone: "完了",
|
|
578
|
+
statusPartial: "進行中",
|
|
579
|
+
statusEmpty: "未着手",
|
|
580
|
+
timeline: "バージョンタイムライン",
|
|
581
|
+
toc: "目次",
|
|
582
|
+
artifacts: "成果物",
|
|
583
|
+
openArch: "図を開く",
|
|
584
|
+
openLink: "開く",
|
|
585
|
+
confirmed: "確認済み",
|
|
586
|
+
draft: "下書き",
|
|
587
|
+
module: "モジュール",
|
|
588
|
+
depends: "依存",
|
|
589
|
+
taskId: "ID",
|
|
590
|
+
taskTitle: "タスク",
|
|
591
|
+
confirmRegistry: "確認レジストリ",
|
|
592
|
+
jump: "要件本文を見る",
|
|
593
|
+
};
|
|
594
|
+
}
|
|
515
595
|
return {
|
|
516
596
|
brand: "项目交付物",
|
|
517
597
|
pageTitle: "交付物档案",
|
|
@@ -631,7 +711,7 @@ function renderArtifact(art, L, lang) {
|
|
|
631
711
|
* @param {ReturnType<typeof buildDeliverablesModel>} model
|
|
632
712
|
*/
|
|
633
713
|
export function renderDeliverablesHtml(model) {
|
|
634
|
-
const lang = model.lang
|
|
714
|
+
const lang = normalizeDeliverablesLang(model.lang);
|
|
635
715
|
const L = labelsFor(lang);
|
|
636
716
|
const stages = model.stages || [];
|
|
637
717
|
const tocHtml = `<nav class="toc" aria-label="${esc(L.toc)}">
|
|
@@ -673,7 +753,7 @@ export function renderDeliverablesHtml(model) {
|
|
|
673
753
|
.join("\n");
|
|
674
754
|
|
|
675
755
|
return `<!DOCTYPE html>
|
|
676
|
-
<html lang="${lang
|
|
756
|
+
<html lang="${pickLang(lang, { en: "en", ja: "ja", zh: "zh-CN" })}">
|
|
677
757
|
<head>
|
|
678
758
|
<meta charset="utf-8" />
|
|
679
759
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|