@seemseam/architec 0.2.11 → 0.2.12
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/README.md +6 -287
- package/bin/archi.js +2 -2
- package/package.json +10 -17
- package/README.zh-CN.md +0 -252
- package/docs/npm-release.md +0 -85
- package/lib/archi-dispatcher.js +0 -291
- package/scripts/check-release-assets.js +0 -101
package/README.md
CHANGED
|
@@ -1,293 +1,12 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @seemseam/architec
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Compatibility shim for the renamed `@seemseam/archi` package.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
[](#quick-start)
|
|
7
|
-
[](#no-login-required)
|
|
8
|
-
|
|
9
|
-
[English](README.md) | [中文](README.zh-CN.md)
|
|
10
|
-
|
|
11
|
-
Architec is an advisory architecture analysis CLI. It helps answer one
|
|
12
|
-
practical question:
|
|
13
|
-
|
|
14
|
-
> Will this change make the codebase harder to maintain?
|
|
15
|
-
|
|
16
|
-
It reviews current changes by default, asks an LLM to interpret compact
|
|
17
|
-
selected-scope evidence, and reports architecture risks such as duplicated
|
|
18
|
-
logic, shadow implementations, unclear boundaries, stale structure, topology
|
|
19
|
-
pressure, and risky hotspots.
|
|
20
|
-
|
|
21
|
-
Architec does not make merge decisions and does not edit code. It gives
|
|
22
|
-
structured advice for humans and coding agents to review.
|
|
23
|
-
|
|
24
|
-
## Why Architec
|
|
25
|
-
|
|
26
|
-
LLM-assisted development can move quickly, but architecture can drift quietly.
|
|
27
|
-
Architec is designed to catch the kinds of issues that accumulate over time:
|
|
28
|
-
|
|
29
|
-
- repeated implementations and "same idea twice" code;
|
|
30
|
-
- compatibility paths that blur into canonical implementations;
|
|
31
|
-
- changed files crossing intended module boundaries;
|
|
32
|
-
- stale cleanup/archive candidates;
|
|
33
|
-
- high-risk work landing in churn-heavy areas;
|
|
34
|
-
- full-project topology pressure that is easy to miss during local edits.
|
|
35
|
-
|
|
36
|
-
The default workflow is incremental-first:
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
archi
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
Use full review when you want the whole-project baseline:
|
|
43
|
-
|
|
44
|
-
```bash
|
|
45
|
-
archi --full
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## How It Fits Together
|
|
49
|
-
|
|
50
|
-
Architec is the review layer. It uses two companion components:
|
|
51
|
-
|
|
52
|
-
| Component | Command / package | Role |
|
|
53
|
-
| --- | --- | --- |
|
|
54
|
-
| **Architec** | `archi` / `architec` | Runs architecture review, calls the LLM through llmgateway, writes advisory results under `.architec/`. |
|
|
55
|
-
| **Hippos** | `hippos` / `seemseam-hippos` | Builds structural project snapshots under `.hippos/`: file manifests, code signatures, repository indexes, structure prompts, and metrics. |
|
|
56
|
-
| **llmgateway** | `llmgateway` | Owns provider credentials, base URLs, API style, model names, and strong/weak model routing. |
|
|
57
|
-
|
|
58
|
-
```text
|
|
59
|
-
source tree + git changes
|
|
60
|
-
|
|
|
61
|
-
v
|
|
62
|
-
Hippos structural snapshot -> .hippos/
|
|
63
|
-
|
|
|
64
|
-
v
|
|
65
|
-
Architec evidence builder -> selected-scope or full-project context
|
|
66
|
-
|
|
|
67
|
-
v
|
|
68
|
-
llmgateway LLM call -> strong / weak model tiers
|
|
69
|
-
|
|
|
70
|
-
v
|
|
71
|
-
Architec review output -> .architec/
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
Day-to-day `archi` runs still use the LLM, but they avoid refreshing the whole
|
|
75
|
-
Hippos snapshot unless requested. `archi --full` uses the Hippos snapshot more
|
|
76
|
-
heavily, and `archi --refresh-from-hippos --full` refreshes it before review.
|
|
77
|
-
|
|
78
|
-
## How It Works
|
|
79
|
-
|
|
80
|
-
Architec combines deterministic code signals with LLM interpretation. The
|
|
81
|
-
deterministic layer keeps the review grounded in concrete evidence; the LLM
|
|
82
|
-
layer turns that evidence into readable architecture advice.
|
|
83
|
-
|
|
84
|
-
1. **Select scope**
|
|
85
|
-
- `archi` reads the current git changes and focuses on changed files.
|
|
86
|
-
- `archi --full` reviews the whole project.
|
|
87
|
-
|
|
88
|
-
2. **Read structural context**
|
|
89
|
-
- Hippos produces `.hippos/` snapshots: file manifests, code signatures,
|
|
90
|
-
repository indexes, metrics, and structure prompts.
|
|
91
|
-
- Architec checks whether that snapshot is present, stale, or unknown.
|
|
92
|
-
|
|
93
|
-
3. **Build architecture evidence**
|
|
94
|
-
- Architec runs static scanners for duplicated logic, shadow
|
|
95
|
-
implementations, import-boundary pressure, cleanup/archive candidates,
|
|
96
|
-
hotspots, topology pressure, and snapshot freshness.
|
|
97
|
-
- Incremental review keeps selected-change concerns separate from broader
|
|
98
|
-
project context so small diffs are not drowned by global noise.
|
|
99
|
-
|
|
100
|
-
4. **Ask the LLM for interpretation**
|
|
101
|
-
- Architec sends compact evidence to llmgateway.
|
|
102
|
-
- llmgateway chooses the configured strong or weak model tier and owns all
|
|
103
|
-
provider credentials.
|
|
104
|
-
|
|
105
|
-
5. **Write advisory output**
|
|
106
|
-
- Architec ranks concerns, keeps raw artifacts for inspection, and writes
|
|
107
|
-
human-readable plus machine-readable output under `.architec/`.
|
|
108
|
-
- The result is advice, not an automatic merge decision or proof of runtime
|
|
109
|
-
correctness.
|
|
110
|
-
|
|
111
|
-
## Install
|
|
112
|
-
|
|
113
|
-
Architec requires Python 3.11+.
|
|
114
|
-
|
|
115
|
-
Recommended install from PyPI:
|
|
116
|
-
|
|
117
|
-
```bash
|
|
118
|
-
python3 -m pip install --user architec
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
This installs:
|
|
122
|
-
|
|
123
|
-
- `archi`, the Architec CLI;
|
|
124
|
-
- `seemseam-llmgateway`, the package that provides the LLM provider gateway;
|
|
125
|
-
- `seemseam-hippos`, the package that provides Hippos structural snapshots.
|
|
126
|
-
|
|
127
|
-
The runtime imports remain `llmgateway` and `hippos`; no separate package index
|
|
128
|
-
setup is required.
|
|
129
|
-
|
|
130
|
-
Local development from this repository:
|
|
131
|
-
|
|
132
|
-
```bash
|
|
133
|
-
python3 -m pip install -e .
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
## Configure LLM Access
|
|
137
|
-
|
|
138
|
-
Architec gets all LLM access through **llmgateway**. Configure provider
|
|
139
|
-
credentials and model tiers in:
|
|
140
|
-
|
|
141
|
-
```text
|
|
142
|
-
~/.llmgateway/config.yaml
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
Minimal example:
|
|
146
|
-
|
|
147
|
-
```yaml
|
|
148
|
-
version: 1
|
|
149
|
-
provider:
|
|
150
|
-
provider_type: openai
|
|
151
|
-
api_style: openai_responses
|
|
152
|
-
base_url: https://your-llm-endpoint/v1
|
|
153
|
-
api_key: sk-...
|
|
154
|
-
settings:
|
|
155
|
-
strong_model: your-strong-model
|
|
156
|
-
weak_model: your-fast-model
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
Architec consumes the configured `strong_model` and `weak_model` tiers. It does
|
|
160
|
-
not store model-provider credentials itself.
|
|
161
|
-
|
|
162
|
-
Check the installation and LLM route:
|
|
5
|
+
Install the primary package for new setups:
|
|
163
6
|
|
|
164
7
|
```bash
|
|
165
|
-
|
|
8
|
+
npm install -g @seemseam/archi
|
|
166
9
|
```
|
|
167
10
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
## Quick Start
|
|
172
|
-
|
|
173
|
-
Review the current selected changes:
|
|
174
|
-
|
|
175
|
-
```bash
|
|
176
|
-
archi
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
Run whole-project architecture review:
|
|
180
|
-
|
|
181
|
-
```bash
|
|
182
|
-
archi --full
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
Save JSON output:
|
|
186
|
-
|
|
187
|
-
```bash
|
|
188
|
-
archi --out review.json
|
|
189
|
-
archi --full --out full-review.json
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
Refresh Hippos inputs before full review:
|
|
193
|
-
|
|
194
|
-
```bash
|
|
195
|
-
archi --refresh-from-hippos --full
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
## Command Summary
|
|
199
|
-
|
|
200
|
-
| Command | Purpose |
|
|
201
|
-
| --- | --- |
|
|
202
|
-
| `archi` | Incremental LLM architecture review for current selected changes. |
|
|
203
|
-
| `archi --full` | Full-project LLM architecture review. |
|
|
204
|
-
| `archi --out review.json` | Save incremental review JSON. |
|
|
205
|
-
| `archi --full --out full-review.json` | Save full-review JSON. |
|
|
206
|
-
| `archi --refresh-from-hippos --full` | Refresh Hippos structural inputs, then run full review. |
|
|
207
|
-
| `archi --check .` | Validate Hippos bundle state and llmgateway configuration. |
|
|
208
|
-
|
|
209
|
-
Advanced compatibility flags and older subcommands may still be accepted for
|
|
210
|
-
existing automation, but new usage should prefer the commands above.
|
|
211
|
-
|
|
212
|
-
## What Architec Reports
|
|
213
|
-
|
|
214
|
-
Architec reports advisory concerns and signals, including:
|
|
215
|
-
|
|
216
|
-
- **Duplication**: repeated logic and suspicious near-duplicates.
|
|
217
|
-
- **Shadow implementations**: second implementations of similar behavior.
|
|
218
|
-
- **Architecture contracts**: import-boundary or dependency-direction pressure.
|
|
219
|
-
- **Cleanup/archive candidates**: stale or legacy-looking code and docs.
|
|
220
|
-
- **Hotspots**: churn-heavy or structurally risky areas.
|
|
221
|
-
- **Topology pressure**: flat or confusing project structure.
|
|
222
|
-
- **Snapshot freshness**: missing, stale, or unknown Hippos context.
|
|
223
|
-
- **Risk context**: optional external facts attached to existing concerns.
|
|
224
|
-
|
|
225
|
-
The output is advisory. It is not a pass/fail result and is not proof of
|
|
226
|
-
runtime correctness.
|
|
227
|
-
|
|
228
|
-
## Outputs
|
|
229
|
-
|
|
230
|
-
Architec writes generated files under `.architec/`:
|
|
231
|
-
|
|
232
|
-
```text
|
|
233
|
-
.architec/
|
|
234
|
-
architec-analysis.json
|
|
235
|
-
architec-summary.md
|
|
236
|
-
architec-viz.html
|
|
237
|
-
code-review-concerns.json
|
|
238
|
-
code-review-discovery.json
|
|
239
|
-
review-events.jsonl
|
|
240
|
-
cache/
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
Hippos writes structural inputs under `.hippos/`.
|
|
244
|
-
|
|
245
|
-
Start with `.architec/architec-summary.md` for the human-readable report, then
|
|
246
|
-
open `.architec/architec-analysis.json` for exact scores, concerns, signals,
|
|
247
|
-
and artifact paths.
|
|
248
|
-
|
|
249
|
-
## Agent Command Compatibility
|
|
250
|
-
|
|
251
|
-
The commands above describe the current public workflow. Some older installed
|
|
252
|
-
`archi` binaries may still show the previous command shape, where full review
|
|
253
|
-
is `archi .` and incremental review is `archi --diff .`.
|
|
254
|
-
|
|
255
|
-
Agents and automation should inspect the local binary before choosing commands:
|
|
256
|
-
|
|
257
|
-
```bash
|
|
258
|
-
archi --help
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
| Help output | Incremental review | Full review |
|
|
262
|
-
| --- | --- | --- |
|
|
263
|
-
| Includes `--full` | `archi` | `archi --full` |
|
|
264
|
-
| Lacks `--full` but includes `--diff` | `archi --diff .` | `archi .` |
|
|
265
|
-
|
|
266
|
-
## No Login Required
|
|
267
|
-
|
|
268
|
-
Architecture analysis does not require `archi login`.
|
|
269
|
-
|
|
270
|
-
Account commands such as `archi login`, `archi whoami --json`, and
|
|
271
|
-
`archi devices --json` may exist for diagnostics, but they are not part of
|
|
272
|
-
normal Architec analysis.
|
|
273
|
-
|
|
274
|
-
## Development
|
|
275
|
-
|
|
276
|
-
Run tests:
|
|
277
|
-
|
|
278
|
-
```bash
|
|
279
|
-
PYTHONPATH=src python3 -m pytest -q
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
Run Architec from this checkout:
|
|
283
|
-
|
|
284
|
-
```bash
|
|
285
|
-
PYTHONPATH=src python3 -m architec
|
|
286
|
-
PYTHONPATH=src python3 -m architec --full
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
## More Documentation
|
|
290
|
-
|
|
291
|
-
- [Usage manual](docs/usage-manual.md)
|
|
292
|
-
- [Architecture stability notes](docs/advisory-review/topics/architecture-stability.md)
|
|
293
|
-
- [Evidence model](docs/advisory-review/topics/evidence-model.md)
|
|
11
|
+
This package keeps the historical `@seemseam/architec` name installable and
|
|
12
|
+
forwards the `archi` command to `@seemseam/archi`.
|
package/bin/archi.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
|
-
const { main } = require("
|
|
4
|
+
const { main } = require("@seemseam/archi/lib/archi-dispatcher");
|
|
5
5
|
|
|
6
6
|
main(process.argv.slice(2))
|
|
7
7
|
.then((status) => {
|
|
8
8
|
process.exit(status === undefined || status === null ? 0 : status);
|
|
9
9
|
})
|
|
10
10
|
.catch((error) => {
|
|
11
|
-
console.error(`archi npm
|
|
11
|
+
console.error(`archi npm shim: ${error.message}`);
|
|
12
12
|
process.exit(1);
|
|
13
13
|
});
|
package/package.json
CHANGED
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seemseam/architec",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "
|
|
5
|
-
"keywords": [
|
|
6
|
-
"architecture",
|
|
7
|
-
"architecture-review",
|
|
8
|
-
"binary",
|
|
9
|
-
"cli",
|
|
10
|
-
"code-analysis",
|
|
11
|
-
"llm"
|
|
12
|
-
],
|
|
3
|
+
"version": "0.2.12",
|
|
4
|
+
"description": "Compatibility shim for the renamed @seemseam/archi package.",
|
|
13
5
|
"homepage": "https://github.com/SeemSeam/architec#readme",
|
|
14
6
|
"author": "SeemSeam",
|
|
15
7
|
"bugs": {
|
|
@@ -17,7 +9,8 @@
|
|
|
17
9
|
},
|
|
18
10
|
"repository": {
|
|
19
11
|
"type": "git",
|
|
20
|
-
"url": "https://github.com/SeemSeam/architec"
|
|
12
|
+
"url": "https://github.com/SeemSeam/architec",
|
|
13
|
+
"directory": "npm/architec-shim"
|
|
21
14
|
},
|
|
22
15
|
"license": "MIT",
|
|
23
16
|
"bin": {
|
|
@@ -25,11 +18,12 @@
|
|
|
25
18
|
},
|
|
26
19
|
"files": [
|
|
27
20
|
"bin/",
|
|
28
|
-
"
|
|
29
|
-
"scripts/check-release-assets.js",
|
|
30
|
-
"docs/npm-release.md",
|
|
21
|
+
"README.md",
|
|
31
22
|
"LICENSE"
|
|
32
23
|
],
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@seemseam/archi": "0.2.12"
|
|
26
|
+
},
|
|
33
27
|
"engines": {
|
|
34
28
|
"node": ">=18"
|
|
35
29
|
},
|
|
@@ -37,8 +31,7 @@
|
|
|
37
31
|
"access": "public"
|
|
38
32
|
},
|
|
39
33
|
"scripts": {
|
|
40
|
-
"test": "node --check bin/archi.js
|
|
41
|
-
"pack:dry-run": "npm pack --dry-run --json"
|
|
42
|
-
"release-assets:check": "node scripts/check-release-assets.js"
|
|
34
|
+
"test": "node --check bin/archi.js",
|
|
35
|
+
"pack:dry-run": "npm pack --dry-run --json"
|
|
43
36
|
}
|
|
44
37
|
}
|
package/README.zh-CN.md
DELETED
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
# Architec
|
|
2
|
-
|
|
3
|
-
**面向 AI 辅助开发代码库的增量架构审查 CLI。**
|
|
4
|
-
|
|
5
|
-
[](https://www.python.org/)
|
|
6
|
-
[](README.md#quick-start)
|
|
7
|
-
[](#无需登录)
|
|
8
|
-
|
|
9
|
-
[English](README.md) | [中文](README.zh-CN.md)
|
|
10
|
-
|
|
11
|
-
Architec 是一个建议型架构分析工具,核心问题是:
|
|
12
|
-
|
|
13
|
-
> 这次改动会不会让项目更难长期维护?
|
|
14
|
-
|
|
15
|
-
默认命令是:
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
archi
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
它会审查当前 git 变更,构建紧凑的架构证据,并通过 LLM 给出可读的架构建议。
|
|
22
|
-
如果需要查看整个项目的架构基线,使用:
|
|
23
|
-
|
|
24
|
-
```bash
|
|
25
|
-
archi --full
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
Architec 只给建议,不做合并判定,不自动修改代码,也不要求登录。
|
|
29
|
-
|
|
30
|
-
## 为什么需要 Architec
|
|
31
|
-
|
|
32
|
-
AI 辅助开发会显著加快编码速度,但也容易带来架构漂移:
|
|
33
|
-
|
|
34
|
-
- 新实现重复造轮子;
|
|
35
|
-
- 兼容路径和主实现混在一起;
|
|
36
|
-
- 小改动绕过了模块边界;
|
|
37
|
-
- helper 逐渐膨胀成无归属子系统;
|
|
38
|
-
- 陈旧代码、陈旧文档和清理候选长期堆积;
|
|
39
|
-
- 高风险文件在高 churn 区域继续扩张。
|
|
40
|
-
|
|
41
|
-
Architec 会把这些信号整理成结构化审查结果,帮助人类 reviewer 或 coding agent
|
|
42
|
-
更快判断哪些建议值得进一步检查。
|
|
43
|
-
|
|
44
|
-
## Architec、Hippos 和 llmgateway
|
|
45
|
-
|
|
46
|
-
Architec 本身是架构审查层,依赖两个运行时组件:
|
|
47
|
-
|
|
48
|
-
| 组件 | 命令 / 包 | 作用 |
|
|
49
|
-
| --- | --- | --- |
|
|
50
|
-
| **Architec** | `archi` / `architec` | 执行架构审查,通过 llmgateway 调用 LLM,并把结果写入 `.architec/`。 |
|
|
51
|
-
| **Hippos** | `hippos` / `seemseam-hippos` | 生成 `.hippos/` 结构快照,包括文件清单、代码签名、仓库索引、结构 prompt 和指标。 |
|
|
52
|
-
| **llmgateway** | `llmgateway` | 管理 provider 凭据、base URL、API 风格、模型名,以及 strong/weak 模型路由。 |
|
|
53
|
-
|
|
54
|
-
```text
|
|
55
|
-
源码树 + git 变更
|
|
56
|
-
|
|
|
57
|
-
v
|
|
58
|
-
Hippos 结构快照 -> .hippos/
|
|
59
|
-
|
|
|
60
|
-
v
|
|
61
|
-
Architec 证据构建 -> 增量范围或全项目上下文
|
|
62
|
-
|
|
|
63
|
-
v
|
|
64
|
-
llmgateway LLM 调用 -> strong / weak 模型
|
|
65
|
-
|
|
|
66
|
-
v
|
|
67
|
-
Architec 审查输出 -> .architec/
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
日常 `archi` 仍然会使用 LLM,但不会默认刷新完整 Hippos 快照。`archi --full`
|
|
71
|
-
更依赖 Hippos 的全项目结构信息;如果要先刷新结构快照,可以运行:
|
|
72
|
-
|
|
73
|
-
```bash
|
|
74
|
-
archi --refresh-from-hippos --full
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
## 工作原理
|
|
78
|
-
|
|
79
|
-
Architec 结合确定性代码信号和 LLM 解释:
|
|
80
|
-
|
|
81
|
-
1. **选择范围**
|
|
82
|
-
- `archi` 读取当前 git 变更,聚焦 changed files。
|
|
83
|
-
- `archi --full` 审查整个项目。
|
|
84
|
-
|
|
85
|
-
2. **读取结构上下文**
|
|
86
|
-
- Hippos 生成 `.hippos/` 快照。
|
|
87
|
-
- Architec 判断快照是否存在、是否陈旧、是否无法判断 freshness。
|
|
88
|
-
|
|
89
|
-
3. **构建架构证据**
|
|
90
|
-
- 静态扫描重复逻辑、shadow implementation、边界压力、清理候选、热点、拓扑压力等。
|
|
91
|
-
- 增量审查会把 selected-change concerns 和全局上下文分开,避免小 diff 被全局噪音淹没。
|
|
92
|
-
|
|
93
|
-
4. **交给 LLM 解释**
|
|
94
|
-
- Architec 把紧凑证据发送给 llmgateway。
|
|
95
|
-
- llmgateway 根据配置选择 strong 或 weak 模型,并负责 provider 凭据。
|
|
96
|
-
|
|
97
|
-
5. **输出建议**
|
|
98
|
-
- Architec 对 concerns 排序,保留原始 artifacts,并生成 Markdown / JSON 输出。
|
|
99
|
-
- 输出是架构建议,不是 pass/fail,也不是运行时正确性的证明。
|
|
100
|
-
|
|
101
|
-
## 安装
|
|
102
|
-
|
|
103
|
-
需要 Python 3.11+。
|
|
104
|
-
|
|
105
|
-
推荐从 PyPI 安装:
|
|
106
|
-
|
|
107
|
-
```bash
|
|
108
|
-
python3 -m pip install --user architec
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
这会安装:
|
|
112
|
-
|
|
113
|
-
- `archi`:Architec CLI;
|
|
114
|
-
- `seemseam-llmgateway`:提供 LLM provider 网关能力的包;
|
|
115
|
-
- `seemseam-hippos`:提供 Hippos 结构快照能力的包。
|
|
116
|
-
|
|
117
|
-
运行时 import 名仍是 `llmgateway` 和 `hippos`,不需要额外配置 Python 包索引。
|
|
118
|
-
|
|
119
|
-
本地开发安装:
|
|
120
|
-
|
|
121
|
-
```bash
|
|
122
|
-
python3 -m pip install -e .
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
## 配置 LLM
|
|
126
|
-
|
|
127
|
-
Architec 的 LLM 调用全部通过 **llmgateway**。请在下面的文件中配置 provider
|
|
128
|
-
和 strong/weak 模型:
|
|
129
|
-
|
|
130
|
-
```text
|
|
131
|
-
~/.llmgateway/config.yaml
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
最小示例:
|
|
135
|
-
|
|
136
|
-
```yaml
|
|
137
|
-
version: 1
|
|
138
|
-
provider:
|
|
139
|
-
provider_type: openai
|
|
140
|
-
api_style: openai_responses
|
|
141
|
-
base_url: https://your-llm-endpoint/v1
|
|
142
|
-
api_key: sk-...
|
|
143
|
-
settings:
|
|
144
|
-
strong_model: your-strong-model
|
|
145
|
-
weak_model: your-fast-model
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
检查安装和 LLM 路由:
|
|
149
|
-
|
|
150
|
-
```bash
|
|
151
|
-
archi --check .
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
## 快速使用
|
|
155
|
-
|
|
156
|
-
审查当前变更:
|
|
157
|
-
|
|
158
|
-
```bash
|
|
159
|
-
archi
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
全项目架构审查:
|
|
163
|
-
|
|
164
|
-
```bash
|
|
165
|
-
archi --full
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
保存 JSON 输出:
|
|
169
|
-
|
|
170
|
-
```bash
|
|
171
|
-
archi --out review.json
|
|
172
|
-
archi --full --out full-review.json
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
刷新 Hippos 快照后再全量审查:
|
|
176
|
-
|
|
177
|
-
```bash
|
|
178
|
-
archi --refresh-from-hippos --full
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
## 命令速查
|
|
182
|
-
|
|
183
|
-
| 命令 | 用途 |
|
|
184
|
-
| --- | --- |
|
|
185
|
-
| `archi` | 对当前 selected changes 进行增量 LLM 架构审查。 |
|
|
186
|
-
| `archi --full` | 对整个项目进行 LLM 架构审查。 |
|
|
187
|
-
| `archi --out review.json` | 保存增量审查 JSON。 |
|
|
188
|
-
| `archi --full --out full-review.json` | 保存全量审查 JSON。 |
|
|
189
|
-
| `archi --refresh-from-hippos --full` | 刷新 Hippos 结构输入后运行全量审查。 |
|
|
190
|
-
| `archi --check .` | 检查 Hippos bundle 状态和 llmgateway 配置。 |
|
|
191
|
-
|
|
192
|
-
## 输出
|
|
193
|
-
|
|
194
|
-
Architec 写入 `.architec/`:
|
|
195
|
-
|
|
196
|
-
```text
|
|
197
|
-
.architec/
|
|
198
|
-
architec-analysis.json
|
|
199
|
-
architec-summary.md
|
|
200
|
-
architec-viz.html
|
|
201
|
-
code-review-concerns.json
|
|
202
|
-
code-review-discovery.json
|
|
203
|
-
review-events.jsonl
|
|
204
|
-
cache/
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
Hippos 写入 `.hippos/`。
|
|
208
|
-
|
|
209
|
-
建议先读 `.architec/architec-summary.md`,需要精确字段时再看
|
|
210
|
-
`.architec/architec-analysis.json`。
|
|
211
|
-
|
|
212
|
-
## Agent 命令兼容
|
|
213
|
-
|
|
214
|
-
当前公开工作流是 `archi` 和 `archi --full`。部分旧版本 `archi` 可能仍显示旧命令形态:
|
|
215
|
-
全量审查是 `archi .`,增量审查是 `archi --diff .`。
|
|
216
|
-
|
|
217
|
-
Agent 和自动化脚本应先检查本机命令:
|
|
218
|
-
|
|
219
|
-
```bash
|
|
220
|
-
archi --help
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
| Help 输出 | 增量审查 | 全量审查 |
|
|
224
|
-
| --- | --- | --- |
|
|
225
|
-
| 包含 `--full` | `archi` | `archi --full` |
|
|
226
|
-
| 不包含 `--full` 但包含 `--diff` | `archi --diff .` | `archi .` |
|
|
227
|
-
|
|
228
|
-
## 无需登录
|
|
229
|
-
|
|
230
|
-
架构分析不需要 `archi login`。账号相关命令可能用于诊断,但不是日常 Architec
|
|
231
|
-
分析流程的一部分。
|
|
232
|
-
|
|
233
|
-
## 开发
|
|
234
|
-
|
|
235
|
-
运行测试:
|
|
236
|
-
|
|
237
|
-
```bash
|
|
238
|
-
PYTHONPATH=src python3 -m pytest -q
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
从当前 checkout 运行 Architec:
|
|
242
|
-
|
|
243
|
-
```bash
|
|
244
|
-
PYTHONPATH=src python3 -m architec
|
|
245
|
-
PYTHONPATH=src python3 -m architec --full
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
## 更多文档
|
|
249
|
-
|
|
250
|
-
- [使用手册](docs/usage-manual.md)
|
|
251
|
-
- [架构稳定性说明](docs/advisory-review/topics/architecture-stability.md)
|
|
252
|
-
- [证据模型](docs/advisory-review/topics/evidence-model.md)
|
package/docs/npm-release.md
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
# npm Release Notes
|
|
2
|
-
|
|
3
|
-
`@seemseam/architec` is a binary dispatcher for the Architec CLI. It is not a
|
|
4
|
-
Node.js rewrite and it must not bundle the Python source trees for `architec`,
|
|
5
|
-
`seemseam_hippos`, or `seemseam_llmgateway`.
|
|
6
|
-
|
|
7
|
-
## Package Identity
|
|
8
|
-
|
|
9
|
-
- npm package: `@seemseam/architec`
|
|
10
|
-
- CLI command: `archi`
|
|
11
|
-
- npm version: must match the released Architec version on PyPI and the
|
|
12
|
-
GitHub tag, for example `0.2.11` and `v0.2.11`
|
|
13
|
-
- source package: PyPI `architec`
|
|
14
|
-
- binary source: GitHub Release standalone `archi` assets
|
|
15
|
-
|
|
16
|
-
The unscoped npm package `architec` is owned by someone else and is not used.
|
|
17
|
-
|
|
18
|
-
## Dispatcher Strategy
|
|
19
|
-
|
|
20
|
-
The npm package contains a small Node.js dispatcher. On first run it:
|
|
21
|
-
|
|
22
|
-
1. maps the current OS/CPU to a release asset name;
|
|
23
|
-
2. downloads `archi-v<version>-checksums.txt` from the matching GitHub Release;
|
|
24
|
-
3. downloads the matching `archi-v<version>-<platform>` binary;
|
|
25
|
-
4. verifies the SHA-256 checksum before caching the binary;
|
|
26
|
-
5. executes the cached binary on later runs.
|
|
27
|
-
|
|
28
|
-
Default release URL:
|
|
29
|
-
|
|
30
|
-
```text
|
|
31
|
-
https://github.com/SeemSeam/architec/releases/download/v<version>/
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
The first npm release gate requires these assets for the selected platform
|
|
35
|
-
matrix:
|
|
36
|
-
|
|
37
|
-
```text
|
|
38
|
-
archi-v0.2.11-linux-x64
|
|
39
|
-
archi-v0.2.11-darwin-x64
|
|
40
|
-
archi-v0.2.11-darwin-arm64
|
|
41
|
-
archi-v0.2.11-win32-x64.exe
|
|
42
|
-
archi-v0.2.11-checksums.txt
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
`linux-arm64` is supported by the dispatcher if an asset exists, but it is not
|
|
46
|
-
part of the default first-release gate until a real Linux arm64 build runner and
|
|
47
|
-
smoke test are selected.
|
|
48
|
-
|
|
49
|
-
## Local Verification
|
|
50
|
-
|
|
51
|
-
```bash
|
|
52
|
-
npm test
|
|
53
|
-
npm run pack:dry-run
|
|
54
|
-
ARCHITEC_NPM_REQUIRED_TRIPLETS=linux-x64 npm run release-assets:check
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
The dispatcher smoke test uses a local file URL fixture and does not contact
|
|
58
|
-
GitHub. `release-assets:check` contacts GitHub and fails until the matching
|
|
59
|
-
GitHub Release binary assets and checksum file exist.
|
|
60
|
-
|
|
61
|
-
## Trusted Publishing
|
|
62
|
-
|
|
63
|
-
Configure npm Trusted Publishing for the existing package:
|
|
64
|
-
|
|
65
|
-
```text
|
|
66
|
-
Provider: GitHub Actions
|
|
67
|
-
Organization or user: SeemSeam
|
|
68
|
-
Repository: architec
|
|
69
|
-
Workflow filename: npm.yml
|
|
70
|
-
Environment name: <blank>
|
|
71
|
-
Allowed actions: npm publish
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
The workflow must exist at `.github/workflows/npm.yml`, use a GitHub-hosted
|
|
75
|
-
runner, use Node 24 with npm 11.5.1 or newer, set `permissions:
|
|
76
|
-
id-token: write`, and publish without `NODE_AUTH_TOKEN`.
|
|
77
|
-
|
|
78
|
-
## Release Order
|
|
79
|
-
|
|
80
|
-
1. Publish `architec` to PyPI.
|
|
81
|
-
2. Build standalone `archi` binaries from the released PyPI package set.
|
|
82
|
-
3. Upload binaries and `archi-v<version>-checksums.txt` to the matching GitHub
|
|
83
|
-
Release.
|
|
84
|
-
4. Verify `npm test`, `npm pack --dry-run`, and `release-assets:check`.
|
|
85
|
-
5. Publish `@seemseam/architec@<version>` through npm Trusted Publishing.
|
package/lib/archi-dispatcher.js
DELETED
|
@@ -1,291 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const crypto = require("node:crypto");
|
|
4
|
-
const fs = require("node:fs");
|
|
5
|
-
const http = require("node:http");
|
|
6
|
-
const https = require("node:https");
|
|
7
|
-
const os = require("node:os");
|
|
8
|
-
const path = require("node:path");
|
|
9
|
-
const { spawn } = require("node:child_process");
|
|
10
|
-
const { fileURLToPath } = require("node:url");
|
|
11
|
-
|
|
12
|
-
const PACKAGE_ROOT = path.resolve(__dirname, "..");
|
|
13
|
-
const PACKAGE_JSON = JSON.parse(
|
|
14
|
-
fs.readFileSync(path.join(PACKAGE_ROOT, "package.json"), "utf8"),
|
|
15
|
-
);
|
|
16
|
-
const VERSION = PACKAGE_JSON.version;
|
|
17
|
-
const OWNER = "SeemSeam";
|
|
18
|
-
const REPO = "architec";
|
|
19
|
-
const SUPPORTED_TRIPLETS = new Set([
|
|
20
|
-
"linux-x64",
|
|
21
|
-
"linux-arm64",
|
|
22
|
-
"darwin-x64",
|
|
23
|
-
"darwin-arm64",
|
|
24
|
-
"win32-x64",
|
|
25
|
-
]);
|
|
26
|
-
|
|
27
|
-
function fail(message) {
|
|
28
|
-
throw new Error(message);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function platformTriplet(platform = process.platform, arch = process.arch) {
|
|
32
|
-
const mappedArch = arch === "x64" || arch === "arm64" ? arch : "";
|
|
33
|
-
if (!mappedArch) {
|
|
34
|
-
fail(`unsupported CPU architecture: ${arch}`);
|
|
35
|
-
}
|
|
36
|
-
if (platform === "linux" || platform === "darwin") {
|
|
37
|
-
return `${platform}-${mappedArch}`;
|
|
38
|
-
}
|
|
39
|
-
if (platform === "win32" && mappedArch === "x64") {
|
|
40
|
-
return "win32-x64";
|
|
41
|
-
}
|
|
42
|
-
fail(`unsupported platform: ${platform}-${arch}`);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function assetNameFor(triplet, version = VERSION) {
|
|
46
|
-
if (!SUPPORTED_TRIPLETS.has(triplet)) {
|
|
47
|
-
fail(`unsupported binary target: ${triplet}`);
|
|
48
|
-
}
|
|
49
|
-
const extension = triplet.startsWith("win32-") ? ".exe" : "";
|
|
50
|
-
return `archi-v${version}-${triplet}${extension}`;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function checksumFileName(version = VERSION) {
|
|
54
|
-
return `archi-v${version}-checksums.txt`;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function defaultCacheBase() {
|
|
58
|
-
if (process.platform === "win32") {
|
|
59
|
-
return (
|
|
60
|
-
process.env.LOCALAPPDATA ||
|
|
61
|
-
path.join(os.homedir(), "AppData", "Local")
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
if (process.platform === "darwin") {
|
|
65
|
-
return path.join(os.homedir(), "Library", "Caches");
|
|
66
|
-
}
|
|
67
|
-
return process.env.XDG_CACHE_HOME || path.join(os.homedir(), ".cache");
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function cacheRoot() {
|
|
71
|
-
return (
|
|
72
|
-
process.env.ARCHITEC_NPM_CACHE_DIR ||
|
|
73
|
-
path.join(defaultCacheBase(), "architec", "npm")
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function releaseBaseUrl() {
|
|
78
|
-
return (
|
|
79
|
-
process.env.ARCHITEC_NPM_RELEASE_BASE_URL ||
|
|
80
|
-
`https://github.com/${OWNER}/${REPO}/releases/download/v${VERSION}/`
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function joinUrl(base, name) {
|
|
85
|
-
const normalized = base.endsWith("/") ? base : `${base}/`;
|
|
86
|
-
return new URL(name, normalized).toString();
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function sha256File(filePath) {
|
|
90
|
-
return new Promise((resolve, reject) => {
|
|
91
|
-
const hash = crypto.createHash("sha256");
|
|
92
|
-
const stream = fs.createReadStream(filePath);
|
|
93
|
-
stream.on("data", (chunk) => hash.update(chunk));
|
|
94
|
-
stream.on("error", reject);
|
|
95
|
-
stream.on("end", () => resolve(hash.digest("hex")));
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function request(url, redirects = 0) {
|
|
100
|
-
return new Promise((resolve, reject) => {
|
|
101
|
-
const parsed = new URL(url);
|
|
102
|
-
const client = parsed.protocol === "http:" ? http : https;
|
|
103
|
-
const req = client.get(
|
|
104
|
-
parsed,
|
|
105
|
-
{
|
|
106
|
-
headers: {
|
|
107
|
-
"User-Agent": `${PACKAGE_JSON.name}/${VERSION}`,
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
|
-
(res) => {
|
|
111
|
-
if (
|
|
112
|
-
res.statusCode >= 300 &&
|
|
113
|
-
res.statusCode < 400 &&
|
|
114
|
-
res.headers.location
|
|
115
|
-
) {
|
|
116
|
-
res.resume();
|
|
117
|
-
if (redirects >= 5) {
|
|
118
|
-
reject(new Error(`too many redirects while fetching ${url}`));
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
resolve(request(new URL(res.headers.location, url).toString(), redirects + 1));
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
if (res.statusCode !== 200) {
|
|
125
|
-
res.resume();
|
|
126
|
-
reject(new Error(`HTTP ${res.statusCode} while fetching ${url}`));
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
resolve(res);
|
|
130
|
-
},
|
|
131
|
-
);
|
|
132
|
-
req.on("error", reject);
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
async function readUrl(url) {
|
|
137
|
-
const parsed = new URL(url);
|
|
138
|
-
if (parsed.protocol === "file:") {
|
|
139
|
-
return fs.promises.readFile(fileURLToPath(parsed));
|
|
140
|
-
}
|
|
141
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
142
|
-
fail(`unsupported URL protocol: ${parsed.protocol}`);
|
|
143
|
-
}
|
|
144
|
-
const res = await request(url);
|
|
145
|
-
const chunks = [];
|
|
146
|
-
for await (const chunk of res) {
|
|
147
|
-
chunks.push(chunk);
|
|
148
|
-
}
|
|
149
|
-
return Buffer.concat(chunks);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
async function downloadUrl(url, targetPath) {
|
|
153
|
-
const parsed = new URL(url);
|
|
154
|
-
if (parsed.protocol === "file:") {
|
|
155
|
-
await fs.promises.copyFile(fileURLToPath(parsed), targetPath);
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
159
|
-
fail(`unsupported URL protocol: ${parsed.protocol}`);
|
|
160
|
-
}
|
|
161
|
-
const res = await request(url);
|
|
162
|
-
await new Promise((resolve, reject) => {
|
|
163
|
-
const stream = fs.createWriteStream(targetPath, { mode: 0o755 });
|
|
164
|
-
res.pipe(stream);
|
|
165
|
-
res.on("error", reject);
|
|
166
|
-
stream.on("error", reject);
|
|
167
|
-
stream.on("finish", resolve);
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function parseChecksums(text) {
|
|
172
|
-
const checksums = new Map();
|
|
173
|
-
for (const line of text.split(/\r?\n/)) {
|
|
174
|
-
const trimmed = line.trim();
|
|
175
|
-
if (!trimmed || trimmed.startsWith("#")) {
|
|
176
|
-
continue;
|
|
177
|
-
}
|
|
178
|
-
const parts = trimmed.split(/\s+/);
|
|
179
|
-
if (parts.length < 2) {
|
|
180
|
-
continue;
|
|
181
|
-
}
|
|
182
|
-
if (/^[a-fA-F0-9]{64}$/.test(parts[0])) {
|
|
183
|
-
checksums.set(parts[1].replace(/^\*/, ""), parts[0].toLowerCase());
|
|
184
|
-
} else if (/^[a-fA-F0-9]{64}$/.test(parts[1])) {
|
|
185
|
-
checksums.set(parts[0].replace(/^\*/, ""), parts[1].toLowerCase());
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
return checksums;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
async function expectedChecksum(assetName) {
|
|
192
|
-
const checksumUrl =
|
|
193
|
-
process.env.ARCHITEC_NPM_CHECKSUM_URL ||
|
|
194
|
-
joinUrl(releaseBaseUrl(), checksumFileName());
|
|
195
|
-
const checksumText = (await readUrl(checksumUrl)).toString("utf8");
|
|
196
|
-
const checksums = parseChecksums(checksumText);
|
|
197
|
-
const expected = checksums.get(assetName);
|
|
198
|
-
if (!expected) {
|
|
199
|
-
fail(`checksum for ${assetName} was not found in ${checksumUrl}`);
|
|
200
|
-
}
|
|
201
|
-
return expected;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function markerMatches(markerPath, assetName, expected) {
|
|
205
|
-
try {
|
|
206
|
-
const marker = JSON.parse(fs.readFileSync(markerPath, "utf8"));
|
|
207
|
-
return (
|
|
208
|
-
marker.version === VERSION &&
|
|
209
|
-
marker.assetName === assetName &&
|
|
210
|
-
marker.sha256 === expected
|
|
211
|
-
);
|
|
212
|
-
} catch {
|
|
213
|
-
return false;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
async function ensureBinary() {
|
|
218
|
-
const configuredBinary = process.env.ARCHITEC_NPM_BINARY_PATH;
|
|
219
|
-
if (configuredBinary) {
|
|
220
|
-
const resolved = path.resolve(configuredBinary);
|
|
221
|
-
if (!fs.existsSync(resolved)) {
|
|
222
|
-
fail(`ARCHITEC_NPM_BINARY_PATH does not exist: ${resolved}`);
|
|
223
|
-
}
|
|
224
|
-
return resolved;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const triplet = platformTriplet();
|
|
228
|
-
const assetName = assetNameFor(triplet);
|
|
229
|
-
const targetDir = path.join(cacheRoot(), VERSION, triplet);
|
|
230
|
-
const targetPath = path.join(targetDir, process.platform === "win32" ? "archi.exe" : "archi");
|
|
231
|
-
const markerPath = path.join(targetDir, "download.json");
|
|
232
|
-
const expected = await expectedChecksum(assetName);
|
|
233
|
-
|
|
234
|
-
if (fs.existsSync(targetPath) && markerMatches(markerPath, assetName, expected)) {
|
|
235
|
-
return targetPath;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
await fs.promises.mkdir(targetDir, { recursive: true });
|
|
239
|
-
if (fs.existsSync(targetPath)) {
|
|
240
|
-
const actual = await sha256File(targetPath);
|
|
241
|
-
if (actual === expected) {
|
|
242
|
-
await fs.promises.chmod(targetPath, 0o755);
|
|
243
|
-
await fs.promises.writeFile(
|
|
244
|
-
markerPath,
|
|
245
|
-
`${JSON.stringify({ version: VERSION, assetName, sha256: expected }, null, 2)}\n`,
|
|
246
|
-
);
|
|
247
|
-
return targetPath;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const tempPath = path.join(targetDir, `${assetName}.${process.pid}.download`);
|
|
252
|
-
await downloadUrl(joinUrl(releaseBaseUrl(), assetName), tempPath);
|
|
253
|
-
const actual = await sha256File(tempPath);
|
|
254
|
-
if (actual !== expected) {
|
|
255
|
-
await fs.promises.rm(tempPath, { force: true });
|
|
256
|
-
fail(`checksum mismatch for ${assetName}: expected ${expected}, got ${actual}`);
|
|
257
|
-
}
|
|
258
|
-
await fs.promises.chmod(tempPath, 0o755);
|
|
259
|
-
await fs.promises.rename(tempPath, targetPath);
|
|
260
|
-
await fs.promises.writeFile(
|
|
261
|
-
markerPath,
|
|
262
|
-
`${JSON.stringify({ version: VERSION, assetName, sha256: expected }, null, 2)}\n`,
|
|
263
|
-
);
|
|
264
|
-
return targetPath;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function runBinary(binaryPath, args) {
|
|
268
|
-
return new Promise((resolve, reject) => {
|
|
269
|
-
const child = spawn(binaryPath, args, {
|
|
270
|
-
stdio: "inherit",
|
|
271
|
-
env: process.env,
|
|
272
|
-
});
|
|
273
|
-
child.on("error", reject);
|
|
274
|
-
child.on("exit", (code) => resolve(code === null ? 1 : code));
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
async function main(args = process.argv.slice(2)) {
|
|
279
|
-
const binaryPath = await ensureBinary();
|
|
280
|
-
return runBinary(binaryPath, args);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
module.exports = {
|
|
284
|
-
VERSION,
|
|
285
|
-
assetNameFor,
|
|
286
|
-
checksumFileName,
|
|
287
|
-
ensureBinary,
|
|
288
|
-
main,
|
|
289
|
-
parseChecksums,
|
|
290
|
-
platformTriplet,
|
|
291
|
-
};
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const https = require("node:https");
|
|
4
|
-
const { URL } = require("node:url");
|
|
5
|
-
const {
|
|
6
|
-
VERSION,
|
|
7
|
-
assetNameFor,
|
|
8
|
-
checksumFileName,
|
|
9
|
-
parseChecksums,
|
|
10
|
-
} = require("../lib/archi-dispatcher");
|
|
11
|
-
|
|
12
|
-
const OWNER = "SeemSeam";
|
|
13
|
-
const REPO = "architec";
|
|
14
|
-
const DEFAULT_REQUIRED_TRIPLETS = [
|
|
15
|
-
"linux-x64",
|
|
16
|
-
"darwin-x64",
|
|
17
|
-
"darwin-arm64",
|
|
18
|
-
"win32-x64",
|
|
19
|
-
];
|
|
20
|
-
|
|
21
|
-
function requiredTriplets() {
|
|
22
|
-
const raw = process.env.ARCHITEC_NPM_REQUIRED_TRIPLETS || "";
|
|
23
|
-
if (!raw.trim()) {
|
|
24
|
-
return DEFAULT_REQUIRED_TRIPLETS;
|
|
25
|
-
}
|
|
26
|
-
return raw.split(",").map((value) => value.trim()).filter(Boolean);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function requestJson(url) {
|
|
30
|
-
return request(url).then((body) => JSON.parse(body.toString("utf8")));
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function request(url) {
|
|
34
|
-
return new Promise((resolve, reject) => {
|
|
35
|
-
const headers = {
|
|
36
|
-
Accept: "application/vnd.github+json",
|
|
37
|
-
"User-Agent": `@seemseam/architec/${VERSION}`,
|
|
38
|
-
};
|
|
39
|
-
if (process.env.GITHUB_TOKEN) {
|
|
40
|
-
headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
|
|
41
|
-
}
|
|
42
|
-
https
|
|
43
|
-
.get(new URL(url), { headers }, (res) => {
|
|
44
|
-
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
45
|
-
res.resume();
|
|
46
|
-
resolve(request(new URL(res.headers.location, url).toString()));
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
if (res.statusCode !== 200) {
|
|
50
|
-
res.resume();
|
|
51
|
-
reject(new Error(`HTTP ${res.statusCode} while fetching ${url}`));
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
const chunks = [];
|
|
55
|
-
res.on("data", (chunk) => chunks.push(chunk));
|
|
56
|
-
res.on("end", () => resolve(Buffer.concat(chunks)));
|
|
57
|
-
})
|
|
58
|
-
.on("error", reject);
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async function main() {
|
|
63
|
-
const release = await requestJson(
|
|
64
|
-
`https://api.github.com/repos/${OWNER}/${REPO}/releases/tags/v${VERSION}`,
|
|
65
|
-
);
|
|
66
|
-
if (release.draft) {
|
|
67
|
-
throw new Error(`GitHub Release v${VERSION} is still a draft`);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const assetByName = new Map((release.assets || []).map((asset) => [asset.name, asset]));
|
|
71
|
-
const checksumsName = checksumFileName();
|
|
72
|
-
const checksumsAsset = assetByName.get(checksumsName);
|
|
73
|
-
if (!checksumsAsset) {
|
|
74
|
-
throw new Error(`missing GitHub Release checksum asset: ${checksumsName}`);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const checksums = parseChecksums(
|
|
78
|
-
(await request(checksumsAsset.browser_download_url)).toString("utf8"),
|
|
79
|
-
);
|
|
80
|
-
const missing = [];
|
|
81
|
-
for (const triplet of requiredTriplets()) {
|
|
82
|
-
const assetName = assetNameFor(triplet);
|
|
83
|
-
if (!assetByName.has(assetName)) {
|
|
84
|
-
missing.push(assetName);
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
if (!checksums.has(assetName)) {
|
|
88
|
-
missing.push(`${assetName} checksum`);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (missing.length) {
|
|
93
|
-
throw new Error(`missing required release assets: ${missing.join(", ")}`);
|
|
94
|
-
}
|
|
95
|
-
console.log(`GitHub Release v${VERSION} has required npm binary assets.`);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
main().catch((error) => {
|
|
99
|
-
console.error(error.message);
|
|
100
|
-
process.exit(1);
|
|
101
|
-
});
|