@oevery/rsp 3.3.0 → 4.1.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 (76) hide show
  1. package/README.md +24 -17
  2. package/README.zh-CN.md +25 -18
  3. package/dist/chunk-JO6NDJM4.mjs +360 -0
  4. package/dist/chunk-LGDCIQYL.mjs +725 -0
  5. package/dist/{chunk-7HXQNBIG.mjs → chunk-P3KO3GZW.mjs} +420 -268
  6. package/dist/chunk-U56XMIOO.mjs +66 -0
  7. package/dist/{chunk-TSUDEX5Y.mjs → chunk-VDRXFKBP.mjs} +142 -867
  8. package/dist/cli.mjs +1111 -2347
  9. package/dist/client-EHRWH5Q7.mjs +108 -0
  10. package/dist/{entry-GIDNR7CJ.mjs → entry-NDASTAI5.mjs} +128 -38
  11. package/dist/viewer/NOTICE.txt +299 -0
  12. package/dist/viewer/app.css +2 -0
  13. package/dist/viewer/app.js +670 -0
  14. package/dist/viewer-build.json +1 -0
  15. package/dist/viewer-service.mjs +551 -0
  16. package/package.json +51 -25
  17. package/skills/rsp/SKILL.md +44 -38
  18. package/skills/rsp/references/architecture.md +39 -0
  19. package/skills/rsp/references/closeout.md +38 -0
  20. package/skills/rsp/references/context-migration.md +22 -0
  21. package/skills/rsp/references/control-outcome.md +3 -29
  22. package/skills/rsp/references/coordination-recovery.md +25 -0
  23. package/skills/rsp/references/coordination-review.md +15 -0
  24. package/skills/rsp/references/coordination.md +59 -0
  25. package/skills/rsp/references/durable-review.md +60 -12
  26. package/skills/rsp/references/focus-continuation.md +12 -4
  27. package/skills/rsp/references/groups-dependencies.md +6 -4
  28. package/skills/rsp/references/response-language.md +2 -0
  29. package/skills/rsp/references/setup-repair.md +42 -7
  30. package/skills/rsp/references/work-model.md +37 -0
  31. package/skills/rsp-commit/SKILL.md +76 -31
  32. package/skills/rsp-commit/references/message.md +34 -0
  33. package/skills/rsp-doc/SKILL.md +68 -0
  34. package/skills/rsp-doc/agents/openai.yaml +3 -0
  35. package/skills/rsp-doc/references/release-communication.md +39 -0
  36. package/skills/rsp-doc/references/skill-authoring.md +36 -0
  37. package/skills/rsp-implement/SKILL.md +60 -25
  38. package/skills/rsp-implement/references/diagnosis.md +10 -0
  39. package/skills/rsp-implement/references/finding-resolution.md +21 -0
  40. package/skills/rsp-implement/references/test-first.md +7 -0
  41. package/skills/rsp-review/SKILL.md +46 -45
  42. package/skills/rsp-review/references/code-review.md +30 -9
  43. package/skills/rsp-review/references/document-review.md +24 -9
  44. package/skills/rsp-review/references/release-communication.md +21 -0
  45. package/skills/rsp-shape/SKILL.md +26 -45
  46. package/skills/rsp-shape/references/complex-shaping.md +3 -1
  47. package/skills/rsp-shape/references/deep-clarification.md +4 -4
  48. package/skills/rsp-shape/references/domain-modeling.md +13 -0
  49. package/skills/rsp-shape/references/module-seams.md +13 -0
  50. package/skills/rsp-shape/references/planning.md +42 -0
  51. package/skills/{rsp-design → rsp-shape}/references/reversible-exploration.md +2 -2
  52. package/skills/rsp-verify/SKILL.md +39 -12
  53. package/dist/chunk-JOMFFQRG.mjs +0 -99
  54. package/dist/chunk-QM2UVPQK.mjs +0 -375
  55. package/dist/entry-YAW3PYFA.mjs +0 -174
  56. package/rules/rsp-rules.md +0 -48
  57. package/skills/rsp/references/implementation-evidence.md +0 -12
  58. package/skills/rsp/references/managed-routing.md +0 -45
  59. package/skills/rsp-design/SKILL.md +0 -53
  60. package/skills/rsp-design/references/domain-modeling.md +0 -11
  61. package/skills/rsp-design/references/module-seams.md +0 -11
  62. package/skills/rsp-diagnose/SKILL.md +0 -48
  63. package/skills/rsp-manage/SKILL.md +0 -101
  64. package/skills/rsp-manage/references/closeout.md +0 -13
  65. package/skills/rsp-manage/references/delegation.md +0 -47
  66. package/skills/rsp-manage/references/interruption-recovery.md +0 -19
  67. package/skills/rsp-manage/references/review-convergence.md +0 -9
  68. package/skills/rsp-release-docs/SKILL.md +0 -77
  69. package/skills/rsp-release-docs/references/convention-discovery.md +0 -65
  70. package/skills/rsp-release-docs/references/evidence-and-surfaces.md +0 -21
  71. package/skills/rsp-release-docs/references/output-contracts.md +0 -97
  72. package/skills/rsp-release-docs/references/publication-lifecycle.md +0 -28
  73. package/skills/rsp-resolve-findings/SKILL.md +0 -61
  74. package/skills/rsp-structural-audit/SKILL.md +0 -71
  75. package/skills/rsp-structural-audit/references/structural-lenses.md +0 -41
  76. package/skills/rsp-tdd/SKILL.md +0 -56
