@betterdanlins/ai-memory 0.3.0 → 0.5.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.
Files changed (33) hide show
  1. package/README.md +71 -3
  2. package/README.zh-CN.md +71 -3
  3. package/bin/cli.js +105 -3
  4. package/package.json +1 -1
  5. package/src/framework.js +57 -17
  6. package/src/managed-blocks.js +62 -0
  7. package/src/manifest.js +7 -1
  8. package/src/model-routing.js +108 -0
  9. package/src/workflow.js +185 -0
  10. package/templates/claude/.claude/agents/economy-test-worker.md +8 -0
  11. package/templates/claude/.claude/agents/premium-implementer.md +8 -0
  12. package/templates/claude/.claude/agents/premium-planner.md +8 -0
  13. package/templates/claude/.claude/agents/premium-reviewer.md +8 -0
  14. package/templates/claude/.claude/agents/standard-implementer.md +8 -0
  15. package/templates/claude/.claude/agents/standard-test-worker.md +8 -0
  16. package/templates/claude/.claude/skills/model-routing/SKILL.md +6 -0
  17. package/templates/claude/CLAUDE.md +6 -0
  18. package/templates/codex/.agents/skills/model-routing/SKILL.md +6 -0
  19. package/templates/codex/.codex/agents/economy_test_worker.toml +6 -0
  20. package/templates/codex/.codex/agents/premium_implementer.toml +6 -0
  21. package/templates/codex/.codex/agents/premium_planner.toml +6 -0
  22. package/templates/codex/.codex/agents/premium_reviewer.toml +6 -0
  23. package/templates/codex/.codex/agents/standard_implementer.toml +6 -0
  24. package/templates/codex/.codex/agents/standard_test_worker.toml +6 -0
  25. package/templates/codex/AGENTS.md +6 -0
  26. package/templates/common/.ai/README.md +4 -1
  27. package/templates/common/.ai/config/model-routing.json +5 -0
  28. package/templates/common/.ai/runs/.gitignore.template +2 -0
  29. package/templates/common/.ai/skills/critic.md +1 -0
  30. package/templates/common/.ai/skills/delivery-readiness.md +2 -0
  31. package/templates/common/.ai/skills/feature-design.md +2 -0
  32. package/templates/common/.ai/skills/model-routing.md +39 -0
  33. package/templates/common/.ai/skills/requirements-flow.md +2 -0
package/README.md CHANGED
@@ -21,6 +21,9 @@ npx @betterdanlins/ai-memory init
21
21
  # Non-interactive
22
22
  npx @betterdanlins/ai-memory init --name demo --stack "PHP + Vue" --tools claude,codex --yes
23
23
 
24
+ # Opt in to staged model routing
25
+ npx @betterdanlins/ai-memory init --model-profile balanced --yes
26
+
24
27
  # Import user-level memory (profile/feedback) from an existing project
25
28
  npx @betterdanlins/ai-memory init --import /path/to/other-project
26
29
 
@@ -29,6 +32,9 @@ npx @betterdanlins/ai-memory update --dry-run
29
32
 
30
33
  # Apply only conflict-free safe updates; abort entirely on user changes
31
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
32
38
  ```
33
39
 
34
40
  Nothing is ever overwritten silently: existing files are asked about one by one (interactive) or skipped (`--yes`).
@@ -36,12 +42,62 @@ An invalid, non-directory, or unreadable `--import` project path fails explicitl
36
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.
37
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.
38
44
 
45
+ ## Versions and compatibility
46
+
47
+ ### v0.5.0 — managed entry blocks
48
+
49
+ - Wraps the framework-owned sections of generated `AGENTS.md` and `CLAUDE.md` in explicit `ai-memory:managed` markers. Future updates replace only that block and preserve project commands and custom rules outside it.
50
+ - An unchanged v0.4.0 entry file can migrate to marked blocks automatically because its generated baseline hash proves it has not been edited. A modified unmarked v0.4.0 file requires one manual merge; after markers are established, later framework updates no longer conflict with user-section changes.
51
+ - Missing, duplicated, or malformed markers fail conservatively instead of triggering a whole-file overwrite. Managed-block updates also re-check the current file hash immediately before writing.
52
+ - `.claude/settings.json` remains a mixed JSON file and still requires review when both the framework and user changed it; Markdown markers are intentionally not applied to JSON.
53
+ - Fixes npm's automatic `.gitignore` template renaming and safely replaces the unchanged v0.4.0 `.ai/runs/.npmignore`; a locally edited obsolete file still requires review.
54
+
55
+ ### v0.4.0 — staged model routing and verified handoffs
56
+
57
+ - 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.
58
+ - 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.
59
+ - 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.
60
+ - 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.
61
+
62
+ ### v0.3.0 — safe framework upgrades and engineering workflow
63
+
64
+ - Introduced `.ai/ai-memory.json` framework/schema metadata, file ownership, generated baseline hashes, legacy v0.1 detection, and explicit migration planning.
65
+ - 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.
66
+ - Added duplicate-destination checks, path traversal and symlink/junction protection, three-phase scaffolding, and diagnostic partial-write summaries.
67
+ - 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.
68
+
69
+ ### Upgrade from v0.3.0 to v0.4.0
70
+
71
+ ```bash
72
+ # Preview only; this must not modify the project
73
+ npx @betterdanlins/ai-memory@0.4.0 update --dry-run
74
+
75
+ # Apply only when the preview has no merge/review blockers
76
+ npx @betterdanlins/ai-memory@0.4.0 update --yes
77
+
78
+ # Optional: explicitly enable staged routing after the safe update
79
+ npx @betterdanlins/ai-memory@0.4.0 models configure --profile balanced
80
+ ```
81
+
82
+ 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.
83
+
84
+ ### Upgrade from v0.4.0 to v0.5.0
85
+
86
+ ```bash
87
+ npx @betterdanlins/ai-memory@0.5.0 update --dry-run
88
+ npx @betterdanlins/ai-memory@0.5.0 update --yes
89
+ ```
90
+
91
+ If `AGENTS.md` or `CLAUDE.md` is still the unchanged v0.4.0 generated version, it migrates automatically. If either unmarked file was customized, dry-run reports `merge`; compare it with a fresh v0.5.0 reference, keep project-specific content in the user block, and retry. Once markers exist, edit only outside `<!-- ai-memory:managed:start/end -->`.
92
+
39
93
  ## What it generates
40
94
 
41
95
  ```
