@seemseam/architec 0.2.11 → 0.2.13

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 CHANGED
@@ -1,293 +1,12 @@
1
- # Architec
1
+ # @seemseam/architec
2
2
 
3
- **Incremental architecture review for AI-assisted codebases.**
3
+ Compatibility shim for the renamed `@seemseam/archi` package.
4
4
 
5
- [![Python](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org/)
6
- [![CLI](https://img.shields.io/badge/CLI-archi-222222)](#quick-start)
7
- [![Login](https://img.shields.io/badge/login-not_required-green)](#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
- archi --check .
8
+ npm install -g @seemseam/archi
166
9
  ```
167
10
 
168
- If the check reports missing LLM configuration, update
169
- `~/.llmgateway/config.yaml`.
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("../lib/archi-dispatcher");
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 dispatcher: ${error.message}`);
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.11",
4
- "description": "Binary dispatcher for the Architec architecture-review CLI.",
5
- "keywords": [
6
- "architecture",
7
- "architecture-review",
8
- "binary",
9
- "cli",
10
- "code-analysis",
11
- "llm"
12
- ],
3
+ "version": "0.2.13",
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
- "lib/",
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.13"
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 && node --check lib/archi-dispatcher.js && node scripts/smoke-dispatcher.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
- [![Python](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org/)
6
- [![CLI](https://img.shields.io/badge/CLI-archi-222222)](README.md#quick-start)
7
- [![Login](https://img.shields.io/badge/login-not_required-green)](#无需登录)
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)
@@ -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.
@@ -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
- });