package/README.md CHANGED
@@ -4,31 +4,30 @@ English | [简体中文](./README.zh-CN.md)
4
4
 
5
5
  **A repository-native engineering workflow for humans and AI agents.**
6
6
 
7
- RSP turns unclear intent into shaped, implemented, reviewed, and verified software changes while keeping project knowledge durable and work recoverable. It uses ordinary repository files—Rules, Specs, and Plans—rather than hidden workflow state.
7
+ RSP turns unclear intent into shaped, implemented, reviewed, and verified software changes while keeping project knowledge durable and work recoverable. It uses ordinary repository files—Skills, Specs, and Plans—rather than hidden workflow state.
8
8
 
9
9
  ## What RSP helps you do
10
10
 
11
11
  - Shape ambiguous work into an executable Change.
12
12
  - Resolve design questions from repository evidence before implementation.
13
- - Route diagnosis, implementation, TDD, review, and release work to explicit capabilities.
13
+ - Route diagnosis, implementation, risk-selected test-first work, review, and release work to explicit capabilities.
14
14
  - Preserve current facts, lasting rationale, scoped instructions, and completed history in the right owners.
15
15
  - Keep authority visible: RSP never infers permission to commit, publish, deploy, or approve work.
16
16
 
17
- ```text
18
- intent → shape → design when needed → diagnose | TDD | implement
19
- → review → durable review → archive
20
- ```
17
+ RSP selects the next responsibility from the task and evidence, not a fixed stage pipeline. A settled authorized task can proceed directly to implementation and its own checks; design, review, coordination and lifecycle closeout apply only when needed and permitted.
21
18
 
22
19
  ## Five-minute start
23
20
 
24
- RSP requires Node.js 22 (`>=22`). Use the current stable release:
21
+ RSP requires Node.js 22 (`>=22`). Run setup from the target project root. For a new project, resolve a stable version once and keep that exact selection. Existing projects use the version in `.rsp/version` and the generated AGENTS entry, not a fresh `latest` resolution. Later `rsp` examples denote that exact invocation or a verified matching executable:
25
22
 
26
23
  ```bash
27
- npx -y @oevery/rsp@latest init --with-project-setup
24
+ npm view @oevery/rsp dist-tags.latest
25
+ # Replace <version> with the inspected exact version.
26
+ RSP_VERSION='<version>'
27
+ npx -y @oevery/rsp@"$RSP_VERSION" init --with-project-setup
28
28
  # fill .rsp/changes/project-setup.md
29
29
  # fill .rsp/specs/design.md
30
- npx -y @oevery/rsp@latest doctor
31
- npx -y @oevery/rsp@latest status --json
30
+ npx -y @oevery/rsp@"$RSP_VERSION" status
32
31
  ```
33
32
 
34
33
  Then create and focus one tracked change:
@@ -36,10 +35,12 @@ Then create and focus one tracked change:
36
35
  ```bash
37
36
  rsp create improve-login "Make login failures actionable"
38
37
  rsp focus improve-login
39
- rsp show --focused
38
+ rsp show improve-login
40
39
  ```
41
40
 
42
- Follow the nearest `AGENTS.md`, edit the focused Change as work progresses, run fresh project checks, make the durable-update decision, and archive only after acceptance is satisfied.
41
+ Focus is a set: the setup Change may still be focused alongside improve-login. Use the explicit name to inspect it; show --focused requires exactly one marker.
42
+
43
+ Confirm that `status` shows the expected work; use read-only `doctor` when diagnosing setup. These are deterministic observations, not semantic acceptance. Follow the nearest `AGENTS.md`, keep the selected Change current and establish applicable required verification, reusing traceable passing evidence when its tested boundary and relevant inputs still apply. Before authorized archive, decide necessary knowledge updates, converge the whole Change and check current readiness.
43
44
 
44
45
  [Read the full getting-started guide](./docs/site/en/getting-started.md).
45
46
 
@@ -47,8 +48,8 @@ Follow the nearest `AGENTS.md`, edit the focused Change as work progresses, run
47
48
 