42
96
  .ai/ # single source of truth, shared by all agents
43
97
  ├── README.md # session entry protocol
44
98
  ├── ai-memory.json # version, schema, tools, ownership, baseline hashes
99
+ ├── config/
100
+ │ └── model-routing.json # inherit/balanced/quality stage routing; user-owned
45
101
  ├── memory/
46
102
  │ ├── MEMORY.md # index — agents read this first
47
103
  │ ├── project-state.md # stack, current version, requirement progress
@@ -49,8 +105,9 @@ An initialized project cannot be initialized again. A newer CLI first uses `upda
49
105
  │ ├── user-profile.md # background, preferences, communication style
50
106
  │ ├── feedback.md # behavioral norms distilled from feedback
51
107
  │ └── features/ # per-feature dossiers
108
+ ├── runs/ # local handoffs and stage receipts; ignored by Git
52
109
  └── skills/ # methodology: requirements-flow, architecture,
53
- # project-inception, feature-design, delivery-readiness, etc.
110
+ # feature-design, model-routing, delivery-readiness, etc.
54
111
 
55
112
  docs/architecture/ # engineering baseline for 0-to-1 projects
56
113
  ├── system-context.md # boundaries, actors, modules, and key flows
@@ -66,7 +123,7 @@ docs/design/vX.Y.Z/ # M/L-risk feature how, technical contracts, delta
66
123
 
67
124
  # Thin adapters, generated only for the tools you enable:
68
125
  CLAUDE.md + .claude/ # Claude Code: commands, skills, agents, settings
69
- AGENTS.md + .agents/ # Codex: skills
126
+ AGENTS.md + .agents/ + .codex/# Codex: skills and model-specific custom agents
70
127
  ```
71
128
 
72
129
  ## CLI commands
@@ -76,6 +133,11 @@ AGENTS.md + .agents/ # Codex: skills
76
133
  | `init` | Initialize a new project; refuses to run over an existing ai-memory installation |
77
134
  | `update --dry-run` | Read-only analysis of the upgrade plan from an existing project to this CLI version |
78
135
  | `update --yes` | Apply conflict-free safe updates; refuse user changes, mixed-file conflicts, or missing migration paths |
136
+ | `models show` | Show the selected profile and resolved tier for every workflow stage |
137
+ | `models configure --profile <name>` | Select `inherit`, `balanced`, or `quality` without changing requirements, designs, or code |
138
+ | `workflow prepare` | Create a local handoff manifest with hashes of formal input documents |
139
+ | `workflow verify` | Reject missing, stale, unresolved, or route-mismatched handoffs before execution |
140
+ | `workflow complete` | Validate and store a structured stage receipt for downstream review |
79
141
 
80
142
  ### `init` options
81
143
 
@@ -84,17 +146,21 @@ AGENTS.md + .agents/ # Codex: skills
84
146
  | `--name <name>` | Project name (defaults to the current directory name) |
85
147
  | `--stack <desc>` | Tech-stack description |
86
148
  | `--tools <list>` | Comma-separated adapters to enable: `claude`, `codex` |
149
+ | `--model-profile <profile>` | `inherit` (default), `balanced`, or `quality`; `--yes` never opts into costly routing implicitly |
87
150
  | `--import <path>` | Import `user-profile` / `feedback` from an existing project |
88
151
  | `--yes` | Non-interactive: fill defaults, skip on any conflict |
