13 Commits
Author SHA1 Message Date
fourbroad a4e72fbf89 ci: sync from octopus@v0.17.0 2026-09-11 13:53:40 +08:00
fourbroad 1d0fbea069 ci: sync from octopus@v0.17.0 2026-09-11 13:51:39 +08:00
fourbroad 54b2c16a76 docs: link workflow.zh.md from README 2026-09-07 15:03:44 +08:00
fourbroad 8f316b7a84 docs: add workflow.zh.md (工单驱动的工作流体系) 2026-09-07 15:03:43 +08:00
fourbroad 3aac7e0e13 docs: add manual install fallback for networks where the one-liner fails 2026-07-30 11:14:56 +08:00
fourbroad 5d7f1442c3 fix(install.ps1): force TLS 1.2 via ServicePointManager (eightarms.net requires TLS>=1.2) 2026-07-30 10:18:54 +08:00
fourbroad 7cabf1c821 fix(install): force TLS 1.2 in Windows PowerShell command (server rejects TLS<1.2) 2026-07-30 10:18:54 +08:00
fourbroad 8cf0112717 fix(install.ps1): point base/download URLs to new Gitea domain (eightarms.net), fix dev->main branch 2026-07-30 10:06:39 +08:00
fourbroad 8c36343570 fix(install): point download/version URLs to new Gitea domain (eightarms.net) 2026-07-30 10:06:39 +08:00
fourbroad bb4bd2d412 docs: migrate README download links to new Gitea domain (eightarms.net) 2026-07-30 10:02:15 +08:00
fourbroad e3098f2225 ci: sync from octopus@0.15.0 2026-07-05 13:45:03 +08:00
fourbroadandGitea Server e79718a5fb Merge pull request 'fix(install): do not kill PowerShell host under 'irm | iex'' (#1) from workflow/bugfix/install-exit-kills-iex-host into main
Reviewed-on: http://octopus.51zxtx.com:3000/fourbroad/octopus-release/pulls/1
2026-07-02 09:54:18 +08:00
octopus-agent fbac1003f0 fix(install): do not kill PowerShell host under 'irm | iex'
The Windows installer used 'exit' inside Install-Octopus. When run via
'irm <url> | iex' (the documented one-liner), there is no script scope, so
'exit' terminates the entire PowerShell host process and closes the terminal
window instead of just ending the install. Most users hit this via the
'already installed' branch (exit 0).

Replace 'exit' with:
  - 'return' for the success (already-installed) path
  - 'throw' for error paths (still non-zero exit when run as .\install.ps1
    in CI; prints the error and returns to the prompt under iex)

Add tests/test.ps1 as a regression guard (source-verification + a behavioral
test proving iex no longer terminates the host).
2026-07-02 09:10:03 +08:00
5 changed files with 300 additions and 19 deletions
+48 -4
View File
@@ -7,22 +7,66 @@ The AI coding agent built for the terminal.
**Unix (Linux/macOS):**
```bash
curl -fsSL http://octopus.51zxtx.com:3000/fourbroad/octopus-release/raw/branch/main/install | bash
curl -fsSL https://eightarms.net/Octopus/octopus-release/raw/branch/main/install | bash
```
**Windows (PowerShell):**
```powershell
irm http://octopus.51zxtx.com:3000/fourbroad/octopus-release/raw/branch/main/install.ps1 | iex
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
irm https://eightarms.net/Octopus/octopus-release/raw/branch/main/install.ps1 | iex
```
### Manual install
If the one-liner fails with a connection error, the direct connection to `eightarms.net` is likely being filtered on your network. Download the archive for your platform from the [releases page](https://eightarms.net/Octopus/octopus-release/releases/latest) and install it manually instead.
**Unix (Linux/macOS):**
```bash
# after downloading octopus-<platform>.tar.gz from the releases page
tar -xzf octopus-linux-x64.tar.gz
sudo mv octopus /usr/local/bin/
octopus --version
```
**Windows (PowerShell):**
After downloading `octopus-windows-x64.zip` from the releases page:
```powershell
$zip = "$env:USERPROFILE\Downloads\octopus-windows-x64.zip"
$dest = "$env:USERPROFILE\.octopus\bin"
$tmp = Join-Path $env:TEMP "octopus_manual"
New-Item -ItemType Directory -Force -Path $dest,$tmp | Out-Null
Expand-Archive -Path $zip -DestinationPath $tmp -Force
Copy-Item "$tmp\octopus.exe" "$dest\octopus.exe" -Force
Remove-Item -Recurse -Force $tmp
$p = [Environment]::GetEnvironmentVariable('Path','User')
if ($p -notlike "*$dest*") { [Environment]::SetEnvironmentVariable('Path', ($p.TrimEnd(';') + ";$dest"), 'User') }
& "$dest\octopus.exe" --version
```
Open a **new** terminal and run `octopus`.
## Desktop (beta)
Prefer a GUI? The Octopus desktop app ships a Windows beta build alongside the CLI:
- **Windows:** download [`octopus-desktop-win-x64.exe`](https://eightarms.net/Octopus/octopus-release/releases/download/desktop-beta/octopus-desktop-win-x64.exe) from the [`desktop-beta` channel](https://eightarms.net/Octopus/octopus-release/releases/tag/desktop-beta) and run the installer. Installed builds auto-update themselves from the same channel.
- This is a beta channel — expect rough edges. macOS builds are on the way but not published yet.
## Documentation
Visit the [documentation](http://octopus.51zxtx.com:3000/fourbroad/octopus/wiki) for usage guides and API reference.
Visit the [documentation](https://eightarms.net/Octopus/octopus/wiki) for usage guides and API reference.
## Workflow
Curious how Octopus organizes agent work? Read about the **ticket-driven workflow system** (in Chinese): [docs/workflow.zh.md](docs/workflow.zh.md)
## Development
The source code and issue tracker are available at the [private development repository](http://octopus.51zxtx.com:3000/fourbroad/octopus).
The source code and issue tracker are available at the [private development repository](https://eightarms.net/Octopus/octopus).
## License
+162
View File
@@ -0,0 +1,162 @@
# Octopus 工单驱动的工作流体系
> Octopus 不只是一个会写代码的终端 AI 代理——它是一套**以工单为契约、以门禁为骨架**的工程化工作流体系。每一个功能、每一个修复、每一次重构,都作为一张类型化工单进入管线,沿着确定的路由前进,穿过强制的评审与验证门禁,最终带着完整的可审计记录落进主干。
## 为什么需要工单驱动
AI 代理写代码的能力已经足够强,真正的工程难题在于**治理**。在 Octopus 中,工单(issue)不是旁路的需求登记簿,而是流程的驱动轴,它同时解决四件事:
| 难题 | 工单驱动给出的答案 |
| --- | --- |
| **边界** | 一张工单天然划定"做什么 / 不做什么",代理的每次行动都有明确授权范围 |
| **验收** | 工单携带验收标准(acceptance criteria),完成与否可被第三方验证,而不是"看起来对了" |
| **审计** | 从分解、评审、实现到验证,每一步都产出持久化工件,事后可以回答"这个变更为什么发生、经过谁之手" |
| **并行** | 多个人类与多个代理会话基于工单分工协作,互不踩踏(配合 git worktree 会话隔离) |
支撑这一切的铁律是**声明纪律(claim discipline**:任何状态断言——"测试通过"、"缺陷已修复"、"工作完成"——都必须有**当轮产出的新鲜证据**(命令、输出、退出码)支撑。置信不是证据,上一次运行的结果不是证据,"改动看起来对"更不是证据。
## 类型驱动路由:标签即流程
每张工单的 `Kind/*` 标签决定它的整条流程路线——进入哪个技能、跳过哪些阶段、哪些门禁必须保留。这套路由由一张**路由表**(`workflow-routing.yaml`)声明式定义,主会话在任何动作之前先经 **Step 0 路由解析**`route_resolver`)查表,而不是临场自由发挥。
当前路由表速览:
| 工单类型 | 入口技能 | 保留的强制门禁 | 语义要点 |
| --- | --- | --- | --- |
| `Kind/Epic` | `analyze-dag` | review-dag, verify | 需求聚合 → 分解为任务 DAG(见下节) |
| `Kind/Feature` | `analyze-dag` | review-dag, review-code, verify | 新功能同样走 DAG 管线;退化情形(工单自带实现)保留 review-code |
| `Kind/Task` | 继承父级 | review-code, verify | DAG 父级下的子工单,走任务路由 implement → review-code → verify |
| `Kind/Bug` | `implement`bugfix 模式) | review-code, verify | 缺陷报告本身就是规格说明——无需需求/设计阶段,但修复必须被证明 |
| `Kind/Security` | `implement`bugfix 模式) | review-code, verify | 紧急缺陷处理;若改动认证/权限契约,属设计级触发 → 升格 `Kind/Feature` |
| `Kind/Refactor` | `implement`refactor 模式) | review-code, verify | 行为保持不变的重构;大型跨文件重构在迭代内分批 |
| `Kind/Testing` | `implement` | review-code, verify | 仅测试变更;verify 强制(测试必须跑绿) |
| `Kind/Process` | `review-artifact`(过程审计) | audit-process | 改的是 `.octopus/` 工作流系统本身——先审计再落地 |
| `Kind/Documentation` | 直接编辑 | (可选轻量评审) | 文档不进完整管线,除非它编码了一份契约 |
| `Kind/MVP` | 交互模式(无管线) | 无 | 早期概念:工单体即文档,维护决策日志与债务登记 |
| 无标签 | 自然语言匹配 | 按匹配结果 | 未标注的工单行为与路由启用前完全一致 |
三条结构性原则:
1. **路由是纯增量的(additive**:路由只能跳过某类工单*不需要*的阶段,永远不能削弱强制门禁。路由表的 `skip` 与技能自身的强制门取**交集**——两边都同意可跳,才算可跳。
2. **标签错了不悄悄改**:如果 `Kind/Bug` 挂在了一个功能形态的工单上,代理不会擅自改标签,而是按标签走路由、同时显式上报不匹配——标签的正确性归创建者所有。
3. **大 Bug 升格规则**:根因是设计决策、或改动共享契约/公共 API/数据迁移的缺陷,已经超出了 Bug 类型的容量——强制升格 `Kind/Feature` 走小型 DAG(预期 1–3 节点),绝不把设计级变更硬塞进 bugfix 通道。
## DAG 工单管线:分解即计划
`Kind/Epic``Kind/Feature` 进入 **DAG 工单管线**——工作流体系最核心的特征。传统做法里"路线图 → 需求 → 设计 → 迭代计划"是四份会互相漂移的文档;Octopus 把它们合并成**一份任务 DAG 工件**:
```
analyze-dag 分解 ──▶ 任务 DAG 工件 ──▶ review-dag 单门 ──PASS──▶ 建 Kind/Task 子工单
(节点=验收标准 (TOPO/REQMAP/ │
边=跨节点契约 RELEASE 三维评审) ▼
拓扑=执行计划) 每张子工单:
implement → review-code → verify
```
**分解(analyze-dag**。Analyst 把 Epic 分解为一张任务 DAG:每个 `task` 节点就是一条可验收的工作项,每条边是节点之间的**契约**(跨会话接口承诺),拓扑顺序就是执行计划。分解产出即冻结副本,后续所有会话以它为共同基线。
**单门评审(review-dag**。三个维度并行评审:**TOPO**(拓扑合理吗)、**REQMAP**(验收标准覆盖需求吗)、**RELEASE**(切分可发布吗)。这一道门替代了旧体系的"设计空间评审 + 迭代计划评审"两道门,且**结构性永不裁剪**never_trim)。
**深度即档位(size_derivation**。评审深度不是拍脑袋定的,由 DAG 的实测属性派生:
| 维度 | D1 | D2 | D3 | D4 |
| --- | --- | --- | --- | --- |
| 节点数 | ≤3 | 48 | 915 | >15 |
| 跨会话边数 | 0 | — | ≤3 | ≥4 |
| 契约变更面 | 无 | — | 仅新增 | 破坏性 |
取三维最坏值定 D1–D4,D1 由 1 名评审员 2 轮收敛,D4 是 5 名评审员(TOPO:2 REQMAP:2 RELEASE:1 分维)最多 4 轮。**分解即定档**——不再需要人工挂规模标签。
**运行期增长信号**。实现期现实与分解漂移时,管线不装死:节点分裂、出现评审范围外的新跨会话边、已冻结契约被破坏——任何一条触发都强制**重新派生深度 + 重跑 review-dag**。工单类型也不是一成不变的:现实超出标签容量时,"按书面执行、把疑点摆上桌面"、可见地重分类。
**里程碑汇聚(verify_milestone**。跨会话扇入 ≥2 的汇聚点自动焊入里程碑;全部入边任务完成且绿灯后触发集成验证(DoD 矩阵由 DAG 切片自动生成)。里程碑本身不建工单——它的完成定义由 verify 承担。
## 强制门禁:review-code 与 verify
管线里的每段生产工作后面都紧跟一道评审门禁,**没有例外路径**:
- **review-code**——代码评审门。按风险档位并行派出最多 10 名独立评审员(Explorer 子代理),每人一个维度对照评审清单扫描,Worker 编排者汇总发现、开发者修订,迭代至收敛。DAG 任务模式下评审对照冻结的 DAG 节点规格与跨会话契约做范围比对——节点实现蔓延出分解边界本身就是发现。
- **verify**——验证门。运行迭代的 DoD(完成定义)矩阵、集成测试、NFR 校验与回归检查。**迭代在所有 DoD 项通过之前不算完成**——无论代码看起来多好。
- **评审轮次预算**。默认 2 轮、高风险 3 轮封顶——超预算未收敛即 STOP,剩余发现转入技术债务登记册收口,不允许无限马拉松式评审。
## 人机决策门:自动批准的边界
代理需要不停地做流程决策("评审通过了,进入下一阶段吗?")。Octopus 把决策门分成性质完全不同的两类:
**执行类门禁可以白名单放行**`auto_approve.stages` 声明哪些阶段的问题可自动通过(如 `review-code``verify``audit-process`)。即便自动放行,还有一条**活人降级链**:有真人在线看着会话(L1)→ 问题落到 Gitea 工单评论等回复(L2)→ 才轮到推荐项自动默认(L3)。
**业务决策永远必须人答**。以下类别的问题**无条件强制人工确认**(`destructive` 铁律,任何白名单都不覆盖):
1. 合并 PR 到主干 / 打 tag / 发版 / 上生产
2. 生产数据库写入(含迁移、回填)
3. 计费与套餐变更
4. 组织 / 租户结构变更
5. 删除持久资源
6. 超出会话生命周期的持久配置写入
一条阶段白名单授予的是"执行门可以不间断前进",**永远不是**"业务决策可以自问自答"。
## 工单生命周期治理
- **认领与 WIP 上限**:开放的无主工作票超过上限(默认 40 张)时,新工作票必须携带排期证据,否则降为登记册行——用机制而非纪律防止工单堆积。
- **冲刺降档(sprint-mode)**:冲刺期间独立工单创建冻结、登记照常(严重度零丢失),开关回落后登记行按正常链路升格。失败证据类工单不受降档影响——失败必须立即可见。
- **MVP 早期概念模式**:一张工单、一个线程、零管线开销;工单体即文档(每决策一行:选了什么/为什么/何时失效)。出现第二个消费者、外部契约或决策冲突即"毕业"——升格标签,反向进入 DAG 管线的回填模式。
- **生产者预检(preflight)**:复盘阶段统计每条路线首轮评审 FAIL/WARN 的高频根因,把 Top 根因作为检查清单**注入**该路线的实现派发(如"动手前每个验收标准必须映射到可证伪的测试断言")——在人犯错之前提醒人,而不是在评审里再抓一遍。清单项的进入与退出都有数据阈值,提示词永不自动变异。
## 两层工件体系:过程即档案
每个工作流运行(run)的所有产物落入**两层工件体系**,索引守卫保证可恢复、可审计:
| 层 | 位置 | 受众 | 内容 |
| --- | --- | --- | --- |
| Tier 1 | `.octopus/runs/{slug}/` | 机器 | 运行索引、DAG 冻结副本、每轮评审发现 JSON、DoD 矩阵 |
| Tier 2 | Gitea wiki `{slug}/` 空间 | 人 | 评审综合报告、计划页、叙述性文档 |
配套的**压缩纪律**保证长管线跑得完:上下文压缩只发生在干净的阶段边界(工件已持久化之后),且由容量触发器驱动;压缩后代理不依赖对话记忆,而是**重读持久化工件**恢复现场。任何压缩摘要开头都有身份卡,先核对身份再继续干活——防止拿着上一个会话的残影做这个会话的决定。
## 多智能体协作模型
Octopus 的主会话是**协调者而非工人**:
- **Explorer 子代理**——只读探索:找文件、搜模式、回答"X 怎么工作",结果即时返回;
- **Worker 子代理**——多步执行:写代码、跑测试、修失败,**先持久化产出再结束**;
- **并行派发**——独立子任务一次性并行发出,避免主会话上下文被文件内容与工具输出淹没。
会话级隔离由 **git worktree 纪律**保障:并发分支工作必须各占独立 worktree(主 checkout 单占用),分支劫持事故有审计日志可归因;子代理共享同一工作流 worktree——隔离发生在工作流之间,而不是协同的子代理之间。
## 合并队列:落地的最后一道秩序
代码评审收敛后,PR 打上 `ready-to-merge` 标签进入**服务端串行合并队列**:
- 队头 PR 由协调机器人校验"标签 + 双绿(CI 检查 + 合并门)+ 风险双检(有 `Risk/Low` 且无 `Risk/High`"后 approve 并合并;
- 非队头 PR 自动做 keep-mergeable 保活(绝不手写合并 main 的保活提交);
- 机械类低风险 PR(依赖升级、锁文件、bot 作者)走 **fast-lane** 快速通道自动补标签;
- 真冲突的 PR 经 merge-tree 预探后出队留痕,不动分支。
## 一张工单的完整旅程
以一个新功能为例,把所有机制串起来:
1. **创建**:功能工单打上 `Kind/Feature` 标签,写清动机与验收期望;
2. **路由**:主会话 Step 0 查路由表 → DAG 管线,先过过程评估门再动手;
3. **分解**`analyze-dag` 产出任务 DAG——节点是验收标准、边是契约、拓扑是计划;实测属性派生出 D1–D4 档位;
4. **单门**`review-dag` 三维并行评审(拓扑/需求映射/可发布性),迭代至收敛 PASS;
5. **拆票**:聚合代理为每个任务节点建一张 `Kind/Task` 子工单,父工单聚合状态总览;
6. **实现**:每张子工单独立走 `implement`(含生产者预检)——在独立 worktree 分支上;
7. **代码评审**`review-code` 多评审员对照节点规格与冻结契约并行扫描,轮次预算内收敛;
8. **验证**`verify` 跑 DoD 矩阵与集成测试;跨会话汇聚点触发里程碑验证;
9. **落地**PR 打 `ready-to-merge` 进合并队列,双绿 + 风险双检通过后自动合并进主干;
10. **归档**:全部工件已在两层体系留档,工单关闭——过程即档案。
## 小结
工单驱动不是"给 AI 加了个 TODO 列表",而是一套完整的工程治理契约:
- **类型即路由**——标签决定流程,流程不再临场发挥;
- **分解即计划**——一份 DAG 工件替代四份会漂移的文档;
- **门禁即质量**——评审与验证不可绕过、不可裁剪、有轮次预算;
- **证据即真相**——没有新鲜证据的状态断言一律无效;
- **工件即档案**——过程可恢复、决策可追溯;
- **人在环上**——执行门可自动,业务决策永远人工。
这套体系本身也是工单驱动的:对 `.octopus/` 工作流系统的任何改进都走 `Kind/Process` 过程审计路由——系统吃自己的狗粮。
+9 -9
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# Public release repository:
# http://octopus.51zxtx.com:3000/fourbroad/octopus-release
# https://eightarms.net/Octopus/octopus-release
set -euo pipefail
APP=octopus
@@ -22,8 +22,8 @@ Options:
--no-modify-path Don't modify shell config files (.zshrc, .bashrc, etc.)
Examples:
curl -fsSL https://octopus.51zxtx.com/install | bash
curl -fsSL https://octopus.51zxtx.com/install | bash -s -- --version 1.0.180
curl -fsSL https://eightarms.net/install | bash
curl -fsSL https://eightarms.net/install | bash -s -- --version 1.0.180
./install --binary /path/to/octopus
EOF
}
@@ -183,24 +183,24 @@ else
fi
if [ -z "$requested_version" ]; then
specific_version=$(curl -s "${GITEA_API_URL:-http://octopus.51zxtx.com:3000/api/v1}/repos/fourbroad/octopus-release/releases?limit=1" | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p')
specific_version=$(curl -s "${GITEA_API_URL:-https://eightarms.net/api/v1}/repos/Octopus/octopus-release/releases?limit=1" | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p')
if [[ $? -ne 0 || -z "$specific_version" ]]; then
echo -e "${RED}Failed to fetch version information${NC}"
exit 1
fi
url="${OCTOPUS_DOWNLOAD_BASE:-http://octopus.51zxtx.com:3000}/fourbroad/octopus-release/releases/download/v${specific_version}/$filename"
url="${OCTOPUS_DOWNLOAD_BASE:-https://eightarms.net}/Octopus/octopus-release/releases/download/v${specific_version}/$filename"
else
# Strip leading 'v' if present
requested_version="${requested_version#v}"
url="${OCTOPUS_DOWNLOAD_BASE:-http://octopus.51zxtx.com:3000}/fourbroad/octopus-release/releases/download/v${requested_version}/$filename"
url="${OCTOPUS_DOWNLOAD_BASE:-https://eightarms.net}/Octopus/octopus-release/releases/download/v${requested_version}/$filename"
specific_version=$requested_version
# Verify the release exists before downloading
http_status=$(curl -sI -o /dev/null -w "%{http_code}" "${GITEA_URL:-http://octopus.51zxtx.com:3000}/fourbroad/octopus-release/releases/tag/v${requested_version}")
http_status=$(curl -sI -o /dev/null -w "%{http_code}" "${GITEA_URL:-https://eightarms.net}/Octopus/octopus-release/releases/tag/v${requested_version}")
if [ "$http_status" = "404" ]; then
echo -e "${RED}Error: Release v${requested_version} not found${NC}"
echo -e "${MUTED}Available releases: ${GITEA_URL:-http://octopus.51zxtx.com:3000}/fourbroad/octopus-release/releases${NC}"
echo -e "${MUTED}Available releases: ${GITEA_URL:-https://eightarms.net}/Octopus/octopus-release/releases${NC}"
exit 1
fi
fi
@@ -456,6 +456,6 @@ echo -e ""
echo -e "cd <project> ${MUTED}# Open directory${NC}"
echo -e "octopus ${MUTED}# Run command${NC}"
echo -e ""
echo -e "${MUTED}For more information visit ${NC}https://octopus.51zxtx.com/docs"
echo -e "${MUTED}For more information visit ${NC}https://eightarms.net/docs"
echo -e ""
echo -e ""
+6 -6
View File
@@ -1,5 +1,5 @@
#Requires -Version 5.1
# Public release repository: http://octopus.51zxtx.com:3000/fourbroad/octopus-release
# Public release repository: https://eightarms.net/Octopus/octopus-release
<#
.SYNOPSIS
Octopus installer for Windows.
@@ -14,10 +14,10 @@
Do not add the install directory to the user PATH.
.EXAMPLE
irm http://octopus.51zxtx.com:3000/fourbroad/octopus-release/raw/branch/dev/install.ps1 | iex
irm https://eightarms.net/Octopus/octopus-release/raw/branch/dev/install.ps1 | iex
.EXAMPLE
irm http://octopus.51zxtx.com:3000/fourbroad/octopus-release/raw/branch/dev/install.ps1 | iex; Install-Octopus -Version 0.1.3
irm https://eightarms.net/Octopus/octopus-release/raw/branch/dev/install.ps1 | iex; Install-Octopus -Version 0.1.3
#>
[CmdletBinding()]
param(
@@ -29,8 +29,8 @@ Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$APP = "octopus"
$REPO = "fourbroad/octopus-release"
$baseUrl = if ($env:OCTOPUS_DOWNLOAD_BASE) { $env:OCTOPUS_DOWNLOAD_BASE } else { "http://octopus.51zxtx.com:3000" }
$REPO = "Octopus/octopus-release"
$baseUrl = if ($env:OCTOPUS_DOWNLOAD_BASE) { $env:OCTOPUS_DOWNLOAD_BASE } else { "https://eightarms.net" }
$INSTALL_DIR = Join-Path $env:USERPROFILE ".octopus\bin"
function Write-Color([string]$Text, [string]$Color = "White") {
@@ -194,7 +194,7 @@ function Install-Octopus {
Write-Host " octopus " -NoNewline; Write-Host "# Run command" -ForegroundColor DarkGray
Write-Host ""
Write-Host "For more information visit " -NoNewline -ForegroundColor DarkGray
Write-Host "https://octopus.51zxtx.com/docs"
Write-Host "https://eightarms.net/docs"
Write-Host ""
}
+75
View File
@@ -0,0 +1,75 @@
#Requires -Version 5.1
<#
.SYNOPSIS
Regression tests for install.ps1.
.BUG
`irm <url> | iex` closed the terminal window. Cause: the script used
`exit`, which under Invoke-Expression terminates the entire PowerShell
host process (there is no script scope to exit). Fix: use `return`
(success) / `throw` (errors) so the host survives.
.NOTES
Host-process termination cannot be observed from inside the dying process,
so these tests combine a source-verification guard with a behavioral test
run in a child process (`exit` cannot be caught by try/catch and kills the
child; `throw` is catchable).
#>
[CmdletBinding()]
param()
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$repoRoot = Split-Path -Parent $PSScriptRoot
$installScript = Join-Path $repoRoot "install.ps1"
$failures = [System.Collections.Generic.List[string]]::new()
function Assert-True([bool]$Cond, [string]$Message) {
if (-not $Cond) { $failures.Add($Message) | Out-Null }
}
# --- Test 1 (source verification): no host-terminating `exit` ---
$content = Get-Content -Path $installScript -Raw
$exitCount = ([regex]::Matches($content, '(?m)^\s*exit\b')).Count
Assert-True ($exitCount -eq 0) "install.ps1 must not use bare 'exit' (found $exitCount); 'irm | iex' would kill the host. Use return/throw."
# --- Test 2: parses cleanly ---
$parseOk = $true
try { [void][scriptblock]::Create($content) } catch { $parseOk = $false }
Assert-True $parseOk "install.ps1 must parse without syntax errors."
# --- Test 3 (behavioral): iex must NOT kill the host ---
# Force the version lookup to fail fast against an unreachable address so the
# script reaches its early-exit/throw path offline. `exit` terminates the child
# (RESULT never emitted); `throw` is caught and RESULT is emitted.
$inner = @'
$ErrorActionPreference = "Stop"
$env:OCTOPUS_DOWNLOAD_BASE = "http://127.0.0.1:1"
$r = "nothing"
try {
Invoke-Expression (Get-Content -Raw "__INSTALL__")
$r = "completed"
} catch {
$r = "caught-throw"
}
Write-Output ("RESULT=$r")
'@.Replace("__INSTALL__", $installScript)
$tmp = Join-Path $env:TEMP ("octopus_iex_test_" + $PID + ".ps1")
Set-Content -Path $tmp -Value $inner -Encoding UTF8
try {
$childOut = (& powershell -NoProfile -File $tmp 2>&1 | Out-String)
} finally {
Remove-Item $tmp -ErrorAction SilentlyContinue
}
Assert-True ($childOut -match 'RESULT=') "iex must not terminate the host (try/catch must observe a RESULT). Child output:`n$childOut"
# --- Report ---
if ($failures.Count -gt 0) {
Write-Host ("FAIL: " + $failures.Count + " test(s)") -ForegroundColor Red
foreach ($f in $failures) { Write-Host (" - " + $f) -ForegroundColor Red }
exit 1
}
Write-Host "PASS: all regression tests passed." -ForegroundColor Green
exit 0