48
49
  ```text
49
50
  .rsp/
50
- ├── rsp-rules.md # minimal fallback protocol
51
- ├── specs/ # durable current facts
51
+ ├── version # exact CLI package selection
52
+ ├── specs/ # current contracts, boundaries, and constraints
52
53
  ├── changes/ # open work
53
54
  ├── focus.d/ # markers selecting work; optional short recovery capsules
54
55
  └── archives/ # completed history
@@ -65,8 +66,12 @@ A Change is one Markdown file with canonical Proposal, Spec, Design, Tasks, Veri
65
66
  - [Configuration reference](./docs/site/en/reference/configuration.md)
66
67
  - [CLI reference](./docs/site/en/reference/cli.md)
67
68
  - [3.0 migration guide](./docs/migrations/3.0.md) and [3.1 migration guide](./docs/migrations/3.1.md)
68
- - [Release notes](./docs/releases/3.3.0.md)
69
- - [Design philosophy](./docs/maintainers/design-philosophy.md) and [maintainer upstream research](./docs/maintainers/upstreams.md)
69
+ - [4.0 migration guide](./docs/migrations/pinned-skills.md)
70
+ - [Changelog](./CHANGELOG.md)
71
+ - [Maintainer tests and Skill validation](./tests/README.md)
72
+ - [Maintainer workflows, publication and documentation recovery](./docs/site/en/contributing.md)
73
+ - [Repository context and contract navigation](./CONTEXT.md)
74
+ - [Authoritative architecture and Specs](./.rsp/specs/design.md) and [maintainer research](./research/README.md)
70
75
 
71
76
  Run the documentation locally:
72
77
 
@@ -80,6 +85,8 @@ The site is only a presentation layer over the Markdown in this repository. It a
80
85
 
81
86
  ## Platform support
82
87
 
83
- RSP is a tool-agnostic file convention and works with any assistant or editor that can read project files. Humans start here; agents follow the nearest `AGENTS.md`, load `skills/rsp/SKILL.md` when available, and use `.rsp/rsp-rules.md` only when the Skill is unavailable.
88
+ RSP is a tool-agnostic file convention and works with any assistant or editor that can read project files. Humans start here; agents follow the nearest `AGENTS.md`, load the required project Core through host Skill discovery or `.agents/skills/rsp/SKILL.md`. Missing required Skills stop dependent work; there is no Rules fallback. Root `skills/` contains authored sources in the RSP repository, not the consumer installation entry.
89
+
90
+ RSP 4.0 uses `init` for a complete setup and the selected target CLI's `update` for the complete environment. It pins exact versions and retires Rules and separate Skill installation; existing projects must follow the [4.0 migration guide](docs/migrations/pinned-skills.md). The seven core Skills remain independently invocable; see [Skill ownership](docs/site/en/guides/skills.md).
84
91
 
85
92
  RSP is licensed under the [MIT License](./LICENSE).
package/README.zh-CN.md CHANGED
@@ -4,31 +4,30 @@
4
4
 
5
5
  **面向人类与 AI 智能体的仓库原生工程工作流。**
6
6
 
7
- RSP 把模糊意图转化为经过塑造、实现、审查和验证的软件变更,同时让项目知识可持久化、工作可恢复。它使用普通仓库文件——规则、规格和计划——而不是隐藏的工作流状态。
7
+ RSP 把模糊意图转化为经过塑造、实现、审查和验证的软件变更,同时让项目知识可持久化、工作可恢复。它使用普通仓库文件——Skills、规格和计划——而不是隐藏的工作流状态。
8
8
 
9
9
  ## RSP 帮助你完成什么
10
10
 
11
11
  - 把模糊工作塑造成一个可执行 Change。
12
12
  - 在实现前根据仓库证据解决设计问题。
13
- - 把诊断、实现、TDD、审查和发布工作路由给明确的能力。
13
+ - 把诊断、实现、按风险选择的测试先行、审查和发布工作路由给明确的能力。
14
14
  - 把当前事实、长期理由、作用域指令和完成历史保存在正确的归属位置。
15
15
  - 保持权限可见:RSP 不会推断提交、发布、部署或批准权限。
16
16
 
17
- ```text
18
- 意图 → 塑造 → 必要时设计 → 诊断 | TDD | 实现
19
- → 审查 → 持久化审查 → 归档
20
- ```
17
+ RSP 根据任务与证据选择下一项职责,不采用固定阶段流水线。已经明确且获授权的任务可以直接实现并完成自身检查;设计、审查、协调和生命周期收尾仅在需要且权限允许时执行。
21
18
 
22
19
  ## 五分钟入门
23
20
 
24
- RSP 需要 Node.js 22(`>=22`)。使用当前稳定版本:
21
+ RSP 需要 Node.js 22(`>=22`),从目标项目根目录执行初始化。新项目只解析一次稳定版本,后续始终使用同一精确版本;已有项目使用 `.rsp/version` 和 AGENTS 中记录的版本,不重新解析 `latest`。后续 `rsp` 表示该精确调用或已核验版本一致的可执行文件:
25
22
 
26
23
  ```bash