89
152
 
90
153
  ## How it works
91
154
 
92
- The generator is a small set of pure functions:
155
+ The generator is a small set of focused modules:
93
156
 
94
157
  - **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.
95
158
  - **render** — substitutes `{{variable}}` placeholders and throws on any undefined variable, so a broken template fails loudly instead of shipping `{{...}}`.
96
159
  - **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).
97
160
  - **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.
161
+ - **managed entry blocks** — Markdown entry files update only their validated framework block. Unmarked or malformed customized files remain manual merges; JSON settings keep conservative whole-file review semantics.
162
+ - **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.
163
+ - **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.
98
164
 
99
165
  ## Design principles
100
166
 
@@ -103,6 +169,8 @@ The generator is a small set of pure functions:
103
169
  - **Dual requirement directories** — human draft (`draft/`) → AI final (`final/`) behind a mandatory critic gate.
104
170
  - **Project engineering baseline** — 0-to-1 projects establish language-agnostic system, data, quality-attribute, and deployment baselines once; ordinary features record only deltas.
105
171
  - **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.
172
+ - **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.
173
+ - **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.
106
174
  - **Evidence-based delivery** — before release, verify applicable contracts, tests, migration, rollback, performance, observability, and operations, producing an explicit readiness verdict.
107
175
  - **Low-noise memory** — persist independently verifiable engineering nodes, durable decisions, status changes, and session handoffs instead of every small coding step.
108
176
  - **Idempotent** — existing files are asked about one by one; `--yes` always skips. Never a silent overwrite.
package/README.zh-CN.md CHANGED
@@ -21,6 +21,9 @@ npx @betterdanlins/ai-memory init
21
21
  # 非交互式
22
22
  npx @betterdanlins/ai-memory init --name demo --stack "PHP + Vue" --tools claude,codex --yes
23
23
 
24
+ # 显式启用分阶段模型路由
25
+ npx @betterdanlins/ai-memory init --model-profile balanced --yes
26
+
24
27
  # 从已有项目导入用户级记忆(profile/feedback)
25
28
  npx @betterdanlins/ai-memory init --import /path/to/other-project
26
29
 
@@ -29,6 +32,9 @@ npx @betterdanlins/ai-memory update --dry-run
29
32
 
30
33
  # 只应用无冲突的安全更新;有用户修改时整体拒绝
31
34
  npx @betterdanlins/ai-memory update --yes
