@betterdanlins/ai-memory 0.1.0 → 0.4.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 +158 -14
- package/README.zh-CN.md +170 -0
- package/bin/cli.js +189 -5
- package/package.json +1 -1
- package/src/framework.js +260 -0
- package/src/legacy-v0.1.js +43 -0
- package/src/manifest.js +20 -1
- package/src/migrations.js +28 -0
- package/src/model-routing.js +108 -0
- package/src/path-safety.js +51 -0
- package/src/scaffold.js +114 -13
- package/src/workflow.js +185 -0
- package/templates/claude/.claude/agents/economy-test-worker.md +8 -0
- package/templates/claude/.claude/agents/premium-implementer.md +8 -0
- package/templates/claude/.claude/agents/premium-planner.md +8 -0
- package/templates/claude/.claude/agents/premium-reviewer.md +8 -0
- package/templates/claude/.claude/agents/standard-implementer.md +8 -0
- package/templates/claude/.claude/agents/standard-test-worker.md +8 -0
- package/templates/claude/.claude/commands/delivery-readiness.md +6 -0
- package/templates/claude/.claude/commands/design-feature.md +6 -0
- package/templates/claude/.claude/commands/finalize-requirement.md +1 -1
- package/templates/claude/.claude/commands/project-inception.md +5 -0
- package/templates/claude/.claude/settings.json +1 -1
- package/templates/claude/.claude/skills/critic/SKILL.md +2 -2
- package/templates/claude/.claude/skills/delivery-readiness/SKILL.md +6 -0
- package/templates/claude/.claude/skills/feature-design/SKILL.md +6 -0
- package/templates/claude/.claude/skills/memory-update/SKILL.md +1 -1
- package/templates/claude/.claude/skills/model-routing/SKILL.md +6 -0
- package/templates/claude/.claude/skills/project-inception/SKILL.md +6 -0
- package/templates/claude/CLAUDE.md +11 -5
- package/templates/codex/.agents/skills/critic/SKILL.md +2 -2
- package/templates/codex/.agents/skills/delivery-readiness/SKILL.md +6 -0
- package/templates/codex/.agents/skills/feature-design/SKILL.md +6 -0
- package/templates/codex/.agents/skills/memory-update/SKILL.md +1 -1
- package/templates/codex/.agents/skills/model-routing/SKILL.md +6 -0
- package/templates/codex/.agents/skills/project-inception/SKILL.md +6 -0
- package/templates/codex/.codex/agents/economy_test_worker.toml +6 -0
- package/templates/codex/.codex/agents/premium_implementer.toml +6 -0
- package/templates/codex/.codex/agents/premium_planner.toml +6 -0
- package/templates/codex/.codex/agents/premium_reviewer.toml +6 -0
- package/templates/codex/.codex/agents/standard_implementer.toml +6 -0
- package/templates/codex/.codex/agents/standard_test_worker.toml +6 -0
- package/templates/codex/AGENTS.md +11 -5
- package/templates/common/.ai/README.md +5 -1
- package/templates/common/.ai/config/model-routing.json +5 -0
- package/templates/common/.ai/memory/project-state.md +1 -1
- package/templates/common/.ai/memory/session-log.md +1 -1
- package/templates/common/.ai/runs/.gitignore +2 -0
- package/templates/common/.ai/skills/architecture.md +24 -14
- package/templates/common/.ai/skills/code-review.md +1 -1
- package/templates/common/.ai/skills/critic.md +4 -2
- package/templates/common/.ai/skills/delivery-readiness.md +34 -0
- package/templates/common/.ai/skills/feature-design.md +35 -0
- package/templates/common/.ai/skills/memory-update.md +19 -14
- package/templates/common/.ai/skills/model-routing.md +39 -0
- package/templates/common/.ai/skills/project-inception.md +33 -0
- package/templates/common/.ai/skills/requirements-flow.md +33 -16
- package/templates/common/docs/architecture/data-model.md +43 -0
- package/templates/common/docs/architecture/deployment.md +40 -0
- package/templates/common/docs/architecture/quality-attributes.md +50 -0
- package/templates/common/docs/architecture/system-context.md +35 -0
- package/templates/common/docs/design/README.md +22 -0
- package/templates/common/docs/design/v1.0.0/.gitkeep +0 -0
- package/templates/common/docs/requirements/README.md +2 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Betterdan
|
|
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
CHANGED
|
@@ -1,26 +1,170 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ai-memory
|
|
2
2
|
|
|
3
|
-
AI
|
|
4
|
-
记忆层(.ai/memory)、方法论层(.ai/skills)、需求版本目录(docs/requirements)
|
|
5
|
-
与两侧薄适配层。
|
|
3
|
+
> Scaffold a shared AI memory layer and requirements workflow into any project — one command, portable across Claude Code, Codex, and future agents.
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
[English](README.md) · [简体中文](README.zh-CN.md)
|
|
6
|
+
|
|
7
|
+
## Why
|
|
8
|
+
|
|
9
|
+
AI coding agents forget everything between sessions, and every tool (Claude Code, Codex, …) wants its own config format. `ai-memory` fixes both:
|
|
10
|
+
|
|
11
|
+
- **One source of truth.** All real content lives in a single agent-agnostic `.ai/` directory. Tool-specific folders (`.claude/`, `.agents/`) are thin wrappers that just point back to it — so switching or adding tools never forks your knowledge base.
|
|
12
|
+
- **Memory that survives sessions.** A structured memory layer records what the last session finished, overall requirement progress, and user-level preferences, so a fresh agent can pick up exactly where the previous one left off.
|
|
13
|
+
- **A requirements workflow with guardrails.** Human rough drafts and AI-finalized specs live in versioned `draft/` → `final/` directories, with a mandatory "critic" review gate before anything is finalized.
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
8
16
|
|
|
9
17
|
```bash
|
|
18
|
+
# Interactive
|
|
10
19
|
npx @betterdanlins/ai-memory init
|
|
11
|
-
|
|
20
|
+
|
|
21
|
+
# Non-interactive
|
|
12
22
|
npx @betterdanlins/ai-memory init --name demo --stack "PHP + Vue" --tools claude,codex --yes
|
|
13
|
-
|
|
23
|
+
|
|
24
|
+
# Opt in to staged model routing
|
|
25
|
+
npx @betterdanlins/ai-memory init --model-profile balanced --yes
|
|
26
|
+
|
|
27
|
+
# Import user-level memory (profile/feedback) from an existing project
|
|
14
28
|
npx @betterdanlins/ai-memory init --import /path/to/other-project
|
|
29
|
+
|
|
30
|
+
# Inspect an existing project's upgrade plan without writing
|
|
31
|
+
npx @betterdanlins/ai-memory update --dry-run
|
|
32
|
+
|
|
33
|
+
# Apply only conflict-free safe updates; abort entirely on user changes
|
|
34
|
+
npx @betterdanlins/ai-memory update --yes
|
|
35
|
+
|
|
36
|
+
# Switch an initialized project from inherited models to staged routing
|
|
37
|
+
npx @betterdanlins/ai-memory models configure --profile balanced
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Nothing is ever overwritten silently: existing files are asked about one by one (interactive) or skipped (`--yes`).
|
|
41
|
+
An invalid, non-directory, or unreadable `--import` project path fails explicitly. If the project directory is valid but an individual memory file is missing, the CLI falls back to its template and reports that fallback in the summary.
|
|
42
|
+
Before writing, the CLI checks for duplicate template destinations, path traversal, and symbolic links, then reads and renders every planned file. If an actual filesystem write fails midway, the error summary lists both written and not-yet-written files.
|
|
43
|
+
An initialized project cannot be initialized again. A newer CLI first uses `update --dry-run` to identify versions and user modifications. `update --yes` applies only additions and baseline-matching safe updates, aborting before any write when merge or review items exist.
|
|
44
|
+
|
|
45
|
+
## Versions and compatibility
|
|
46
|
+
|
|
47
|
+
### v0.4.0 — staged model routing and verified handoffs
|
|
48
|
+
|
|
49
|
+
- Adds opt-in `inherit`, `balanced`, and `quality` profiles. New and upgraded projects default to `inherit`, so installing v0.4.0 does not silently increase model cost or change v0.3.0 workflow depth.
|
|
50
|
+
- Adds native Claude Code and Codex agents for premium planning/review, standard or premium implementation, and economy or standard test authoring. Model routing selects an executor only after the existing S/M/L workflow decides which stages are required.
|
|
51
|
+
- Adds `models show/configure` plus `workflow prepare/verify/complete`. Cross-model handoffs reference formal requirements, designs, and plans by path and SHA-256; stale inputs, unresolved decisions, route changes, unsafe paths, and incomplete acceptance receipts fail closed.
|
|
52
|
+
- Keeps `.ai/config/model-routing.json` user-owned and `.ai/runs/` local-only. Exact account entitlements are not auto-discovered: Claude uses native model aliases, while Codex agents use native reasoning-effort settings.
|
|
53
|
+
|
|
54
|
+
### v0.3.0 — safe framework upgrades and engineering workflow
|
|
55
|
+
|
|
56
|
+
- Introduced `.ai/ai-memory.json` framework/schema metadata, file ownership, generated baseline hashes, legacy v0.1 detection, and explicit migration planning.
|
|
57
|
+
- Added `update --dry-run` and conflict-free `update --yes`. User memory, requirements, architecture, and design assets are preserved; changed framework or mixed files require review instead of being overwritten.
|
|
58
|
+
- Added duplicate-destination checks, path traversal and symlink/junction protection, three-phase scaffolding, and diagnostic partial-write summaries.
|
|
59
|
+
- Added the language-agnostic `project-inception`, `feature-design`, and `delivery-readiness` workflows covering data modeling, technical contracts, deployment, performance, observability, scalability, reliability, security, and cost.
|
|
60
|
+
|
|
61
|
+
### Upgrade from v0.3.0 to v0.4.0
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# Preview only; this must not modify the project
|
|
65
|
+
npx @betterdanlins/ai-memory@0.4.0 update --dry-run
|
|
66
|
+
|
|
67
|
+
# Apply only when the preview has no merge/review blockers
|
|
68
|
+
npx @betterdanlins/ai-memory@0.4.0 update --yes
|
|
69
|
+
|
|
70
|
+
# Optional: explicitly enable staged routing after the safe update
|
|
71
|
+
npx @betterdanlins/ai-memory@0.4.0 models configure --profile balanced
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
After the update, the profile remains `inherit` until explicitly changed. Do not re-run `init` on an existing v0.3.0 project. If dry-run reports merge or review items, merge them with the generated v0.4.0 files as references, then run dry-run again.
|
|
75
|
+
|
|
76
|
+
## What it generates
|
|
77
|
+
|
|
15
78
|
```
|
|
79
|
+
.ai/ # single source of truth, shared by all agents
|
|
80
|
+
├── README.md # session entry protocol
|
|
81
|
+
├── ai-memory.json # version, schema, tools, ownership, baseline hashes
|
|
82
|
+
├── config/
|
|
83
|
+
│ └── model-routing.json # inherit/balanced/quality stage routing; user-owned
|
|
84
|
+
├── memory/
|
|
85
|
+
│ ├── MEMORY.md # index — agents read this first
|
|
86
|
+
│ ├── project-state.md # stack, current version, requirement progress
|
|
87
|
+
│ ├── session-log.md # rolling log of progress & next steps
|
|
88
|
+
│ ├── user-profile.md # background, preferences, communication style
|
|
89
|
+
│ ├── feedback.md # behavioral norms distilled from feedback
|
|
90
|
+
│ └── features/ # per-feature dossiers
|
|
91
|
+
├── runs/ # local handoffs and stage receipts; ignored by Git
|
|
92
|
+
└── skills/ # methodology: requirements-flow, architecture,
|
|
93
|
+
# feature-design, model-routing, delivery-readiness, etc.
|
|
94
|
+
|
|
95
|
+
docs/architecture/ # engineering baseline for 0-to-1 projects
|
|
96
|
+
├── system-context.md # boundaries, actors, modules, and key flows
|
|
97
|
+
├── data-model.md # entities, ownership, consistency, and migration
|
|
98
|
+
├── quality-attributes.md # performance, observability, scalability, reliability
|
|
99
|
+
└── deployment.md # topology, releases, rollback, and operations
|
|
100
|
+
|
|
101
|
+
docs/requirements/vX.Y.Z/
|
|
102
|
+
├── draft/ # human-written rough requirements
|
|
103
|
+
└── final/ # AI-finalized specs (passes the critic gate)
|
|
104
|
+
|
|
105
|
+
docs/design/vX.Y.Z/ # M/L-risk feature how, technical contracts, deltas
|
|
106
|
+
|
|
107
|
+
# Thin adapters, generated only for the tools you enable:
|
|
108
|
+
CLAUDE.md + .claude/ # Claude Code: commands, skills, agents, settings
|
|
109
|
+
AGENTS.md + .agents/ + .codex/# Codex: skills and model-specific custom agents
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## CLI commands
|
|
16
113
|
|
|
17
|
-
|
|
114
|
+
| Command | Description |
|
|
115
|
+
| --- | --- |
|
|
116
|
+
| `init` | Initialize a new project; refuses to run over an existing ai-memory installation |
|
|
117
|
+
| `update --dry-run` | Read-only analysis of the upgrade plan from an existing project to this CLI version |
|
|
118
|
+
| `update --yes` | Apply conflict-free safe updates; refuse user changes, mixed-file conflicts, or missing migration paths |
|
|
119
|
+
| `models show` | Show the selected profile and resolved tier for every workflow stage |
|
|
120
|
+
| `models configure --profile <name>` | Select `inherit`, `balanced`, or `quality` without changing requirements, designs, or code |
|
|
121
|
+
| `workflow prepare` | Create a local handoff manifest with hashes of formal input documents |
|
|
122
|
+
| `workflow verify` | Reject missing, stale, unresolved, or route-mismatched handoffs before execution |
|
|
123
|
+
| `workflow complete` | Validate and store a structured stage receipt for downstream review |
|
|
18
124
|
|
|
19
|
-
|
|
125
|
+
### `init` options
|
|
126
|
+
|
|
127
|
+
| Option | Description |
|
|
128
|
+
| --- | --- |
|
|
129
|
+
| `--name <name>` | Project name (defaults to the current directory name) |
|
|
130
|
+
| `--stack <desc>` | Tech-stack description |
|
|
131
|
+
| `--tools <list>` | Comma-separated adapters to enable: `claude`, `codex` |
|
|
132
|
+
| `--model-profile <profile>` | `inherit` (default), `balanced`, or `quality`; `--yes` never opts into costly routing implicitly |
|
|
133
|
+
| `--import <path>` | Import `user-profile` / `feedback` from an existing project |
|
|
134
|
+
| `--yes` | Non-interactive: fill defaults, skip on any conflict |
|
|
135
|
+
|
|
136
|
+
## How it works
|
|
137
|
+
|
|
138
|
+
The generator is a small set of focused modules:
|
|
139
|
+
|
|
140
|
+
- **manifest** — walks the `common` / `claude` / `codex` template groups, keeping only the groups whose tools are enabled, and produces a flat `{src, dest}` list; duplicate destinations fail before writing and list every source.
|
|
141
|
+
- **render** — substitutes `{{variable}}` placeholders and throws on any undefined variable, so a broken template fails loudly instead of shipping `{{...}}`.
|
|
142
|
+
- **scaffold** — validates safe destinations and symbolic links, resolves conflicts, and reads/renders the complete plan in memory before writing; it also validates `--import` and pulls `user-profile` / `feedback` from it (falling back to the template with an explicit report when an individual source file is missing).
|
|
143
|
+
- **framework metadata** — a fresh init records framework/schema versions, ownership, and generated hashes; update uses them to distinguish safe updates from user changes. Metadata-free v0.1 projects are planned conservatively as legacy installations.
|
|
144
|
+
- **model routing** — optional profiles map existing workflow stages to premium, standard, economy, inherited, or no-model execution. Claude and Codex adapters use native custom-agent configuration; routing never creates extra S/M/L stages.
|
|
145
|
+
- **workflow handoff** — a local manifest references formal requirements/design/plan files by path and SHA-256. Verification fails closed on stale inputs, unresolved decisions, changed routing, path escape, or symbolic links.
|
|
146
|
+
|
|
147
|
+
## Design principles
|
|
148
|
+
|
|
149
|
+
- **Single source** — content lives only in `.ai/`; `.claude/` and `.agents/` are trigger wrappers.
|
|
150
|
+
- **Engineering-node memory** — write session-log entries for independently verifiable features/phases, durable decisions, status changes, or tool handoffs, avoiding noise from tiny implementation steps.
|
|
151
|
+
- **Dual requirement directories** — human draft (`draft/`) → AI final (`final/`) behind a mandatory critic gate.
|
|
152
|
+
- **Project engineering baseline** — 0-to-1 projects establish language-agnostic system, data, quality-attribute, and deployment baselines once; ordinary features record only deltas.
|
|
153
|
+
- **Risk-tiered design** — S-risk requirements proceed directly to implementation; M/L-risk work uses feature-design for how, technical contracts, and engineering impact, with Superpowers selected only when justified by risk.
|
|
154
|
+
- **Optional cost routing** — `inherit` preserves prior behavior; explicit profiles can use stronger models for planning/review and cheaper workers for bounded test work while deterministic test execution uses no model.
|
|
155
|
+
- **Artifact-based handoff** — cross-model stages read formal artifacts through a verified handoff rather than relying on conversational summaries; implementation receipts expose coverage, deviations, and unresolved risk to the final reviewer.
|
|
156
|
+
- **Evidence-based delivery** — before release, verify applicable contracts, tests, migration, rollback, performance, observability, and operations, producing an explicit readiness verdict.
|
|
157
|
+
- **Low-noise memory** — persist independently verifiable engineering nodes, durable decisions, status changes, and session handoffs instead of every small coding step.
|
|
158
|
+
- **Idempotent** — existing files are asked about one by one; `--yes` always skips. Never a silent overwrite.
|
|
159
|
+
- **Safe writes** — destinations must remain inside the target project, symbolic-link overwrites are rejected, and write failures retain a diagnostic progress summary.
|
|
160
|
+
- **Explicit compatibility** — newer CLIs can recognize older schemas, user assets are never overwritten from templates, and actual updates only touch baseline-matching files after a dry run rather than re-running init.
|
|
161
|
+
|
|
162
|
+
## Development
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
npm test # node --test test/*.test.js
|
|
166
|
+
```
|
|
20
167
|
|
|
21
|
-
|
|
22
|
-
- 节点式记忆:完成任务节点立即写 session-log,跨工具切换不断档
|
|
23
|
-
- 需求双目录:人工粗稿 `draft/` → AI 定稿 `final/`(强制 critic 门)
|
|
24
|
-
- 幂等:已存在的文件逐个询问,绝不静默覆盖(`--yes` 一律跳过)
|
|
168
|
+
## License
|
|
25
169
|
|
|
26
|
-
|
|
170
|
+
[MIT](LICENSE) © Betterdan
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# ai-memory
|
|
2
|
+
|
|
3
|
+
> 一条命令,为任意项目搭建一套共享的 AI 记忆层与需求工作流 —— 跨 Claude Code、Codex 及未来的 agent 通用。
|
|
4
|
+
|
|
5
|
+
[English](README.md) · [简体中文](README.zh-CN.md)
|
|
6
|
+
|
|
7
|
+
## 为什么
|
|
8
|
+
|
|
9
|
+
AI 编码 agent 在会话之间会丢失全部上下文,而每个工具(Claude Code、Codex……)又各有各的配置格式。`ai-memory` 同时解决这两个问题:
|
|
10
|
+
|
|
11
|
+
- **单一源。** 所有实际内容只存在于一份 agent 无关的 `.ai/` 目录里。工具专属目录(`.claude/`、`.agents/`)只是指回它的薄包装 —— 切换或新增工具都不会让你的知识库分叉。
|
|
12
|
+
- **跨会话不断档的记忆。** 结构化的记忆层记录上个会话完成到哪、需求整体进度、以及用户级偏好,让全新的 agent 能从上一个 agent 停下的地方精确接续。
|
|
13
|
+
- **带护栏的需求工作流。** 人工粗稿与 AI 定稿分别存放在带版本号的 `draft/` → `final/` 目录中,定稿前强制经过一道 “critic” 反驳评审门。
|
|
14
|
+
|
|
15
|
+
## 快速开始
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# 交互式
|
|
19
|
+
npx @betterdanlins/ai-memory init
|
|
20
|
+
|
|
21
|
+
# 非交互式
|
|
22
|
+
npx @betterdanlins/ai-memory init --name demo --stack "PHP + Vue" --tools claude,codex --yes
|
|
23
|
+
|
|
24
|
+
# 显式启用分阶段模型路由
|
|
25
|
+
npx @betterdanlins/ai-memory init --model-profile balanced --yes
|
|
26
|
+
|
|
27
|
+
# 从已有项目导入用户级记忆(profile/feedback)
|
|
28
|
+
npx @betterdanlins/ai-memory init --import /path/to/other-project
|
|
29
|
+
|
|
30
|
+
# 已初始化项目只读检查升级计划
|
|
31
|
+
npx @betterdanlins/ai-memory update --dry-run
|
|
32
|
+
|
|
33
|
+
# 只应用无冲突的安全更新;有用户修改时整体拒绝
|
|
34
|
+
npx @betterdanlins/ai-memory update --yes
|
|
35
|
+
|
|
36
|
+
# 已初始化项目从继承模型切换到分层路由
|
|
37
|
+
npx @betterdanlins/ai-memory models configure --profile balanced
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
绝不静默覆盖:已存在的文件会逐个询问(交互式),或直接跳过(`--yes`)。
|
|
41
|
+
`--import` 指向的项目目录不存在、不是目录或不可读时会直接报错;目录有效但某个记忆文件缺失时会回退到默认模板,并在执行摘要中明确提示。
|
|
42
|
+
生成前会检查重复模板目标、路径越界和符号链接;模板读取与渲染全部通过后才开始写入。若实际磁盘写入中途失败,错误摘要会列出已经写入和尚未写入的文件。
|
|
43
|
+
已初始化项目不能重新运行 `init`;新版 CLI 先通过 `update --dry-run` 识别版本和用户修改。`update --yes` 只应用新增和基线哈希匹配的安全更新,存在合并/审查项时在写入前整体拒绝。
|
|
44
|
+
|
|
45
|
+
## 版本与兼容性
|
|
46
|
+
|
|
47
|
+
### v0.4.0 —— 分阶段模型路由与可靠交接
|
|
48
|
+
|
|
49
|
+
- 新增可选的 `inherit`、`balanced`、`quality` profile。新项目和升级项目默认保持 `inherit`,安装 v0.4.0 不会静默增加模型成本,也不会改变 v0.3.0 的工作流深度。
|
|
50
|
+
- 为 Claude Code 和 Codex 新增原生分层 agents,覆盖高级规划/审查、中等或高级实现、低成本或中等测试编写。仍先由原有 S/M/L 流程决定是否需要某个阶段,模型路由只选择执行者。
|
|
51
|
+
- 新增 `models show/configure` 和 `workflow prepare/verify/complete`。跨模型交接通过路径和 SHA-256 引用正式需求、设计与计划;输入过期、未决问题、路由变化、不安全路径和验收回执缺失都会关闭式失败。
|
|
52
|
+
- `.ai/config/model-routing.json` 属于用户配置,`.ai/runs/` 只保存本地运行状态。框架不会自动探测账号模型权限:Claude 使用原生模型别名,Codex agents 使用原生推理等级。
|
|
53
|
+
|
|
54
|
+
### v0.3.0 —— 安全升级与工程工作流
|
|
55
|
+
|
|
56
|
+
- 引入 `.ai/ai-memory.json` 框架/Schema 元数据、文件所有权、生成基线哈希、v0.1 legacy 检测和显式迁移规划。
|
|
57
|
+
- 新增 `update --dry-run` 与无冲突 `update --yes`。用户记忆、需求、架构和设计资产始终保留;修改过的框架文件或混合文件进入人工审查,不会被直接覆盖。
|
|
58
|
+
- 增加重复目标、路径穿越、符号链接/junction 防护、三阶段生成和可诊断的部分写入摘要。
|
|
59
|
+
- 增加语言无关的 `project-inception`、`feature-design`、`delivery-readiness`,覆盖数据建模、技术接口、部署、性能、可观测性、扩展性、可靠性、安全和成本。
|
|
60
|
+
|
|
61
|
+
### 从 v0.3.0 升级到 v0.4.0
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# 只读预览,不得修改项目
|
|
65
|
+
npx @betterdanlins/ai-memory@0.4.0 update --dry-run
|
|
66
|
+
|
|
67
|
+
# 仅在预览没有 merge/review 阻塞项时应用
|
|
68
|
+
npx @betterdanlins/ai-memory@0.4.0 update --yes
|
|
69
|
+
|
|
70
|
+
# 可选:安全升级后显式启用分阶段模型路由
|
|
71
|
+
npx @betterdanlins/ai-memory@0.4.0 models configure --profile balanced
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
升级完成后 profile 仍为 `inherit`,只有显式配置才会改变。不要在已有 v0.3.0 项目重新运行 `init`。若 dry-run 出现 merge/review 项,应以新生成的 v0.4.0 文件为参考人工合并,然后重新执行 dry-run。
|
|
75
|
+
|
|
76
|
+
## 生成什么
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
.ai/ # 单一源,所有 agent 共享
|
|
80
|
+
├── README.md # 会话进场协议
|
|
81
|
+
├── ai-memory.json # 版本、Schema、工具、所有权和生成基线哈希
|
|
82
|
+
├── config/
|
|
83
|
+
│ └── model-routing.json # inherit/balanced/quality 阶段路由;属于用户配置
|
|
84
|
+
├── memory/
|
|
85
|
+
│ ├── MEMORY.md # 索引 —— agent 进场先读
|
|
86
|
+
│ ├── project-state.md # 技术栈、当前版本、需求进度
|
|
87
|
+
│ ├── session-log.md # 进展与下一步的流水日志
|
|
88
|
+
│ ├── user-profile.md # 背景、偏好、沟通方式
|
|
89
|
+
│ ├── feedback.md # 从反馈提炼的行为规范
|
|
90
|
+
│ └── features/ # 功能档案
|
|
91
|
+
├── runs/ # 本地交接与阶段回执;默认不进入 Git
|
|
92
|
+
└── skills/ # 方法论:requirements-flow、architecture、
|
|
93
|
+
# feature-design、model-routing、delivery-readiness 等
|
|
94
|
+
|
|
95
|
+
docs/architecture/ # 0→1 项目工程基线
|
|
96
|
+
├── system-context.md # 系统边界、参与者、模块和关键流程
|
|
97
|
+
├── data-model.md # 实体、关系、所有权、一致性和迁移
|
|
98
|
+
├── quality-attributes.md # 性能、可观测性、扩展性、可靠性等
|
|
99
|
+
└── deployment.md # 部署拓扑、发布、回滚和运行保障
|
|
100
|
+
|
|
101
|
+
docs/requirements/vX.Y.Z/
|
|
102
|
+
├── draft/ # 人工书写的粗稿需求
|
|
103
|
+
└── final/ # AI 定稿(通过 critic 门)
|
|
104
|
+
|
|
105
|
+
docs/design/vX.Y.Z/ # M/L 级功能 how、技术接口与工程增量
|
|
106
|
+
|
|
107
|
+
# 薄适配层,仅为你启用的工具生成:
|
|
108
|
+
CLAUDE.md + .claude/ # Claude Code:commands、skills、agents、settings
|
|
109
|
+
AGENTS.md + .agents/ + .codex/# Codex:skills 与分层自定义 agents
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## CLI 命令
|
|
113
|
+
|
|
114
|
+
| 命令 | 说明 |
|
|
115
|
+
| --- | --- |
|
|
116
|
+
| `init` | 初始化全新项目;检测到已有 ai-memory 安装时拒绝执行 |
|
|
117
|
+
| `update --dry-run` | 只读分析旧项目到当前 CLI 版本的升级计划 |
|
|
118
|
+
| `update --yes` | 应用无冲突安全更新;用户修改、混合文件冲突或缺失迁移路径会拒绝执行 |
|
|
119
|
+
| `models show` | 显示当前 profile 和每个阶段解析后的模型等级 |
|
|
120
|
+
| `models configure --profile <name>` | 选择 `inherit`、`balanced` 或 `quality`,不修改需求、设计或代码 |
|
|
121
|
+
| `workflow prepare` | 用正式输入文档的哈希创建本地交接清单 |
|
|
122
|
+
| `workflow verify` | 执行前拒绝缺失、过期、有未决问题或路由不匹配的交接 |
|
|
123
|
+
| `workflow complete` | 校验并保存供后续审查使用的结构化阶段回执 |
|
|
124
|
+
|
|
125
|
+
### `init` 选项
|
|
126
|
+
|
|
127
|
+
| 选项 | 说明 |
|
|
128
|
+
| --- | --- |
|
|
129
|
+
| `--name <name>` | 项目名(默认取当前目录名) |
|
|
130
|
+
| `--stack <desc>` | 技术栈描述 |
|
|
131
|
+
| `--tools <list>` | 逗号分隔的适配层:`claude`、`codex` |
|
|
132
|
+
| `--model-profile <profile>` | `inherit`(默认)、`balanced` 或 `quality`;`--yes` 不会静默启用高成本路由 |
|
|
133
|
+
| `--import <path>` | 从已有项目导入 `user-profile` / `feedback` |
|
|
134
|
+
| `--yes` | 非交互模式:缺参用默认值,冲突一律跳过 |
|
|
135
|
+
|
|
136
|
+
## 工作原理
|
|
137
|
+
|
|
138
|
+
生成器由一组职责明确的小模块构成:
|
|
139
|
+
|
|
140
|
+
- **manifest** —— 遍历 `common` / `claude` / `codex` 模板组,只保留已启用工具对应的组,产出一份扁平的 `{src, dest}` 清单;重复目标会在写入前报错并列出来源。
|
|
141
|
+
- **render** —— 替换 `{{变量}}` 占位符,遇到任何未定义变量即抛错,让坏掉的模板大声失败,而不是把 `{{...}}` 发出去。
|
|
142
|
+
- **scaffold** —— 预检安全目标路径和符号链接,处理冲突并在内存中完成全部读取/渲染后再逐个写入;提供 `--import` 时先校验导入目录,再从中拉取 `user-profile` / `feedback`(单个源文件缺失则回退到模板并明确报告)。
|
|
143
|
+
- **framework metadata** —— 全新 init 记录 framework/schema 版本、文件所有权和生成哈希;update 据此区分安全更新与用户修改。无元数据的 v0.1 项目按 legacy 保守规划。
|
|
144
|
+
- **model routing** —— 可选 profile 把现有阶段映射为 premium、standard、economy、inherit 或无模型执行;Claude/Codex 使用原生自定义 agent 配置,路由不会增加 S/M/L 阶段。
|
|
145
|
+
- **workflow handoff** —— 本地清单只用路径和 SHA-256 引用正式需求/设计/计划;输入过期、未决问题、路由变化、路径越界或符号链接都会关闭式失败。
|
|
146
|
+
|
|
147
|
+
## 设计原则
|
|
148
|
+
|
|
149
|
+
- **单一源** —— 正文只在 `.ai/`;`.claude/`、`.agents/` 只是触发包装。
|
|
150
|
+
- **工程节点记忆** —— 只在可独立验收的功能/阶段、关键决策、状态变化或工具切换时写 session-log,避免细碎步骤制造噪声。
|
|
151
|
+
- **需求双目录** —— 人工粗稿(`draft/`)→ AI 定稿(`final/`),中间隔一道强制 critic 门。
|
|
152
|
+
- **项目工程基线** —— 0→1 项目先建立语言无关的系统、数据、质量属性和部署基线;普通功能只记录增量,不重复设计全局架构。
|
|
153
|
+
- **风险分级设计** —— S 级需求直接实现;M/L 级用 feature-design 补齐 how、技术接口和工程影响,Superpowers 只按风险选择使用。
|
|
154
|
+
- **可选成本路由** —— `inherit` 保持旧行为;显式 profile 才用高级模型规划/审查和低成本 worker 做有边界的测试工作,确定性测试执行不调用模型。
|
|
155
|
+
- **工程产物交接** —— 跨模型阶段通过校验后的正式文档交接,不依赖对话摘要;阶段回执向最终 reviewer 暴露验收覆盖、设计偏差和未决风险。
|
|
156
|
+
- **证据化交付** —— 发布前按风险检查契约、测试、迁移、回滚、性能、可观测性和运行保障,输出明确 ready 状态。
|
|
157
|
+
- **低噪声记忆** —— 只在可验收工程节点、关键决策、状态变化和会话切换时落盘,不记录每个细碎代码步骤。
|
|
158
|
+
- **幂等** —— 已存在的文件逐个询问;`--yes` 一律跳过。绝不静默覆盖。
|
|
159
|
+
- **安全落盘** —— 目标必须位于项目目录内,拒绝跟随符号链接覆盖;写入失败会保留可诊断的进度摘要。
|
|
160
|
+
- **显式兼容** —— 新版 CLI 可识别旧 Schema,用户资产永不由模板覆盖;升级先 dry-run,实际更新只处理基线匹配文件,不把重新 init 当作更新。
|
|
161
|
+
|
|
162
|
+
## 开发
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
npm test # node --test test/*.test.js
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## 许可证
|
|
169
|
+
|
|
170
|
+
[MIT](LICENSE) © Betterdan
|
package/bin/cli.js
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import {
|
|
6
|
+
import { applyFrameworkUpdate, detectInstallation, METADATA_DEST, planFrameworkUpdate, writeFrameworkMetadata } from '../src/framework.js';
|
|
7
|
+
import {
|
|
8
|
+
createModelRoutingConfig, MODEL_PROFILES, readModelRoutingConfig,
|
|
9
|
+
resolveModelRouting, writeModelRoutingConfig,
|
|
10
|
+
} from '../src/model-routing.js';
|
|
11
|
+
import { ScaffoldError, scaffold } from '../src/scaffold.js';
|
|
12
|
+
import { prepareHandoff, recordStageResult, verifyHandoff } from '../src/workflow.js';
|
|
6
13
|
|
|
7
14
|
const TEMPLATES = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'templates');
|
|
15
|
+
const PACKAGE = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8'));
|
|
8
16
|
const program = new Command();
|
|
9
17
|
|
|
10
|
-
program.name('ai-memory').description('AI memory + requirements workflow scaffolder');
|
|
18
|
+
program.name('ai-memory').description('AI memory + requirements workflow scaffolder').version(PACKAGE.version);
|
|
11
19
|
|
|
12
20
|
program
|
|
13
21
|
.command('init')
|
|
@@ -15,11 +23,22 @@ program
|
|
|
15
23
|
.option('--name <name>', '项目名')
|
|
16
24
|
.option('--stack <desc>', '技术栈描述')
|
|
17
25
|
.option('--tools <list>', '启用工具,逗号分隔:claude,codex')
|
|
26
|
+
.option('--model-profile <profile>', '模型路由策略:inherit,balanced,quality', 'inherit')
|
|
18
27
|
.option('--import <path>', '从已有项目导入 user-profile/feedback')
|
|
19
28
|
.option('--yes', '非交互模式:缺参用默认值,冲突一律跳过')
|
|
20
29
|
.action(async (opts) => {
|
|
21
30
|
const targetDir = process.cwd();
|
|
31
|
+
const existingInstallation = await detectInstallation(targetDir);
|
|
32
|
+
if (existingInstallation.kind !== 'none') {
|
|
33
|
+
const label = existingInstallation.kind === 'legacy'
|
|
34
|
+
? '检测到无元数据的 v0.1.0 legacy 项目'
|
|
35
|
+
: `项目已由 ai-memory ${existingInstallation.metadata.frameworkVersion} 初始化`;
|
|
36
|
+
throw new Error(`${label};不要重新运行 init,请先执行 ai-memory update --dry-run`);
|
|
37
|
+
}
|
|
22
38
|
let { name, stack, tools: toolsRaw } = opts;
|
|
39
|
+
if (!MODEL_PROFILES.includes(opts.modelProfile)) {
|
|
40
|
+
throw new Error(`model profile 仅支持 ${MODEL_PROFILES.join('、')},收到: ${opts.modelProfile}`);
|
|
41
|
+
}
|
|
23
42
|
|
|
24
43
|
// Validate CLI-supplied --tools before entering interactive prompts
|
|
25
44
|
if (toolsRaw !== undefined) {
|
|
@@ -33,7 +52,7 @@ program
|
|
|
33
52
|
|
|
34
53
|
let importFrom = opts.import;
|
|
35
54
|
if (!opts.yes) {
|
|
36
|
-
const { input, checkbox } = await import('@inquirer/prompts');
|
|
55
|
+
const { input, checkbox, select } = await import('@inquirer/prompts');
|
|
37
56
|
name ??= await input({ message: '项目名:', default: path.basename(targetDir) });
|
|
38
57
|
stack ??= await input({ message: '技术栈描述:', default: '(待补充)' });
|
|
39
58
|
toolsRaw ??= (await checkbox({
|
|
@@ -46,12 +65,23 @@ program
|
|
|
46
65
|
importFrom ??= (await input({
|
|
47
66
|
message: '从已有项目导入 user-profile/feedback?(输入项目路径,留空跳过)', default: '',
|
|
48
67
|
})) || undefined;
|
|
68
|
+
if (opts.modelProfile === 'inherit') {
|
|
69
|
+
opts.modelProfile = await select({
|
|
70
|
+
message: '模型路由策略:',
|
|
71
|
+
choices: [
|
|
72
|
+
{ name: 'inherit - 继承当前会话模型,兼容旧行为', value: 'inherit' },
|
|
73
|
+
{ name: 'balanced - 高级规划/中等实现/便宜测试/高级审查', value: 'balanced' },
|
|
74
|
+
{ name: 'quality - 关键实现与诊断也使用高级模型', value: 'quality' },
|
|
75
|
+
],
|
|
76
|
+
default: 'inherit',
|
|
77
|
+
});
|
|
78
|
+
}
|
|
49
79
|
}
|
|
50
80
|
name ??= path.basename(targetDir);
|
|
51
81
|
stack ??= '(待补充)';
|
|
52
82
|
toolsRaw ??= 'claude,codex';
|
|
53
83
|
|
|
54
|
-
const tools = toolsRaw.split(',').map(s => s.trim()).filter(Boolean);
|
|
84
|
+
const tools = [...new Set(toolsRaw.split(',').map(s => s.trim()).filter(Boolean))];
|
|
55
85
|
if (tools.length === 0) {
|
|
56
86
|
console.log('未启用任何工具适配,仅生成 .ai/ 通用层');
|
|
57
87
|
}
|
|
@@ -63,18 +93,172 @@ program
|
|
|
63
93
|
return (await confirm({ message: `${dest} 已存在,覆盖?`, default: false })) ? 'overwrite' : 'skip';
|
|
64
94
|
};
|
|
65
95
|
|
|
66
|
-
const
|
|
96
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
97
|
+
const vars = {
|
|
98
|
+
projectName: name, techStack: stack, date, modelProfile: opts.modelProfile, frameworkVersion: PACKAGE.version,
|
|
99
|
+
};
|
|
67
100
|
const r = await scaffold({ templatesRoot: TEMPLATES, targetDir, vars, tools, onConflict, importFrom });
|
|
101
|
+
if (r.skipped.length === 0) {
|
|
102
|
+
try {
|
|
103
|
+
await writeFrameworkMetadata({
|
|
104
|
+
targetDir, templatesRoot: TEMPLATES, frameworkVersion: PACKAGE.version,
|
|
105
|
+
tools, projectName: name, techStack: stack, date,
|
|
106
|
+
});
|
|
107
|
+
r.written.push(METADATA_DEST);
|
|
108
|
+
} catch (err) {
|
|
109
|
+
throw new ScaffoldError({
|
|
110
|
+
phase: 'write', dest: METADATA_DEST, destPath: path.join(targetDir, '.ai', 'ai-memory.json'),
|
|
111
|
+
cause: err, summary: r, notWritten: [METADATA_DEST],
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
console.log('警告:存在冲突跳过项,未创建框架元数据;当前结果不能自动升级');
|
|
116
|
+
}
|
|
68
117
|
console.log(`完成:写入 ${r.written.length},跳过 ${r.skipped.length}`);
|
|
118
|
+
console.log(`模型路由:${opts.modelProfile}`);
|
|
119
|
+
if (r.imported.length) for (const d of r.imported) console.log(` 已导入 ${d}`);
|
|
120
|
+
if (r.fallback.length) for (const d of r.fallback) console.log(` 导入源缺失,使用模板 ${d}`);
|
|
69
121
|
if (r.skipped.length) for (const d of r.skipped) console.log(` 跳过 ${d}`);
|
|
70
122
|
console.log('下一步:打开项目让 agent 读 .ai/memory/MEMORY.md;把项目命令补进 CLAUDE.md/AGENTS.md。');
|
|
71
123
|
});
|
|
72
124
|
|
|
125
|
+
const models = program.command('models').description('查看或配置阶段模型路由');
|
|
126
|
+
|
|
127
|
+
models
|
|
128
|
+
.command('show')
|
|
129
|
+
.description('显示当前模型路由配置与阶段映射')
|
|
130
|
+
.action(async () => {
|
|
131
|
+
const config = await readModelRoutingConfig(process.cwd());
|
|
132
|
+
console.log(`模型路由:${config.profile}`);
|
|
133
|
+
for (const [stage, tier] of Object.entries(resolveModelRouting(config))) console.log(` ${stage}: ${tier}`);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
models
|
|
137
|
+
.command('configure')
|
|
138
|
+
.description('切换模型路由预设;不修改需求、设计或代码')
|
|
139
|
+
.requiredOption('--profile <profile>', 'inherit,balanced,quality')
|
|
140
|
+
.action(async (opts) => {
|
|
141
|
+
const installation = await detectInstallation(process.cwd());
|
|
142
|
+
if (installation.kind === 'none') throw new Error('当前目录不是 ai-memory 项目,请先运行 init');
|
|
143
|
+
const current = await readModelRoutingConfig(process.cwd());
|
|
144
|
+
const next = createModelRoutingConfig(opts.profile);
|
|
145
|
+
next.overrides = current.overrides;
|
|
146
|
+
await writeModelRoutingConfig(process.cwd(), next);
|
|
147
|
+
console.log(`模型路由已切换:${current.profile} → ${next.profile}`);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const workflow = program.command('workflow').description('创建并校验跨模型阶段交接');
|
|
151
|
+
|
|
152
|
+
workflow
|
|
153
|
+
.command('prepare')
|
|
154
|
+
.description('根据正式文档生成带哈希的本地交接清单')
|
|
155
|
+
.requiredOption('--feature <id>', '功能标识,不得包含路径分隔符')
|
|
156
|
+
.requiredOption('--stage <stage>', '目标工作流阶段')
|
|
157
|
+
.requiredOption('--risk <level>', 'S,M,L')
|
|
158
|
+
.option('--input <path>', '正式输入文件,可重复', collect, [])
|
|
159
|
+
.option('--acceptance <id>', '验收标准编号,可重复', collect, [])
|
|
160
|
+
.option('--scope <path>', '允许变更范围,可重复', collect, [])
|
|
161
|
+
.option('--unresolved <text>', '未决问题,可重复;存在时 verify 会阻止进入阶段', collect, [])
|
|
162
|
+
.action(async (opts) => {
|
|
163
|
+
const result = await prepareHandoff({
|
|
164
|
+
targetDir: process.cwd(), feature: opts.feature, stage: opts.stage, risk: opts.risk,
|
|
165
|
+
inputs: opts.input, acceptanceCriteria: opts.acceptance, allowedChangeScope: opts.scope,
|
|
166
|
+
unresolvedDecisions: opts.unresolved,
|
|
167
|
+
});
|
|
168
|
+
console.log(`交接清单已生成:${result.path}`);
|
|
169
|
+
console.log(`执行等级:${result.handoff.executorTier}`);
|
|
170
|
+
if (result.handoff.unresolvedDecisions.length) console.log(`警告:仍有 ${result.handoff.unresolvedDecisions.length} 个未决问题`);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
workflow
|
|
174
|
+
.command('verify')
|
|
175
|
+
.description('验证交接输入、哈希、路由和未决问题')
|
|
176
|
+
.requiredOption('--feature <id>', '功能标识')
|
|
177
|
+
.option('--stage <stage>', '期望工作流阶段')
|
|
178
|
+
.action(async (opts) => {
|
|
179
|
+
const handoff = await verifyHandoff({ targetDir: process.cwd(), feature: opts.feature, expectedStage: opts.stage });
|
|
180
|
+
console.log(`交接校验通过:${handoff.featureId} / ${handoff.stage} / ${handoff.executorTier}`);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
workflow
|
|
184
|
+
.command('complete')
|
|
185
|
+
.description('校验阶段回执并保存到对应 run 目录')
|
|
186
|
+
.requiredOption('--feature <id>', '功能标识')
|
|
187
|
+
.requiredOption('--result <path>', '项目内阶段回执 JSON 路径')
|
|
188
|
+
.action(async (opts) => {
|
|
189
|
+
const recorded = await recordStageResult({ targetDir: process.cwd(), feature: opts.feature, resultPath: opts.result });
|
|
190
|
+
console.log(`阶段回执已记录:${recorded.path} / ${recorded.result.status}`);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
program
|
|
194
|
+
.command('update')
|
|
195
|
+
.description('检查当前项目升级到本 CLI 版本所需的变更')
|
|
196
|
+
.option('--dry-run', '只输出升级计划,不修改任何文件')
|
|
197
|
+
.option('--yes', '应用无冲突的安全更新;存在合并/审查项时拒绝写入')
|
|
198
|
+
.action(async (opts) => {
|
|
199
|
+
if (opts.dryRun === opts.yes) throw new Error('update 必须且只能指定 --dry-run 或 --yes');
|
|
200
|
+
const plan = await planFrameworkUpdate({
|
|
201
|
+
targetDir: process.cwd(), templatesRoot: TEMPLATES, frameworkVersion: PACKAGE.version,
|
|
202
|
+
});
|
|
203
|
+
printUpdatePlan(plan);
|
|
204
|
+
if (opts.yes) {
|
|
205
|
+
const result = await applyFrameworkUpdate({ targetDir: process.cwd(), templatesRoot: TEMPLATES, plan });
|
|
206
|
+
console.log(`安全更新完成:写入 ${result.written.length}`);
|
|
207
|
+
for (const dest of result.written) console.log(` ${dest}`);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
73
211
|
program.parseAsync().catch((e) => {
|
|
74
212
|
if (e.name === 'ExitPromptError') {
|
|
75
213
|
console.log('已取消');
|
|
76
214
|
} else {
|
|
77
215
|
console.error('错误: ' + e.message);
|
|
216
|
+
if (e.name === 'ScaffoldError') {
|
|
217
|
+
console.error(`已写入 ${e.summary.written.length}:`);
|
|
218
|
+
for (const d of e.summary.written) console.error(` ${d}`);
|
|
219
|
+
console.error(`尚未写入 ${e.notWritten.length}:`);
|
|
220
|
+
for (const d of e.notWritten) console.error(` ${d}`);
|
|
221
|
+
if (e.summary.skipped.length) {
|
|
222
|
+
console.error(`已跳过 ${e.summary.skipped.length}:`);
|
|
223
|
+
for (const d of e.summary.skipped) console.error(` ${d}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
78
226
|
}
|
|
79
227
|
process.exit(1);
|
|
80
228
|
});
|
|
229
|
+
|
|
230
|
+
function printUpdatePlan(plan) {
|
|
231
|
+
const current = plan.installation.kind === 'legacy'
|
|
232
|
+
? 'v0.1.0 legacy(无元数据)'
|
|
233
|
+
: `${plan.installation.metadata.frameworkVersion} / schema ${plan.installation.metadata.schemaVersion}`;
|
|
234
|
+
console.log(`当前:${current}`);
|
|
235
|
+
console.log(`目标:${plan.frameworkVersion} / schema ${plan.schemaVersion}`);
|
|
236
|
+
console.log(`工具:${plan.tools.length ? plan.tools.join(',') : '(仅 common)'}`);
|
|
237
|
+
|
|
238
|
+
const groups = [
|
|
239
|
+
['add', '新增'],
|
|
240
|
+
['update', '可安全更新'],
|
|
241
|
+
['merge', '需要合并'],
|
|
242
|
+
['review', '需要人工审查'],
|
|
243
|
+
['review-remove', '需要确认移除'],
|
|
244
|
+
['preserve', '保留用户资产'],
|
|
245
|
+
['unchanged', '无需变化'],
|
|
246
|
+
];
|
|
247
|
+
for (const [action, label] of groups) {
|
|
248
|
+
const items = plan.actions.filter(item => item.action === action);
|
|
249
|
+
console.log(`${label} ${items.length}`);
|
|
250
|
+
if (action !== 'unchanged') for (const item of items) console.log(` ${item.dest}`);
|
|
251
|
+
}
|
|
252
|
+
if (plan.migrations.length) {
|
|
253
|
+
console.log(`Schema 迁移 ${plan.migrations.length}(当前仅规划,不执行)`);
|
|
254
|
+
for (const migration of plan.migrations) {
|
|
255
|
+
console.log(` ${migration.id}: ${migration.from} → ${migration.to}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
console.log('升级计划完成');
|
|
259
|
+
if (process.argv.includes('--dry-run')) console.log('dry-run:未修改任何文件');
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function collect(value, previous) {
|
|
263
|
+
return [...previous, value];
|
|
264
|
+
}
|