27
- npx -y @oevery/rsp@latest init --with-project-setup
24
+ npm view @oevery/rsp dist-tags.latest
25
+ # 把 <version> 替换为已检查的精确版本。
26
+ RSP_VERSION='<version>'
27
+ npx -y @oevery/rsp@"$RSP_VERSION" init --with-project-setup
28
28
  # 填写 .rsp/changes/project-setup.md
29
29
  # 填写 .rsp/specs/design.md
30
- npx -y @oevery/rsp@latest doctor
31
- npx -y @oevery/rsp@latest status --json
30
+ npx -y @oevery/rsp@"$RSP_VERSION" status
32
31
  ```
33
32
 
34
33
  然后创建并聚焦一个受跟踪变更:
@@ -36,10 +35,12 @@ npx -y @oevery/rsp@latest status --json
36
35
  ```bash
37
36
  rsp create improve-login "让登录失败信息可供用户采取行动"
38
37
  rsp focus improve-login
39
- rsp show --focused
38
+ rsp show improve-login
40
39
  ```
41
40
 
42
- 遵循最近的 `AGENTS.md`,在工作推进时同步当前聚焦的 Change,运行最新的项目检查,判断是否需要持久化更新,并仅在满足验收条件后归档。
41
+ Focus 是一个集合:project-setup 和 improve-login 可以同时聚焦。使用明确名称查看目标;show --focused 要求恰好一个标记。
42
+
43
+ 确认 `doctor` 显示预期的项目设置,`status` 显示预期工作;这些是确定性观察,不是语义验收。遵循最近的 `AGENTS.md`,保持选中的 Change 与实际结果同步,并建立适用的必需验证:被测边界和相关输入仍然适用时,复用可追溯的通过证据。授权归档前,判断必要的知识更新,收敛整个 Change,再检查当前就绪状态。
43
44
 
44
45
  [阅读完整入门指南](./docs/site/zh-CN/getting-started.md)。
45
46
 
@@ -47,10 +48,10 @@ rsp show --focused
47
48
 
48
49
  ```text
49
50
  .rsp/
50
- ├── rsp-rules.md # 最小后备协议
51
- ├── specs/ # 持久化的当前事实
51
+ ├── version # 精确的 CLI 软件包版本
52
+ ├── specs/ # 当前契约、边界与必要约束
52
53
  ├── changes/ # 未完成工作
53
- ├── focus.d/ # 选择当前工作的空标记文件
54
+ ├── focus.d/ # 工作选择标记;可附短恢复胶囊
54
55
  └── archives/ # 已完成历史