35
+
36
+ # 已初始化项目从继承模型切换到分层路由
37
+ npx @betterdanlins/ai-memory models configure --profile balanced
32
38
  ```
33
39
 
34
40
  绝不静默覆盖:已存在的文件会逐个询问(交互式),或直接跳过(`--yes`)。
@@ -36,12 +42,62 @@ npx @betterdanlins/ai-memory update --yes
36
42
  生成前会检查重复模板目标、路径越界和符号链接;模板读取与渲染全部通过后才开始写入。若实际磁盘写入中途失败,错误摘要会列出已经写入和尚未写入的文件。
37
43
  已初始化项目不能重新运行 `init`;新版 CLI 先通过 `update --dry-run` 识别版本和用户修改。`update --yes` 只应用新增和基线哈希匹配的安全更新,存在合并/审查项时在写入前整体拒绝。
38
44
 
45
+ ## 版本与兼容性
46
+
47
+ ### v0.5.0 —— 入口文件受管区块
48
+
49
+ - 为生成的 `AGENTS.md` 和 `CLAUDE.md` 框架区域增加明确的 `ai-memory:managed` 标记。后续升级只替换该区块,区块外的项目命令和自定义规则保持不变。
50
+ - 未修改的 v0.4.0 入口文件可依据生成基线哈希自动迁移为带标记结构。已经修改且没有标记的 v0.4.0 文件需要一次人工合并;标记建立后,后续框架升级不再与用户区块修改冲突。
51
+ - 标记缺失、重复或格式损坏时保守拒绝自动合并,不会退化为整文件覆盖。写入受管区块前还会再次核对当前文件哈希。
52
+ - `.claude/settings.json` 仍是混合 JSON 文件;框架和用户都修改时继续要求审查,不会向 JSON 强行加入 Markdown 标记。
53
+ - 修复 npm 自动改名 `.gitignore` 模板的问题,并安全替换未修改的 v0.4.0 `.ai/runs/.npmignore`;若该废弃文件被本地修改,仍要求人工审查。
54
+
55
+ ### v0.4.0 —— 分阶段模型路由与可靠交接
56
+
57
+ - 新增可选的 `inherit`、`balanced`、`quality` profile。新项目和升级项目默认保持 `inherit`,安装 v0.4.0 不会静默增加模型成本,也不会改变 v0.3.0 的工作流深度。
58
+ - 为 Claude Code 和 Codex 新增原生分层 agents,覆盖高级规划/审查、中等或高级实现、低成本或中等测试编写。仍先由原有 S/M/L 流程决定是否需要某个阶段,模型路由只选择执行者。
59
+ - 新增 `models show/configure` 和 `workflow prepare/verify/complete`。跨模型交接通过路径和 SHA-256 引用正式需求、设计与计划;输入过期、未决问题、路由变化、不安全路径和验收回执缺失都会关闭式失败。
60
+ - `.ai/config/model-routing.json` 属于用户配置,`.ai/runs/` 只保存本地运行状态。框架不会自动探测账号模型权限:Claude 使用原生模型别名,Codex agents 使用原生推理等级。
61
+
62
+ ### v0.3.0 —— 安全升级与工程工作流
63
+
64
+ - 引入 `.ai/ai-memory.json` 框架/Schema 元数据、文件所有权、生成基线哈希、v0.1 legacy 检测和显式迁移规划。
65
+ - 新增 `update --dry-run` 与无冲突 `update --yes`。用户记忆、需求、架构和设计资产始终保留;修改过的框架文件或混合文件进入人工审查,不会被直接覆盖。
66
+ - 增加重复目标、路径穿越、符号链接/junction 防护、三阶段生成和可诊断的部分写入摘要。
67
+ - 增加语言无关的 `project-inception`、`feature-design`、`delivery-readiness`,覆盖数据建模、技术接口、部署、性能、可观测性、扩展性、可靠性、安全和成本。
68
+
69
+ ### 从 v0.3.0 升级到 v0.4.0
70
+
71
+ ```bash
72
+ # 只读预览,不得修改项目
73
+ npx @betterdanlins/ai-memory@0.4.0 update --dry-run
74
+
75
+ # 仅在预览没有 merge/review 阻塞项时应用
76
+ npx @betterdanlins/ai-memory@0.4.0 update --yes
77
+
78
+ # 可选:安全升级后显式启用分阶段模型路由
79
+ npx @betterdanlins/ai-memory@0.4.0 models configure --profile balanced
80
+ ```
81
+
82
+ 升级完成后 profile 仍为 `inherit`,只有显式配置才会改变。不要在已有 v0.3.0 项目重新运行 `init`。若 dry-run 出现 merge/review 项,应以新生成的 v0.4.0 文件为参考人工合并,然后重新执行 dry-run。
83
+
84
+ ### 从 v0.4.0 升级到 v0.5.0
85
+
86
+ ```bash
87
+ npx @betterdanlins/ai-memory@0.5.0 update --dry-run
88
+ npx @betterdanlins/ai-memory@0.5.0 update --yes
89
+ ```
90
+
91
+ 如果 `AGENTS.md` 或 `CLAUDE.md` 仍是未修改的 v0.4.0 生成版本,会自动迁移。若无标记入口已被定制,dry-run 会报告 `merge`;应对照全新 v0.5.0 参考文件,把项目内容放进 user 区块后重试。标记建立后,只在 `<!-- ai-memory:managed:start/end -->` 之外编辑。
92
+
39
93
  ## 生成什么
40
94
 
41
95
  ```
42
96
  .ai/ # 单一源,所有 agent 共享
43
97
  ├── README.md # 会话进场协议
44
98
  ├── ai-memory.json # 版本、Schema、工具、所有权和生成基线哈希
99
+ ├── config/
100
+ │ └── model-routing.json # inherit/balanced/quality 阶段路由;属于用户配置
45
101
  ├── memory/
46
102
  │ ├── MEMORY.md # 索引 —— agent 进场先读
47
103
  │ ├── project-state.md # 技术栈、当前版本、需求进度
@@ -49,8 +105,9 @@ npx @betterdanlins/ai-memory update --yes
49
105
  │ ├── user-profile.md # 背景、偏好、沟通方式
50
106
  │ ├── feedback.md # 从反馈提炼的行为规范
51
107
  │ └── features/ # 功能档案
108
+ ├── runs/ # 本地交接与阶段回执;默认不进入 Git
52
109
  └── skills/ # 方法论:requirements-flow、architecture、
53
- # project-inception、feature-design、delivery-readiness 等
110
+ # feature-design、model-routing、delivery-readiness 等
54
111
 
55
112
  docs/architecture/ # 0→1 项目工程基线
56
113
  ├── system-context.md # 系统边界、参与者、模块和关键流程
@@ -66,7 +123,7 @@ docs/design/vX.Y.Z/ # M/L 级功能 how、技术接口与工程增量
66
123
 
67
124
  # 薄适配层,仅为你启用的工具生成:
68
125
  CLAUDE.md + .claude/ # Claude Code:commands、skills、agents、settings
69
- AGENTS.md + .agents/ # Codex:skills
126
+ AGENTS.md + .agents/ + .codex/# Codex:skills 与分层自定义 agents
70
127
  ```
71
128
 
72
129
  ## CLI 命令
@@ -76,6 +133,11 @@ AGENTS.md + .agents/ # Codex:skills
76
133
  | `init` | 初始化全新项目;检测到已有 ai-memory 安装时拒绝执行 |
77
134
  | `update --dry-run` | 只读分析旧项目到当前 CLI 版本的升级计划 |
78
135
  | `update --yes` | 应用无冲突安全更新;用户修改、混合文件冲突或缺失迁移路径会拒绝执行 |
136
+ | `models show` | 显示当前 profile 和每个阶段解析后的模型等级 |
137
+ | `models configure --profile <name>` | 选择 `inherit`、`balanced` 或 `quality`,不修改需求、设计或代码 |
138
+ | `workflow prepare` | 用正式输入文档的哈希创建本地交接清单 |
139
+ | `workflow verify` | 执行前拒绝缺失、过期、有未决问题或路由不匹配的交接 |
140
+ | `workflow complete` | 校验并保存供后续审查使用的结构化阶段回执 |
79
141
 
80
142
  ### `init` 选项
81
143
 
@@ -84,17 +146,21 @@ AGENTS.md + .agents/ # Codex:skills
84
146
  | `--name <name>` | 项目名(默认取当前目录名) |
85
147
  | `--stack <desc>` | 技术栈描述 |
86
148
  | `--tools <list>` | 逗号分隔的适配层:`claude`、`codex` |
149
+ | `--model-profile <profile>` | `inherit`(默认)、`balanced` 或 `quality`;`--yes` 不会静默启用高成本路由 |
87
150
  | `--import <path>` | 从已有项目导入 `user-profile` / `feedback` |
88
151
  | `--yes` | 非交互模式:缺参用默认值,冲突一律跳过 |
89
152
 
90
153
  ## 工作原理
91
154
 
92
- 生成器由一组纯函数构成:
155
+ 生成器由一组职责明确的小模块构成:
93
156
 
94
157
  - **manifest** —— 遍历 `common` / `claude` / `codex` 模板组,只保留已启用工具对应的组,产出一份扁平的 `{src, dest}` 清单;重复目标会在写入前报错并列出来源。
95
158
  - **render** —— 替换 `{{变量}}` 占位符,遇到任何未定义变量即抛错,让坏掉的模板大声失败,而不是把 `{{...}}` 发出去。
96
159
  - **scaffold** —— 预检安全目标路径和符号链接,处理冲突并在内存中完成全部读取/渲染后再逐个写入;提供 `--import` 时先校验导入目录,再从中拉取 `user-profile` / `feedback`(单个源文件缺失则回退到模板并明确报告)。
97
160
  - **framework metadata** —— 全新 init 记录 framework/schema 版本、文件所有权和生成哈希;update 据此区分安全更新与用户修改。无元数据的 v0.1 项目按 legacy 保守规划。
161
+ - **managed entry blocks** —— Markdown 入口只更新通过校验的框架区块;无标记或标记损坏的定制文件仍人工合并,JSON settings 继续采用保守整文件审查。
162
+ - **model routing** —— 可选 profile 把现有阶段映射为 premium、standard、economy、inherit 或无模型执行;Claude/Codex 使用原生自定义 agent 配置,路由不会增加 S/M/L 阶段。
163
+ - **workflow handoff** —— 本地清单只用路径和 SHA-256 引用正式需求/设计/计划;输入过期、未决问题、路由变化、路径越界或符号链接都会关闭式失败。
98
164
 
99
165
  ## 设计原则
100
166
 
@@ -103,6 +169,8 @@ AGENTS.md + .agents/ # Codex:skills
103
169
  - **需求双目录** —— 人工粗稿(`draft/`)→ AI 定稿(`final/`),中间隔一道强制 critic 门。
104
170
  - **项目工程基线** —— 0→1 项目先建立语言无关的系统、数据、质量属性和部署基线;普通功能只记录增量,不重复设计全局架构。
105
171
  - **风险分级设计** —— S 级需求直接实现;M/L 级用 feature-design 补齐 how、技术接口和工程影响,Superpowers 只按风险选择使用。
172
+ - **可选成本路由** —— `inherit` 保持旧行为;显式 profile 才用高级模型规划/审查和低成本 worker 做有边界的测试工作,确定性测试执行不调用模型。
173
+ - **工程产物交接** —— 跨模型阶段通过校验后的正式文档交接,不依赖对话摘要;阶段回执向最终 reviewer 暴露验收覆盖、设计偏差和未决风险。
106
174
  - **证据化交付** —— 发布前按风险检查契约、测试、迁移、回滚、性能、可观测性和运行保障,输出明确 ready 状态。
107
175
  - **低噪声记忆** —— 只在可验收工程节点、关键决策、状态变化和会话切换时落盘,不记录每个细碎代码步骤。
108
176
  - **幂等** —— 已存在的文件逐个询问;`--yes` 一律跳过。绝不静默覆盖。
package/bin/cli.js CHANGED
@@ -4,7 +4,12 @@ import { readFile } from 'node:fs/promises';
4
4
  import path from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
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';
7
11
  import { ScaffoldError, scaffold } from '../src/scaffold.js';
12
+ import { prepareHandoff, recordStageResult, verifyHandoff } from '../src/workflow.js';
8
13
 
9
14
  const TEMPLATES = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'templates');
10
15
  const PACKAGE = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8'));
@@ -18,6 +23,7 @@ program
18
23
  .option('--name <name>', '项目名')
19
24
  .option('--stack <desc>', '技术栈描述')
20
25
  .option('--tools <list>', '启用工具,逗号分隔:claude,codex')
26
+ .option('--model-profile <profile>', '模型路由策略:inherit,balanced,quality', 'inherit')
21
27
  .option('--import <path>', '从已有项目导入 user-profile/feedback')
22
28
  .option('--yes', '非交互模式:缺参用默认值,冲突一律跳过')
23
29
  .action(async (opts) => {
@@ -30,6 +36,9 @@ program
30
36
  throw new Error(`${label};不要重新运行 init,请先执行 ai-memory update --dry-run`);
31
37
  }
32
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
+ }
33
42
 
34
43
  // Validate CLI-supplied --tools before entering interactive prompts
35
44
  if (toolsRaw !== undefined) {
@@ -43,7 +52,7 @@ program
43
52
 
44
53
  let importFrom = opts.import;
45
54
  if (!opts.yes) {
46
- const { input, checkbox } = await import('@inquirer/prompts');
55
+ const { input, checkbox, select } = await import('@inquirer/prompts');
47
56
  name ??= await input({ message: '项目名:', default: path.basename(targetDir) });
48
57
  stack ??= await input({ message: '技术栈描述:', default: '(待补充)' });
49
58
  toolsRaw ??= (await checkbox({
@@ -56,6 +65,17 @@ program
56
65
  importFrom ??= (await input({
57
66
  message: '从已有项目导入 user-profile/feedback?(输入项目路径,留空跳过)', default: '',
58
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
+ }
59
79
  }
60
80
  name ??= path.basename(targetDir);
61
81
  stack ??= '(待补充)';
@@ -74,7 +94,9 @@ program
74
94
  };
75
95
 
76
96
  const date = new Date().toISOString().slice(0, 10);
77
- const vars = { projectName: name, techStack: stack, date };
97
+ const vars = {
98
+ projectName: name, techStack: stack, date, modelProfile: opts.modelProfile, frameworkVersion: PACKAGE.version,
99
+ };
78
100
  const r = await scaffold({ templatesRoot: TEMPLATES, targetDir, vars, tools, onConflict, importFrom });
79
101
  if (r.skipped.length === 0) {
80
102
  try {
@@ -93,12 +115,81 @@ program
93
115
  console.log('警告:存在冲突跳过项,未创建框架元数据;当前结果不能自动升级');
94
116
  }
95
117
  console.log(`完成:写入 ${r.written.length},跳过 ${r.skipped.length}`);
118
+ console.log(`模型路由:${opts.modelProfile}`);
96
119
  if (r.imported.length) for (const d of r.imported) console.log(` 已导入 ${d}`);
97
120
  if (r.fallback.length) for (const d of r.fallback) console.log(` 导入源缺失,使用模板 ${d}`);
98
121
  if (r.skipped.length) for (const d of r.skipped) console.log(` 跳过 ${d}`);
99
122
  console.log('下一步:打开项目让 agent 读 .ai/memory/MEMORY.md;把项目命令补进 CLAUDE.md/AGENTS.md。');
100
123
  });
101
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
+
102
193
  program
103
194
  .command('update')
104
195
  .description('检查当前项目升级到本 CLI 版本所需的变更')
@@ -112,8 +203,9 @@ program
112
203
  printUpdatePlan(plan);
113
204
  if (opts.yes) {
114
205
  const result = await applyFrameworkUpdate({ targetDir: process.cwd(), templatesRoot: TEMPLATES, plan });
115
- console.log(`安全更新完成:写入 ${result.written.length}`);
206
+ console.log(`安全更新完成:写入 ${result.written.length},移除 ${result.removed.length}`);
116
207
  for (const dest of result.written) console.log(` ${dest}`);
208
+ for (const dest of result.removed) console.log(` 移除 ${dest}`);
117
209
  }
118
210
  });
119
211
 
@@ -125,6 +217,10 @@ program.parseAsync().catch((e) => {
125
217
  if (e.name === 'ScaffoldError') {
126
218
  console.error(`已写入 ${e.summary.written.length}:`);
127
219
  for (const d of e.summary.written) console.error(` ${d}`);
220
+ if (e.summary.removed?.length) {
221
+ console.error(`已移除 ${e.summary.removed.length}:`);
222
+ for (const d of e.summary.removed) console.error(` ${d}`);
223
+ }
128
224
  console.error(`尚未写入 ${e.notWritten.length}:`);
129
225
  for (const d of e.notWritten) console.error(` ${d}`);
130
226
  if (e.summary.skipped.length) {
@@ -147,6 +243,8 @@ function printUpdatePlan(plan) {
147
243
  const groups = [
148
244
  ['add', '新增'],
149
245
  ['update', '可安全更新'],
246
+ ['update-managed', '更新受管区块'],
247
+ ['remove', '安全移除'],
150
248
  ['merge', '需要合并'],
151
249
  ['review', '需要人工审查'],
152
250
  ['review-remove', '需要确认移除'],
@@ -167,3 +265,7 @@ function printUpdatePlan(plan) {
167
265
  console.log('升级计划完成');
168
266
  if (process.argv.includes('--dry-run')) console.log('dry-run:未修改任何文件');
169
267
  }
268
+
269
+ function collect(value, previous) {
270
+ return [...previous, value];
271
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@betterdanlins/ai-memory",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "AI memory + requirements workflow scaffolder for Claude Code & Codex",
5
5
  "type": "module",
6
6
  "bin": {
package/src/framework.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { createHash } from 'node:crypto';
2
- import { access, mkdir, readFile, writeFile } from 'node:fs/promises';
2
+ import { access, mkdir, readFile, unlink, writeFile } from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
  import { buildManifest } from './manifest.js';
5
5
  import { matchesLegacyV01Baseline } from './legacy-v0.1.js';
6
+ import { hasValidManagedBlock, isManagedBlockFile, replaceManagedBlock } from './managed-blocks.js';
6
7
  import { migrationsBetween } from './migrations.js';
7
8
  import { assertNoSymlinkPath, resolveSafeDestination } from './path-safety.js';
8
9
  import { render } from './render.js';
@@ -12,6 +13,7 @@ export const CURRENT_SCHEMA_VERSION = 1;
12
13
  export const METADATA_DEST = '.ai/ai-memory.json';
13
14
 
14
15
  const USER_PREFIXES = [
16
+ '.ai/config/',
15
17
  '.ai/memory/',
16
18
  'docs/architecture/',
17
19
  'docs/requirements/v',
@@ -88,7 +90,8 @@ export async function planFrameworkUpdate({ targetDir, templatesRoot, frameworkV
88
90
  if (installation.kind === 'none') throw new Error('当前目录不是 ai-memory 项目,请先运行 init');
89
91
 
90
92
  const tools = installation.kind === 'metadata' ? installation.metadata.tools : installation.tools;
91
- const vars = installation.kind === 'metadata' ? installation.metadata.templateVars : installation.templateVars;
93
+ const rawVars = installation.kind === 'metadata' ? installation.metadata.templateVars : installation.templateVars;
94
+ const vars = { modelProfile: 'inherit', ...rawVars, frameworkVersion };
92
95
  if (installation.kind === 'metadata' && compareVersions(installation.metadata.frameworkVersion, frameworkVersion) > 0) {
93
96
  throw new Error(`项目框架版本 ${installation.metadata.frameworkVersion} 高于当前 CLI ${frameworkVersion},请使用更新版本的 CLI`);
94
97
  }
@@ -109,6 +112,9 @@ export async function planFrameworkUpdate({ targetDir, templatesRoot, frameworkV
109
112
 
110
113
  const currentHash = hashContent(current);
111
114
  const desiredHash = hashContent(desired);
115
+ if (isManagedBlockFile(dest) && !hasValidManagedBlock(desired)) {
116
+ throw new Error(`受管模板缺少有效 managed 标记: ${dest}`);
117
+ }
112
118
  if (currentHash === desiredHash) {
113
119
  actions.push({ action: 'unchanged', dest, ownership });
114
120
  } else if (ownership === 'user') {
@@ -117,18 +123,33 @@ export async function planFrameworkUpdate({ targetDir, templatesRoot, frameworkV
117
123
  const matchesBaseline = installation.kind === 'metadata'
118
124
  ? installation.metadata.files[dest]?.sha256 === currentHash
119
125
  : matchesLegacyV01Baseline(dest, current, vars);
120
- const action = matchesBaseline ? 'update' : ownership === 'mixed' ? 'merge' : 'review';
121
- actions.push({ action, dest, ownership, currentHash, desiredHash });
126
+ if (isManagedBlockFile(dest) && hasValidManagedBlock(current)) {
127
+ const merged = replaceManagedBlock(current, desired);
128
+ actions.push({
129
+ action: merged === current ? 'unchanged' : 'update-managed',
130
+ dest, ownership, currentHash, desiredHash,
131
+ });
132
+ } else {
133
+ const action = matchesBaseline ? 'update' : ownership === 'mixed' ? 'merge' : 'review';
134
+ actions.push({ action, dest, ownership, currentHash, desiredHash });
135
+ }
122
136
  }
123
137
  }
124
138
 
125
139
  if (installation.kind === 'metadata') {
126
140
  for (const [dest, record] of Object.entries(installation.metadata.files)) {
127
- if (desiredDests.has(dest) || !(await exists(path.join(targetDir, ...dest.split('/'))))) continue;
141
+ const destPath = resolveSafeDestination(targetDir, dest);
142
+ const current = await readOptional(destPath);
143
+ if (desiredDests.has(dest) || current === undefined) continue;
144
+ const currentHash = hashContent(current);
145
+ const canRemove = record.ownership === 'framework'
146
+ && ownershipFor(dest) === 'framework'
147
+ && record.sha256 === currentHash;
128
148
  actions.push({
129
- action: record.ownership === 'user' ? 'preserve' : 'review-remove',
149
+ action: record.ownership === 'user' ? 'preserve' : canRemove ? 'remove' : 'review-remove',
130
150
  dest,
131
151
  ownership: record.ownership,
152
+ currentHash,
132
153
  });
133
154
  }
134
155
  }
@@ -138,6 +159,7 @@ export async function planFrameworkUpdate({ targetDir, templatesRoot, frameworkV
138
159
  frameworkVersion,
139
160
  schemaVersion: CURRENT_SCHEMA_VERSION,
140
161
  tools,
162
+ templateVars: vars,
141
163
  migrations,
142
164
  actions,
143
165
  };
@@ -153,33 +175,51 @@ export async function applyFrameworkUpdate({ targetDir, templatesRoot, plan }) {
153
175
  throw new Error(`存在不可自动执行的 Schema 迁移: ${manualMigrations.map(item => item.id).join(', ')}`);
154
176
  }
155
177
 
156
- const vars = plan.installation.kind === 'metadata'
157
- ? plan.installation.metadata.templateVars
158
- : plan.installation.templateVars;
178
+ const vars = plan.templateVars;
159
179
  const manifest = await buildManifest(templatesRoot, plan.tools);
160
180
  const sources = new Map(manifest.map(entry => [entry.dest, entry.src]));
161
181
  const candidates = [];
162
182
 
163
- for (const action of plan.actions.filter(item => item.action === 'add' || item.action === 'update')) {
183
+ for (const action of plan.actions.filter(item => ['add', 'update', 'update-managed', 'remove'].includes(item.action))) {
184
+ if (action.action === 'remove') {
185
+ const destPath = resolveSafeDestination(targetDir, action.dest);
186
+ await assertNoSymlinkPath(targetDir, destPath);
187
+ const current = await readFile(destPath, 'utf8');
188
+ if (hashContent(current) !== action.currentHash) {
189
+ throw new Error(`文件在预检后发生变化,请重新 dry-run: ${action.dest}`);
190
+ }
191
+ candidates.push({ ...action, destPath });
192
+ continue;
193
+ }
164
194
  const src = sources.get(action.dest);
165
195
  if (!src) throw new Error(`升级计划缺少当前模板: ${action.dest}`);
166
196
  const destPath = resolveSafeDestination(targetDir, action.dest);
167
197
  await assertNoSymlinkPath(targetDir, destPath);
168
- if (action.action === 'update') {
169
- const current = await readFile(destPath, 'utf8');
198
+ let current;
199
+ if (action.action !== 'add') {
200
+ current = await readFile(destPath, 'utf8');
170
201
  if (action.currentHash && hashContent(current) !== action.currentHash) {
171
202
  throw new Error(`文件在预检后发生变化,请重新 dry-run: ${action.dest}`);
172
203
  }
173
204
  }
174
- candidates.push({ ...action, destPath, content: render(await readFile(src, 'utf8'), vars) });
205
+ const desired = render(await readFile(src, 'utf8'), vars);
206
+ const content = action.action === 'update-managed'
207
+ ? replaceManagedBlock(current, desired)
208
+ : desired;
209
+ candidates.push({ ...action, destPath, content });
175
210
  }
176
211
 
177
- const summary = { written: [], skipped: [], imported: [], fallback: [] };
212
+ const summary = { written: [], removed: [], skipped: [], imported: [], fallback: [] };
178
213
  for (const [index, item] of candidates.entries()) {
179
214
  try {
180
- await mkdir(path.dirname(item.destPath), { recursive: true });
181
- await writeFile(item.destPath, item.content, { flag: item.action === 'add' ? 'wx' : 'w' });
182
- summary.written.push(item.dest);
215
+ if (item.action === 'remove') {
216
+ await unlink(item.destPath);
217
+ summary.removed.push(item.dest);
218
+ } else {
219
+ await mkdir(path.dirname(item.destPath), { recursive: true });
220
+ await writeFile(item.destPath, item.content, { flag: item.action === 'add' ? 'wx' : 'w' });
221
+ summary.written.push(item.dest);
222
+ }
183
223
  } catch (err) {
184
224
  throw new ScaffoldError({
185
225
  phase: 'update', dest: item.dest, destPath: item.destPath, cause: err, summary,