55
56
  ```
56
57
 
@@ -65,8 +66,12 @@ rsp show --focused
65
66
  - [配置参考](./docs/site/zh-CN/reference/configuration.md)
66
67
  - [CLI 参考](./docs/site/zh-CN/reference/cli.md)
67
68
  - [3.0 迁移指南](./docs/migrations/3.0.md)与 [3.1 迁移指南](./docs/migrations/3.1.md)
68
- - [发布说明](./docs/releases/3.3.0.md)
69
- - [设计哲学](./docs/maintainers/design-philosophy.md)与[维护者上游研究](./docs/maintainers/upstreams.md)
69
+ - [4.0 迁移指南](./docs/migrations/pinned-skills.md)
70
+ - [变更日志](./CHANGELOG.md)
71
+ - [维护者测试与 Skill 验证](./tests/README.md)
72
+ - [维护工作流、发布与文档恢复](./docs/site/zh-CN/contributing.md)
73
+ - [仓库上下文与契约导航](./CONTEXT.md)
74
+ - [权威架构与 Specs](./.rsp/specs/design.md)及[维护者研究](./research/README.md)
70
75
 
71
76
  在本地运行文档:
72
77
 
@@ -80,6 +85,8 @@ pnpm docs:build
80
85
 
81
86
  ## 平台支持
82
87
 
83
- RSP 是与工具无关的文件约定,适用于任何能读取项目文件的助手或编辑器。人类从这里开始;AI 智能体遵循最近的 `AGENTS.md`,优先加载 `skills/rsp/SKILL.md`,仅在 Skill 不可用时使用 `.rsp/rsp-rules.md` 后备协议。
88
+ RSP 是与工具无关的文件约定,适用于任何能读取项目文件的助手或编辑器。人类从这里开始;AI 智能体遵循最近的 `AGENTS.md`,通过宿主 Skill discovery 或 `.agents/skills/rsp/SKILL.md` 加载必需的项目 Core。缺少必需 Skill 时停止依赖工作,不再使用 Rules 后备协议。根目录 `skills/` 是 RSP 仓库中的分发源码,不是消费者的安装入口。
89
+
90
+ RSP 4.0 用 `init` 完成接入,用选定目标 CLI 的 `update` 一次更新完整环境;固定精确版本,退役 Rules 与独立 Skill 安装流程。已有项目须先阅读 [4.0 迁移指南](docs/migrations/pinned-skills.md)。七个核心 Skills 仍可独立调用,职责见 [Skills 指南](docs/site/zh-CN/guides/skills.md)。
84
91
 
85
92
  RSP 使用 [MIT 许可证](./LICENSE)。
@@ -0,0 +1,360 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ readCurrentDocument
4
+ } from "./chunk-LGDCIQYL.mjs";
5
+ import {
6
+ PKG_ROOT,
7
+ RSP_DIR,
8
+ getVersion,
9
+ inspectManagedDirectory,
10
+ inspectManagedFile,
11
+ inspectRspConfig,
12
+ markdownText,
13
+ parseMarkdown,
14
+ walkMarkdown
15
+ } from "./chunk-P3KO3GZW.mjs";
16
+
17
+ // src/architecture/model.ts
18
+ import { createHash } from "crypto";
19
+ import { realpath } from "fs/promises";
20
+ import { isAbsolute, relative, resolve } from "path";
21
+
22
+ // src/architecture/selection.ts
23
+ function selectArchitecture(model, selection) {
24
+ const selected = new Set(model.nodes.map((node) => node.id));
25
+ if (selection.kind === "focus") {
26
+ if (!selected.has(selection.id))
27
+ throw new Error(`Unknown architecture node: ${selection.id}`);
28
+ selected.clear();
29
+ selected.add(selection.id);
30
+ }
31
+ if (selection.kind === "node") {
32
+ if (!selected.has(selection.id))
33
+ throw new Error(`Unknown architecture node: ${selection.id}`);
34
+ selected.clear();
35
+ selected.add(selection.id);
36
+ let added = true;
37
+ while (added) {
38
+ added = false;
39
+ for (const node of model.nodes) {
40
+ if (node.parent && selected.has(node.parent) && !selected.has(node.id)) {
41
+ selected.add(node.id);
42
+ added = true;
43
+ }
44
+ }
45
+ }
46
+ }
47
+ if (selection.kind === "view") {
48
+ const view = model.views.find((view2) => view2.id === selection.id);
49
+ if (!view)
50
+ throw new Error(`Unknown architecture view: ${selection.id}`);
51
+ selected.clear();
52
+ view.include.forEach((id) => selected.add(id));
53
+ }
54
+ const relations = model.relations.filter((edge) => selected.has(edge.source) || selected.has(edge.target));
55
+ const visible = /* @__PURE__ */ new Set([...selected, ...relations.flatMap((edge) => [edge.source, edge.target])]);
56
+ return { selected, visible, relations };
57
+ }
58
+
59
+ // src/architecture/model.ts
60
+ var ARCHITECTURE_FILE = ".rsp/architecture.json";
61
+ var MAX_CONTENT_BYTES = 16 * 1024 * 1024;
62
+ var digest = (value) => createHash("sha256").update(value).digest("hex");
63
+ function projectPath(file) {
64
+ if (!file || isAbsolute(file) || file.includes("\\") || file.split("/").some((part) => !part || part === "." || part === "..") || file.includes("\0"))
65
+ throw new Error(`Unsafe project-relative path: ${file}`);
66
+ if (file.split("/").some((part) => part === ".git" || part === "node_modules"))
67
+ throw new Error(`Private/runtime path is not readable: ${file}`);
68
+ return file;
69
+ }
70
+ async function readProjectFile(root, file) {
71
+ projectPath(file);
72
+ return (await readCurrentDocument(resolve(root, file), root, file, MAX_CONTENT_BYTES)).content;
73
+ }
74
+ function object(value) {
75
+ if (!value || typeof value !== "object" || Array.isArray(value))
76
+ throw new Error("Architecture entries must be objects");
77
+ return value;
78
+ }
79
+ function text(value, field) {
80
+ if (typeof value !== "string" || !value.trim())
81
+ throw new Error(`Architecture ${field} must be a non-empty string`);
82
+ return value;
83
+ }
84
+ function strings(value, field) {
85
+ if (!Array.isArray(value))
86
+ throw new Error(`Architecture ${field} must be an array`);
87
+ return value.map((item) => text(item, field));
88
+ }
89
+ function collection(value, field) {
90
+ if (!Array.isArray(value))
91
+ throw new Error(`Architecture ${field} must be an array`);
92
+ return value.map(object);
93
+ }
94
+ function unique(items, field) {
95
+ if (new Set(items.map((item) => item.id)).size !== items.length)
96
+ throw new Error(`Duplicate architecture ${field} identity`);
97
+ }
98
+ function parseArchitecture(content) {
99
+ const value = object(JSON.parse(content));
100
+ if (value.schemaVersion !== 1)
101
+ throw new Error(`Unsupported architecture schemaVersion: ${String(value.schemaVersion)}`);
102
+ const nodes = collection(value.nodes, "nodes").map((node) => ({ id: text(node.id, "node.id"), title: text(node.title, "node.title"), description: text(node.description, "node.description"), ...node.parent === void 0 ? {} : { parent: text(node.parent, "node.parent") } }));
103
+ const relations = collection(value.relations, "relations").map((edge) => ({ id: text(edge.id, "relation.id"), source: text(edge.source, "relation.source"), target: text(edge.target, "relation.target"), type: text(edge.type, "relation.type"), description: text(edge.description, "relation.description") }));
104
+ const views = collection(value.views, "views").map((view) => ({ id: text(view.id, "view.id"), title: text(view.title, "view.title"), node: text(view.node, "view.node"), include: strings(view.include, "view.include") }));
105
+ const bindings = collection(value.bindings, "bindings").map((binding) => {
106
+ const file = projectPath(text(binding.file, "binding.file"));
107
+ if (!/\.md$/i.test(file))
108
+ throw new Error("Architecture bindings must reference Markdown files");
109
+ const scope = binding.scope ?? "node";
110
+ if (scope !== "node" && scope !== "subtree")
111
+ throw new Error("Architecture binding.scope must be node or subtree");
112
+ const headings = binding.headings === void 0 ? void 0 : strings(binding.headings, "binding.headings");
113
+ if (headings?.length === 0)
114
+ throw new Error("Omit headings for a whole-file binding");
115
+ return { node: text(binding.node, "binding.node"), file, ...headings ? { headings } : {}, scope };
116
+ });
117
+ unique(nodes, "node");
118
+ unique(relations, "relation");
119
+ unique(views, "view");
120
+ const ids = new Set(nodes.map((node) => node.id));
121
+ const requireNode = (id) => {
122
+ if (!ids.has(id))
123
+ throw new Error(`Missing architecture node: ${id}`);
124
+ };
125
+ for (const node of nodes) {
126
+ const visited = /* @__PURE__ */ new Set([node.id]);
127
+ let parent = node.parent;
128
+ while (parent !== void 0) {
129
+ requireNode(parent);
130
+ if (visited.has(parent))
131
+ throw new Error(`Architecture hierarchy cycle: ${parent}`);
132
+ visited.add(parent);
133
+ parent = nodes.find((node2) => node2.id === parent).parent;
134
+ }
135
+ }
136
+ relations.forEach((edge) => {
137
+ requireNode(edge.source);
138
+ requireNode(edge.target);
139
+ });
140
+ views.forEach((view) => {
141
+ requireNode(view.node);
142
+ view.include.forEach(requireNode);
143
+ });
144
+ bindings.forEach((binding) => requireNode(binding.node));
145
+ return { schemaVersion: 1, nodes, relations, views, bindings };
146
+ }
147
+ function documentHeadings(content) {
148
+ const result = [];
149
+ const stack = [];
150
+ const counts = /* @__PURE__ */ new Map();
151
+ const anchors = /* @__PURE__ */ new Set();
152
+ walkMarkdown(parseMarkdown(content).root, (node) => {
153
+ if (node.type !== "heading")
154
+ return;
155
+ const title = markdownText(node);
156
+ while (stack.length && stack.at(-1).depth >= node.depth) stack.pop();
157
+ stack.push({ title, depth: node.depth });
158
+ const base = title.toLowerCase().replace(/[^\p{L}\p{N}\s_-]/gu, "").replace(/\s/g, "-") || "section";
159
+ let count = counts.get(base) ?? 0;
160
+ let anchor = count ? `${base}-${count}` : base;
161
+ while (anchors.has(anchor)) anchor = `${base}-${++count}`;
162
+ counts.set(base, count + 1);
163
+ anchors.add(anchor);
164
+ result.push({ title, depth: node.depth, anchor, hierarchy: stack.map((item) => item.title) });
165
+ });
166
+ return result;
167
+ }
168
+ function resolveHeading(headings, hierarchy) {
169
+ const matches = headings.filter((heading) => hierarchy.length <= heading.hierarchy.length && hierarchy.every((title, i) => title === heading.hierarchy[heading.hierarchy.length - hierarchy.length + i]));
170
+ if (matches.length !== 1)
171
+ throw new Error(`${matches.length ? "Ambiguous" : "Missing"} chapter: ${hierarchy.join(" > ")}`);
172
+ return matches[0];
173
+ }
174
+ async function queryArchitecture(options = {}) {
175
+ if (options.node !== void 0 && !options.node.trim())
176
+ throw new Error("--node requires a non-empty ID");
177
+ if (options.view !== void 0 && !options.view.trim())
178
+ throw new Error("--view requires a non-empty ID");
179
+ if (options.node && options.view)
180
+ throw new Error("--node and --view are mutually exclusive");
181
+ const root = await realpath(resolve(options.root ?? process.cwd()));
182
+ const config = await inspectRspConfig(root);
183
+ if (config.issues.length)
184
+ throw new Error(config.issues.join("; "));
185
+ let content;
186
+ try {
187
+ content = await readProjectFile(root, ARCHITECTURE_FILE);
188
+ } catch (error) {
189
+ const { lstat } = await import("fs/promises");
190
+ try {
191
+ await lstat(resolve(root, ARCHITECTURE_FILE));
192
+ } catch (missing) {
193
+ if (missing.code === "ENOENT")
194
+ return { configured: false, source: { root, file: ARCHITECTURE_FILE, revision: "" }, selection: { kind: "full" }, nodes: [], relations: [], views: [], documents: [], diagnostics: [{ code: "not_configured", message: `Architecture is not configured: ${ARCHITECTURE_FILE}` }] };
195
+ }
196
+ throw error;
197
+ }
198
+ const model = parseArchitecture(content);
199
+ const selection = options.node ? { kind: "node", id: options.node } : options.view ? { kind: "view", id: options.view } : { kind: "full" };
200
+ const { selected, visible, relations } = selectArchitecture(model, selection);
201
+ const diagnostics = [];
202
+ const chapterMaps = /* @__PURE__ */ new Map();
203
+ const sourceInputs = [content, JSON.stringify(config.config)];
204
+ for (const file of new Set(model.bindings.map((binding) => binding.file))) {
205
+ try {
206
+ const body = await readProjectFile(root, file);
207
+ chapterMaps.set(file, documentHeadings(body));
208
+ sourceInputs.push(file, body);
209
+ } catch (error) {
210
+ diagnostics.push({ code: "document_unavailable", file, message: error.message });
211
+ }
212
+ }
213
+ const nodes = model.nodes.filter((node) => visible.has(node.id)).map((node) => {
214
+ const ancestors = /* @__PURE__ */ new Set();
215
+ let parent = node.parent;
216
+ while (parent) {
217
+ ancestors.add(parent);
218
+ parent = model.nodes.find((node2) => node2.id === parent)?.parent;
219
+ }
220
+ const bindings = model.bindings.filter((binding) => binding.node === node.id || binding.scope === "subtree" && ancestors.has(binding.node)).map((binding) => {
221
+ const reference = { ...binding, origin: binding.node, inherited: binding.node !== node.id };
222
+ if (!binding.headings || !chapterMaps.has(binding.file))
223
+ return reference;
224
+ try {
225
+ return { ...reference, anchor: resolveHeading(chapterMaps.get(binding.file), binding.headings).anchor };
226
+ } catch (error) {
227
+ diagnostics.push({ code: "chapter_unresolved", file: binding.file, message: error.message });
228
+ return reference;
229
+ }
230
+ });
231
+ return { ...node, boundary: !selected.has(node.id), bindings };
232
+ });
233
+ return { configured: true, source: { root, file: ARCHITECTURE_FILE, revision: digest(sourceInputs.join("\0")) }, selection, nodes, relations, views: model.views.filter((view) => selection.kind === "full" || view.id === options.view || selected.has(view.node)), documents: [...new Set(nodes.flatMap((node) => node.bindings.map((binding) => binding.file)))], diagnostics };
234
+ }
235
+ function isWithin(root, target) {
236
+ const path = relative(root, target);
237
+ return path !== ".." && !path.startsWith("../") && !isAbsolute(path);
238
+ }
239
+
240
+ // src/core/project-version.ts
241
+ import { readFile, realpath as realpath2 } from "fs/promises";
242
+ import { join } from "path";
243
+ var PROJECT_VERSION_PATH = ".rsp/version";
244
+ var PROJECT_SOURCE_PATH = ".rsp/source";
245
+ var SOURCE_INVOCATION = "mise exec -- node dist/cli.mjs";
246
+ function isExactVersion(value) {
247
+ return /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-(?:0|[1-9]\d*|\d*[a-z-][\da-z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-z-][\da-z-]*))*)?(?:\+[\da-z-]+(?:\.[\da-z-]+)*)?$/i.test(value);
248
+ }
249
+ function exactInvocation(version) {
250
+ if (!isExactVersion(version))
251
+ throw new Error(`Invalid exact RSP version: ${version}`);
252
+ return `npx -y @oevery/rsp@${version}`;
253
+ }
254
+ async function readProjectVersion(projectRoot = ".") {
255
+ const directory = inspectManagedDirectory(join(projectRoot, RSP_DIR), "RSP root", { allowMissing: true });
256
+ if (directory.issue)
257
+ throw directory.issue;
258
+ if (!directory.exists)
259
+ return null;
260
+ const path = join(projectRoot, PROJECT_VERSION_PATH);
261
+ const inspection = inspectManagedFile(path, "project CLI version", { allowMissing: true });
262
+ if (inspection.issue)
263
+ throw inspection.issue;
264
+ if (!inspection.exists)
265
+ return null;
266
+ const version = (await readFile(path, "utf8")).trim();
267
+ if (!isExactVersion(version))
268
+ throw new Error(`${PROJECT_VERSION_PATH} must contain one exact package version, not a tag or range`);
269
+ return version;
270
+ }
271
+ async function readProjectRuntime(projectRoot = ".") {
272
+ const version = await readProjectVersion(projectRoot);
273
+ const source = inspectManagedFile(join(projectRoot, PROJECT_SOURCE_PATH), "project source selection", { allowMissing: true });
274
+ if (source.issue)
275
+ throw source.issue;
276
+ if (!source.exists)
277
+ return { mode: "package", version };
278
+ if (version !== null)
279
+ throw new Error(`${PROJECT_SOURCE_PATH} and ${PROJECT_VERSION_PATH} are mutually exclusive; explicitly select one runtime.`);
280
+ if ((await readFile(join(projectRoot, PROJECT_SOURCE_PATH), "utf8")).trim() !== ".")
281
+ throw new Error(`${PROJECT_SOURCE_PATH} must contain . to select this RSP source checkout.`);
282
+ if (await realpath2(projectRoot) !== await realpath2(PKG_ROOT))
283
+ throw new Error(`Source mode requires this checkout's local CLI. Build here, then use ${SOURCE_INVOCATION} <command>; a registry package or another checkout cannot substitute.`);
284
+ const sourceDirectory = inspectManagedDirectory(join(projectRoot, "src"), "RSP source directory");
285
+ const sourceEntry = inspectManagedFile(join(projectRoot, "src/cli.ts"), "RSP source entry");
286
+ if (sourceDirectory.issue || sourceEntry.issue)
287
+ throw sourceDirectory.issue ?? sourceEntry.issue;
288
+ return { mode: "source" };
289
+ }
290
+ async function selectProjectRuntime(operation = "ordinary") {
291
+ const version = await getVersion();
292
+ exactInvocation(version);
293
+ const selected = await readProjectRuntime();
294
+ if (selected.mode === "source")
295
+ return { mode: "source", version };
296
+ if (selected.version && selected.version !== version && operation !== "update") {
297
+ throw new Error(`Project pins RSP ${selected.version}; running CLI is ${version}. Use ${exactInvocation(selected.version)} <command>. To migrate deliberately, inspect the target version and run ${exactInvocation(version)} update.`);
298
+ }
299
+ if (selected.version && operation === "update" && compareVersions(version, selected.version) < 0)
300
+ throw new Error(`Downgrading RSP from ${selected.version} to ${version} is unsupported. Use ${exactInvocation(selected.version)} <command>.`);
301
+ return { mode: "package", version };
302
+ }
303
+ function compareVersions(left, right) {
304
+ const parts = (value) => {
305
+ const [core, ...pre] = value.split("+", 1)[0].split("-");
306
+ return { core: core.split(".").map(BigInt), pre: pre.length ? pre.join("-").split(".") : [] };
307
+ };
308
+ const a = parts(left);
309
+ const b = parts(right);
310
+ for (let i = 0; i < 3; i++) {
311
+ if (a.core[i] !== b.core[i])
312
+ return a.core[i] > b.core[i] ? 1 : -1;
313
+ }
314
+ if (!a.pre.length || !b.pre.length)
315
+ return Number(!a.pre.length) - Number(!b.pre.length);
316
+ for (let i = 0; i < Math.max(a.pre.length, b.pre.length); i++) {
317
+ const x = a.pre[i];
318
+ const y = b.pre[i];
319
+ if (x === y)
320
+ continue;
321
+ if (x === void 0 || y === void 0)
322
+ return x === void 0 ? -1 : 1;
323
+ const nx = /^\d+$/.test(x);
324
+ const ny = /^\d+$/.test(y);
325
+ if (nx && ny)
326
+ return BigInt(x) > BigInt(y) ? 1 : -1;
327
+ if (nx !== ny)
328
+ return nx ? -1 : 1;
329
+ return x > y ? 1 : -1;
330
+ }
331
+ return 0;
332
+ }
333
+ async function guardProjectVersion(rawArgs) {
334
+ if (rawArgs.some((arg) => ["--help", "-h"].includes(arg)) || rawArgs.length === 1 && ["--version", "-v"].includes(rawArgs[0]))
335
+ return;
336
+ if (["doctor", "config", "update"].includes(rawArgs[0] ?? ""))
337
+ return;
338
+ await selectProjectRuntime();
339
+ }
340
+
341
+ export {
342
+ selectArchitecture,
343
+ ARCHITECTURE_FILE,
344
+ MAX_CONTENT_BYTES,
345
+ digest,
346
+ projectPath,
347
+ readProjectFile,
348
+ documentHeadings,
349
+ queryArchitecture,
350
+ isWithin,
351
+ PROJECT_VERSION_PATH,
352
+ SOURCE_INVOCATION,
353
+ isExactVersion,
354
+ exactInvocation,
355
+ readProjectVersion,
356
+ readProjectRuntime,
357
+ selectProjectRuntime,
358
+ compareVersions,
359
+ guardProjectVersion
360
+ };