aimship 0.1.0-preview.18 → 0.1.0-preview.19
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/CONTRIBUTING.md +11 -5
- package/README.md +69 -87
- package/README.zh-CN.md +68 -86
- package/docs/cli.md +9 -3
- package/docs/guide.md +1 -1
- package/docs/guide.zh-CN.md +1 -1
- package/lib/cli.mjs +11 -9
- package/lib/install.mjs +26 -3
- package/lib/protocol.mjs +27 -0
- package/lib/state.mjs +11 -6
- package/package.json +5 -2
- package/skills/aimship/references/recovery.md +3 -1
package/CONTRIBUTING.md
CHANGED
|
@@ -78,19 +78,25 @@ The output directory must not exist. The harness creates isolated code and deliv
|
|
|
78
78
|
|
|
79
79
|
These examples check local document/state behavior. They do not establish automatic Skill discovery, real cross-host continuation, UI interaction or HTTP acceptance. Run output records assertions and limitations. Results from older schemas do not establish current behavior.
|
|
80
80
|
|
|
81
|
-
##
|
|
81
|
+
## Test upcoming changes
|
|
82
82
|
|
|
83
|
-
The
|
|
83
|
+
The README's normal installation/update entry is `npx aimship@latest install`. `latest` means the current recommended version; it can be a verified Preview before the first formal release, and follows formal releases afterward. Ordinary users do not choose a channel.
|
|
84
|
+
|
|
85
|
+
For an explicit test invitation or development check, `npx aimship@preview install` selects the published Preview channel. Users who previously used `@preview` and want to follow formal releases should use `@latest` once; the installer records the actual version, not a persistent npm channel choice. Exact versions belong in recovery or reproducibility instructions, not quick-start choices.
|
|
86
|
+
|
|
87
|
+
## Preview and formal releases
|
|
88
|
+
|
|
89
|
+
The [Publish preview workflow](.github/workflows/publish-preview.yml) is manually dispatched in `clsx/aimship`. It does not publish on push. Review and commit the versioned source, run tests and documentation/package checks, push the default branch and an exact `vX.Y.Z-preview.N` or unsuffixed `vX.Y.Z` tag, then select that same tag as both the workflow ref and its `tag` input. The tagged commit must contain the workflow and matching `package.json` version. `publishConfig.tag` must be `preview` for a Preview or `latest` for a formal release; RC versions are not supported. Preview is for validation. Formal 0.1.0 is a manual decision after real complete deliveries on Mac and Windows, passing existing automated checks, no known blockers in the declared scope and accurate README content; no fixed user count, waiting period or all-model/all-tool coverage is required. Formal status does not guarantee every AI output is correct. For example, with the GitHub CLI:
|
|
84
90
|
|
|
85
91
|
```sh
|
|
86
92
|
gh workflow run publish-preview.yml --repo clsx/aimship --ref vX.Y.Z-preview.N -f tag=vX.Y.Z-preview.N -f operation=publish
|
|
87
93
|
```
|
|
88
94
|
|
|
89
|
-
Validation runs on a GitHub-hosted runner without OIDC permission. It checks the release context, runs the full tests and documentation/package checks, and packs once. Every tarball member is compared with the source. The publishing job receives that exact artifact, rechecks its source and digest, and publishes `aimship` publicly to `https://registry.npmjs.org`
|
|
95
|
+
Validation runs on a GitHub-hosted runner without OIDC permission. It checks the release context, runs the full tests and documentation/package checks, and packs once. Every tarball member is compared with the source. The publishing job receives that exact artifact, rechecks its source and digest, and publishes `aimship` publicly to `https://registry.npmjs.org`: Preview packages use `preview`, formal packages use `latest`. After verifying the downloaded bytes and provenance, it synchronizes the applicable tags. Before any formal publication, a verified Preview also advances `latest`. Starting with the first formal release, only formal releases advance `latest`; later Previews update only `preview`. Formal releases retain the opt-in Preview tag rather than pointing it at the formal package. Forward checks, verification and recovery follow that same channel policy. `latest` is the default installation channel; pointing it at a preview version does not make that version stable. Actions and runner Node/npm versions are pinned in the workflow; these CI versions are not a replacement for the package's stated runtime requirements or a claim of cross-platform validation.
|
|
90
96
|
|
|
91
97
|
The npm package's trusted publisher must identify GitHub Actions, owner `clsx`, repository `aimship`, workflow filename `publish-preview.yml`, no environment, and allow direct `npm publish` and `npm dist-tag`. The tag permission is separate from publishing permission and must be explicitly authorized by the package owner; the workflow never changes the trust configuration. Tag operations require npm 11.21+ or 12.2+ (the workflow pins npm 11.21.0). Staged publishing is not used. No long-lived npm token is stored. Only jobs that publish or synchronize tags have `id-token: write`; validation has no OIDC permission, and all jobs have only `contents: read` for repository access. This is an ongoing publishing permission for that workflow, so changes to it deserve release-security review. A newly created npm trust must complete its first successful publication within npm's required window; see [npm trusted publishing](https://docs.npmjs.com/trusted-publishers/).
|
|
92
98
|
|
|
93
|
-
On any uncertain publish result, inspect the registry and run logs before retrying. An existing version with different integrity stops the workflow; an identical version is verified without republishing. Missing provenance, unrecognized version formats, or a newer
|
|
99
|
+
On any uncertain publish result, inspect the registry and run logs before retrying. An existing version with different integrity stops the workflow; an identical version is verified without republishing. Missing provenance, unrecognized version formats, or a newer version in a tag targeted by this release stops the operation. A formal `latest` does not block publishing a later Preview to `preview`. Channel synchronization requires the separate tag permission. A failed tag update may leave publication complete but channel synchronization pending; do not republish or create a replacement version. Preserve the run URL, commit, artifact digest and registry outcome. Create the GitHub Release separately (`--prerelease` for Preview only, formal otherwise) after successful npm verification, using the same tag and validated package; the publishing workflow has no repository write permission. Do not reuse a released version for changed package contents.
|
|
94
100
|
|
|
95
101
|
To finish channel synchronization for an **already published, explicitly approved** release, run the current `main` workflow with `operation=sync-tags` and the original release tag:
|
|
96
102
|
|
|
@@ -98,4 +104,4 @@ To finish channel synchronization for an **already published, explicitly approve
|
|
|
98
104
|
gh workflow run publish-preview.yml --repo clsx/aimship --ref main -f tag=vX.Y.Z-preview.N -f operation=sync-tags
|
|
99
105
|
```
|
|
100
106
|
|
|
101
|
-
This operation never publishes a package or moves a Git tag. It compares the existing npm tarball with the original tagged source, checks its registry integrity and provenance availability, and updates only tags that do not already match. Each write is preceded by a fresh registry check; an old run cannot intentionally roll
|
|
107
|
+
This operation never publishes a package or moves a Git tag. It compares the existing npm tarball with the original tagged source, checks its registry integrity and provenance availability, and updates only tags that do not already match. Each write is preceded by a fresh registry check; an old run cannot intentionally roll its target channels back or use a Preview to replace a formal `latest`. The shared workflow concurrency group serializes these operations, but npm offers no compare-and-swap for changes made outside this workflow: avoid concurrent manual tag edits. On a denied or uncertain write, inspect the registry and logs before any authorized retry. Do not switch credentials or broaden trusted-publisher permissions automatically.
|
package/README.md
CHANGED
|
@@ -2,155 +2,137 @@
|
|
|
2
2
|
|
|
3
3
|
[中文](README.zh-CN.md) · [Guide](docs/guide.md)
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**Turn a requirement into a delivery you can review, follow and resume in your AI coding tool.**
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Aimship is for developers who own the result, from a small fix to a feature spanning several repositories. Bring a goal and whatever materials you already have. The agent clarifies requirements, prepares designs, plans implementation batches, reviews the changes and records independent verification. You review the key documents and decisions before dependent work proceeds.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
Standard document outlines, identifiers and references connect requirements and acceptance criteria to designs, implementation tasks, reviews and test results. Documents are produced as needed for the delivery goal, giving you a consistent basis for review without requiring an empty template for every stage.
|
|
9
|
+
- **Understand the proposal before code changes:** requirements and acceptance criteria connect to the design, tasks and tests.
|
|
10
|
+
- **See what is actually finished:** `status.md` summarizes delivery stages; the plan tracks batch/task progress, reviews track findings, and tests retain actual results.
|
|
11
|
+
- **Continue without reconstructing the conversation:** saved documents, decisions and state give the next session a starting point. Changes return to the affected design and review.
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
The agent organizes requirements, design, implementation and verification; you review key documents and important decisions. Feedback leads to revisions, and approval enables subsequent work. Final acceptance is based on the agreed goal and actual verification results.
|
|
16
|
-
|
|
17
|
-
Documents and state are saved throughout the work, keeping progress, decisions and verification evidence traceable and reducing the need to repeat background or reconstruct earlier work.
|
|
13
|
+
Aimship manages the delivery process. The agent still needs to investigate, self-check and correct its work; you judge the content and acceptance. Model and AI-tool behavior can vary. **Currently in Preview**; installation or passing tool tests is not proof that every tool/model combination works.
|
|
18
14
|
|
|
19
|
-
[
|
|
15
|
+
[Start](#install) · [Example](#delivery-example) · [Upgrade](#update) · [Troubleshooting](#installation-troubleshooting)
|
|
20
16
|
|
|
21
17
|
<a id="quick-start"></a>
|
|
22
18
|
<a id="install"></a>
|
|
23
|
-
##
|
|
24
|
-
|
|
25
|
-
Requires **Node.js 22+ and npm**.
|
|
19
|
+
## Start a delivery
|
|
26
20
|
|
|
27
|
-
|
|
21
|
+
Requires **Node.js 22+ and npm**. Install for your AI tool:
|
|
28
22
|
|
|
29
23
|
```sh
|
|
30
|
-
npx aimship@
|
|
24
|
+
npx aimship@latest install --host codex
|
|
31
25
|
```
|
|
32
26
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
```sh
|
|
36
|
-
npx aimship@preview install --host codex --host claude-code
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
The npx package prompt confirms downloading and running the package; `install` performs the Aimship installation or upgrade. `@preview` selects the published preview channel; an exact published version, such as `@0.1.0-preview.18`, can be used instead. The preview channel is experimental and does not imply a stable release.
|
|
40
|
-
|
|
41
|
-
### A specific version or local package
|
|
27
|
+
Use `codex`, `cursor` or `claude-code` for `--host`. To connect several tools, repeat the option, such as `--host codex --host claude-code`. No global npm installation, administrator privilege or npm login is required; npm needs a writable cache. `latest` means the currently recommended version. Before the first formal release, it points to a Preview; the command stays the same afterward.
|
|
42
28
|
|
|
43
|
-
|
|
29
|
+
Refresh the tool's Skill discovery or start a new session, activate Aimship, then give it a concrete request. For example:
|
|
44
30
|
|
|
45
|
-
|
|
46
|
-
npx aimship@0.1.0-preview.18 install --host codex
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
If you already have an Aimship `.tgz` package, replace `VERSION` with its actual version and run from the directory containing it:
|
|
31
|
+
> Use Aimship to add CSV export for the selected orders in this dashboard. Keep the existing permission rules; help me clarify the requirements first.
|
|
50
32
|
|
|
51
|
-
|
|
52
|
-
npx --offline file:./aimship-VERSION.tgz install --host codex
|
|
53
|
-
```
|
|
33
|
+
You can start with an idea, a bug report, an existing PRD or a prototype. A complete specification and code checkout are not prerequisites. Aimship saves a separate delivery folder under `~/Aimship/` by default and gives you its location, current progress and next review action. Code stays in its existing location. Add the actual delivery folder to your workspace or grant access when your tool needs it.
|
|
54
34
|
|
|
55
|
-
|
|
35
|
+
<a id="start-a-delivery"></a>
|
|
36
|
+
<a id="getting-started"></a>
|
|
37
|
+
The [first-use guide](docs/guide.md#start) covers tool activation, access and the first review. Provide an existing delivery's directory to [continue it](docs/guide.md#continue).
|
|
56
38
|
|
|
57
|
-
|
|
39
|
+
<a id="delivery-example"></a>
|
|
40
|
+
## What you review along the way
|
|
58
41
|
|
|
59
|
-
|
|
42
|
+
**Illustrative example, not a completed customer delivery:** for the order-export request above, the documents could connect the following items. These excerpts show the organization, not executed test results.
|
|
60
43
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
44
|
+
| Document | Example of useful content |
|
|
45
|
+
| --- | --- |
|
|
46
|
+
| `prd.md` | REQ-001: export only selected orders; AC-001: selecting two permitted orders yields exactly those two rows. |
|
|
47
|
+
| `tech-design.md` / applicable contracts | Explain permission enforcement, export limits and error behavior before implementation. |
|
|
48
|
+
| `plan.md` | TASK-001 implements selection and permission checks; its execution status is maintained as batches finish. |
|
|
49
|
+
| `review.md` | Record the reviewed code baseline, findings, impact, fixes and recheck conclusion. |
|
|
50
|
+
| `testing.md` | TC-001 checks AC-001. Execution remains “not run” until a RUN records actual assertions and evidence. |
|
|
51
|
+
| `status.md` | Show the stable delivery stages, current review stop and next action; batch detail stays in the plan. |
|
|
64
52
|
|
|
65
|
-
|
|
53
|
+
<a id="what-a-delivery-contains"></a>
|
|
54
|
+
<a id="delivery-structure-and-documents"></a>
|
|
55
|
+
<a id="delivery-artifacts"></a>
|
|
56
|
+
<a id="delivery-workflow"></a>
|
|
57
|
+
A normal delivery follows **requirements → design and applicable contracts/SQL → test design → plan → implementation with batch reviews → overall review → independent verification → acceptance**. Documents are produced as needed. An applicable supplied PRD can be adopted; original materials are retained. A prototype is optional. Key approvals apply to the current content, and completion, passing tests and your acceptance remain separate facts.
|
|
66
58
|
|
|
67
|
-
|
|
68
|
-
<a id="getting-started"></a>
|
|
69
|
-
After installation, follow the [Guide](docs/guide.md#start) to prepare a workspace, activate Aimship and begin your first request. For an existing delivery, follow [continuation](docs/guide.md#continue).
|
|
59
|
+
See the [guided example](docs/guide.md#start) and [delivery directory reference](docs/guide.md#delivery-structure). Contributors can also [run synthetic deliveries](CONTRIBUTING.md#executable-examples) with actual local assertions; those checks do not establish AI-host behavior.
|
|
70
60
|
|
|
71
61
|
<a id="update"></a>
|
|
72
62
|
<a id="update-or-stop-using-aimship"></a>
|
|
73
63
|
<a id="upgrades-and-host-management"></a>
|
|
74
64
|
<a id="upgrades-and-ai-development-tools"></a>
|
|
75
|
-
##
|
|
65
|
+
## Upgrade and manage tools
|
|
76
66
|
|
|
77
|
-
|
|
67
|
+
Save current work, then use the same installation command:
|
|
78
68
|
|
|
79
69
|
```sh
|
|
80
|
-
npx aimship@
|
|
70
|
+
npx aimship@latest install
|
|
81
71
|
```
|
|
82
72
|
|
|
83
|
-
|
|
73
|
+
Existing tool registrations, configuration, custom content and delivery files are retained. Repeat neither `--host` nor `--data-root` for an ordinary update. There is no background updater. After upgrading, start a new session or refresh the tool's rules, activate Aimship and resume the same delivery directory.
|
|
84
74
|
|
|
85
|
-
|
|
75
|
+
Compatible delivery protocols can continue without rewriting old documents or invalidating unchanged approvals. Legacy Preview 17/18 states are supported by an exact protocol mapping; older unverified states are not assumed compatible. Software installation itself does not change delivery records. For a breaking or unverified protocol switch, the installer explains the impact and stops before changing the active installation. Strongly prefer finishing ongoing deliveries first; explicit risk acceptance is an [advanced recovery option](#installation-troubleshooting), not permission to write unsupported state. The npx download prompt is separate from that decision.
|
|
86
76
|
|
|
87
77
|
<a id="add-a-host"></a>
|
|
88
78
|
<a id="add-an-ai-development-tool"></a>
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
For example, connect Cursor after installing for Codex:
|
|
79
|
+
Connect another tool, retaining existing registrations:
|
|
92
80
|
|
|
93
81
|
```sh
|
|
94
|
-
npx aimship@
|
|
82
|
+
npx aimship@latest install --host cursor
|
|
95
83
|
```
|
|
96
84
|
|
|
97
|
-
This
|
|
85
|
+
This also installs the currently recommended version. See [tool setup](docs/guide.md#hosts) if the entry is not discovered.
|
|
98
86
|
|
|
99
|
-
|
|
87
|
+
Check the local installation without downloading or upgrading:
|
|
100
88
|
|
|
101
89
|
```sh
|
|
102
90
|
node "$HOME/.aimship/bin/aimship.mjs" --version
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
This command queries the installed local launcher directly. `installedActiveVersion` is the active installed version. The query neither downloads a package nor upgrades the installation.
|
|
106
|
-
|
|
107
|
-
### Check installation health
|
|
108
|
-
|
|
109
|
-
```sh
|
|
110
91
|
node "$HOME/.aimship/bin/aimship.mjs" status
|
|
111
92
|
```
|
|
112
93
|
|
|
113
|
-
|
|
94
|
+
`installedActiveVersion` identifies the active software. Health checks cover managed files and configuration; they cannot confirm the AI tool loaded the Skill. For delivery progress, open its `status.md`. With a custom `--home`, use the launcher beneath that location.
|
|
114
95
|
|
|
115
|
-
|
|
96
|
+
### Optional material location
|
|
116
97
|
|
|
117
|
-
|
|
98
|
+
On the first installation, append `--data-root "./MyDeliveries"` to choose another delivery root. Relative paths resolve from the working directory. Later installs retain this configuration; existing deliveries are not moved. It is a material location, not a code repository.
|
|
118
99
|
|
|
119
100
|
<a id="uninstall"></a>
|
|
120
|
-
##
|
|
101
|
+
## Uninstall
|
|
121
102
|
|
|
122
|
-
Disconnect one tool while retaining the core
|
|
103
|
+
Disconnect one tool while retaining the core, or remove all managed software:
|
|
123
104
|
|
|
124
105
|
```sh
|
|
125
|
-
npx aimship@
|
|
106
|
+
npx aimship@latest uninstall --host cursor
|
|
107
|
+
npx aimship@latest uninstall
|
|
126
108
|
```
|
|
127
109
|
|
|
128
|
-
Shared entries remain when another registered tool needs them. Disconnecting the last tool
|
|
129
|
-
|
|
130
|
-
Remove all managed software and tool entries:
|
|
131
|
-
|
|
132
|
-
```sh
|
|
133
|
-
npx aimship@preview uninstall
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
Full uninstall retains delivery materials, user code, custom files and necessary configuration. Modified/unowned files are preserved and reported. The launcher is removed on complete removal. Restart tool sessions to discard cached rules. Deleting delivery history is a separate explicit action.
|
|
110
|
+
These are alternative operations. Shared entries remain when another registered tool needs them. Disconnecting the last tool retains the core; full uninstall removes the launcher and managed releases. Both retain delivery materials, user code, custom files and necessary configuration. Modified or unowned files are preserved and reported. Refresh tool sessions afterward to discard cached rules.
|
|
137
111
|
|
|
138
112
|
<a id="installation-troubleshooting"></a>
|
|
139
113
|
## Installation and upgrade problems
|
|
140
114
|
|
|
141
115
|
| Symptom | What to do |
|
|
142
116
|
| --- | --- |
|
|
143
|
-
|
|
|
144
|
-
|
|
|
145
|
-
|
|
|
146
|
-
| Unexpected version | Query `installedActiveVersion
|
|
147
|
-
|
|
|
117
|
+
| Entry absent or duplicated | Refresh discovery using [tool setup](docs/guide.md#hosts); installation health does not prove loading. |
|
|
118
|
+
| Managed path modified or occupied | Preserve the contents and explicitly reconcile ownership/customizations before retrying. |
|
|
119
|
+
| Interrupted installation | Inspect `status`, resolve the cause, then rerun the exact pending package against the same home. Do not switch versions during recovery. |
|
|
120
|
+
| Unexpected version | Query `installedActiveVersion`, use `install` to update, then refresh the session's rules. |
|
|
121
|
+
| Upgrade confirmation required | Finish ongoing deliveries first. If you explicitly accept the reported risk, rerun the same target package's `install` with `--accept-upgrade-risk`. Unsupported delivery writes remain blocked. |
|
|
122
|
+
| Download failed or package unavailable | Check the package, exact version and registry; download failure is not installation success. |
|
|
148
123
|
|
|
149
|
-
|
|
124
|
+
<details>
|
|
125
|
+
<summary>Advanced: exact packages and recovery</summary>
|
|
150
126
|
|
|
151
|
-
|
|
152
|
-
|
|
127
|
+
Replace `VERSION` with an actual published or pending version, for example `npx aimship@VERSION install`. If accepting an upgrade risk, append `--accept-upgrade-risk` to that exact command. `npx --yes` only accepts downloading/running the package and cannot substitute for this flag.
|
|
128
|
+
|
|
129
|
+
For an existing local package, run `npx --offline file:./aimship-VERSION.tgz install --host codex` from its directory. Source and isolated installation checks are in [Contributing](CONTRIBUTING.md#isolated-installation-check).
|
|
130
|
+
|
|
131
|
+
</details>
|
|
153
132
|
|
|
154
|
-
|
|
133
|
+
## Feedback and development
|
|
155
134
|
|
|
156
|
-
|
|
135
|
+
The [Guide](docs/guide.md) covers review, changes, continuation and acceptance; the [CLI protocol](docs/cli.md) serves agents and tool developers. Development and opt-in testing information is in [Contributing](CONTRIBUTING.md).
|
|
136
|
+
|
|
137
|
+
<a id="use-cases-and-examples"></a>
|
|
138
|
+
Share concrete problems and suggestions through [GitHub Issues](https://github.com/clsx/aimship/issues), with a minimal reproduction and no private delivery data. Released under the [MIT License](LICENSE).
|
package/README.zh-CN.md
CHANGED
|
@@ -2,155 +2,137 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.md) · [使用指南](docs/guide.zh-CN.md)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**在 AI 开发工具中,把一项需求推进为你能审核、跟进和接续的交付。**
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Aimship 面向对交付结果负责的开发者,适用于小型修复,也适用于跨多个仓库的功能。你提供目标和已有材料,Agent 澄清需求、准备设计、安排实施批次、审查改动并记录独立验证;依赖这些方案的工作推进前,由你审核关键文档与决定。
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
统一的文档大纲、编号和引用,将需求与验收标准关联到设计、实施任务、审核和测试结果。文档按交付目标和实际需要生成,既提供一致的阅读与核对依据,也不要求每个阶段都填一份空模板。
|
|
9
|
+
- **改代码前看懂方案:** 需求与验收标准关联到设计、任务和测试。
|
|
10
|
+
- **看清实际完成情况:** `status.md` 汇总交付阶段,计划维护批次/任务进度,审查记录问题,测试保留真实结果。
|
|
11
|
+
- **换会话有据可接:** 保存的文档、决定和状态给下个会话提供起点;需求变化回到受影响的方案和审核。
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
Agent 整理需求、设计方案并组织实施与验证,你审核关键文档和重要决定。修改意见纳入修订,审核通过后推进后续工作;最终依据约定目标和实际验证结果验收成果。
|
|
16
|
-
|
|
17
|
-
文档与状态在工作过程中持续保存,使进度、决定和验证依据可追溯,减少重复交代背景和重新梳理已有工作的负担。
|
|
13
|
+
Aimship 管理交付过程。Agent 仍须调查、自检和修正,你判断内容与验收结果。不同模型和 AI 工具的表现会有差异。**目前处于 Preview 体验阶段**;安装成功或程序测试通过,不代表所有工具/模型组合都已验证。
|
|
18
14
|
|
|
19
|
-
[
|
|
15
|
+
[开始使用](#install) · [示例](#delivery-example) · [升级](#update) · [排障](#installation-troubleshooting)
|
|
20
16
|
|
|
21
17
|
<a id="快速开始"></a>
|
|
22
18
|
<a id="install"></a>
|
|
23
|
-
##
|
|
24
|
-
|
|
25
|
-
需要 **Node.js 22+ 和 npm**。
|
|
19
|
+
## 开始一次交付
|
|
26
20
|
|
|
27
|
-
|
|
21
|
+
需要 **Node.js 22+ 和 npm**。为你的 AI 工具安装:
|
|
28
22
|
|
|
29
23
|
```sh
|
|
30
|
-
npx aimship@
|
|
24
|
+
npx aimship@latest install --host codex
|
|
31
25
|
```
|
|
32
26
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
```sh
|
|
36
|
-
npx aimship@preview install --host codex --host claude-code
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
npx 的获取包提示只确认下载并运行该包;执行 `install` 才会安装或升级 Aimship。`@preview` 表示已发布的预览通道,也可以替换为明确版本号,例如 `@0.1.0-preview.18`。预览通道用于实验性版本,不代表稳定版。
|
|
40
|
-
|
|
41
|
-
### 指定版本或本地包
|
|
27
|
+
`--host` 可选 `codex`、`cursor` 或 `claude-code`。连接多个工具时重复选项,例如 `--host codex --host claude-code`。无需全局 npm 安装、管理员权限或 npm 登录;npm 缓存需要可写。`latest` 表示当前推荐版本,首次正式版前指向 Preview,之后命令也不变。
|
|
42
28
|
|
|
43
|
-
|
|
29
|
+
刷新工具的 Skill 发现或新建会话,启用 Aimship,再提供具体需求。例如:
|
|
44
30
|
|
|
45
|
-
|
|
46
|
-
npx aimship@0.1.0-preview.18 install --host codex
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
如果已有 Aimship 的 `.tgz` 安装包,将 `VERSION` 替换为实际版本号,在包所在目录执行:
|
|
31
|
+
> 使用 Aimship,给这个后台增加选中订单的 CSV 导出,保留现有权限规则,先帮我澄清需求。
|
|
50
32
|
|
|
51
|
-
|
|
52
|
-
npx --offline file:./aimship-VERSION.tgz install --host codex
|
|
53
|
-
```
|
|
33
|
+
可以从想法、缺陷报告、已有 PRD 或原型开始,不必先准备完整规格或代码仓库。Aimship 默认在 `~/Aimship/` 下保存独立交付目录,告诉你实际位置、当前进度及下一项审核操作。代码保留原位置。工具需要时,将实际交付目录加入工作空间或授予访问权限。
|
|
54
34
|
|
|
55
|
-
|
|
35
|
+
<a id="开始一次交付"></a>
|
|
36
|
+
<a id="使用入口"></a>
|
|
37
|
+
[首次使用指南](docs/guide.zh-CN.md#start)说明工具启用、访问权限及首轮审核。已有交付请提供原目录,按[续接说明](docs/guide.zh-CN.md#continue)继续。
|
|
56
38
|
|
|
57
|
-
|
|
39
|
+
<a id="delivery-example"></a>
|
|
40
|
+
## 过程中你会看到什么
|
|
58
41
|
|
|
59
|
-
|
|
42
|
+
**以下是结构示例,不是真实客户完成的交付:** 上述订单导出需求可以形成这些对应关系。片段展示内容组织方式,不代表测试已执行。
|
|
60
43
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
44
|
+
| 文档 | 有判断价值的内容示例 |
|
|
45
|
+
| --- | --- |
|
|
46
|
+
| `prd.md` | REQ-001:仅导出选中订单;AC-001:选择两条有权限的订单时,结果恰好包含这两行。 |
|
|
47
|
+
| `tech-design.md`/适用契约 | 实施前解释权限校验、导出限制及错误处理。 |
|
|
48
|
+
| `plan.md` | TASK-001 实现选择与权限检查,批次完成后维护其执行状态。 |
|
|
49
|
+
| `review.md` | 记录审查代码基线、问题、影响、修复和复查结论。 |
|
|
50
|
+
| `testing.md` | TC-001 检查 AC-001;RUN 记录实际断言与依据前,执行状态保持“未执行”。 |
|
|
51
|
+
| `status.md` | 展示稳定交付阶段、当前审核停点与下一步;批次明细留在计划中。 |
|
|
64
52
|
|
|
65
|
-
|
|
53
|
+
<a id="一个需求会留下哪些文档"></a>
|
|
54
|
+
<a id="交付目录与文档"></a>
|
|
55
|
+
<a id="交付成果"></a>
|
|
56
|
+
<a id="交付流程"></a>
|
|
57
|
+
正常交付依次经过 **需求 → 技术设计及适用契约/SQL → 测试设计 → 计划 → 实施与批次审查 → 整体审查 → 独立验证 → 验收**。按实际需要产出文档;完整适用的已有 PRD 可直接采用,原件保留,原型按需选择。关键批准对应当前内容,阶段完成、测试通过与你的验收保持独立。
|
|
66
58
|
|
|
67
|
-
|
|
68
|
-
<a id="使用入口"></a>
|
|
69
|
-
安装完成后,按[使用指南](docs/guide.zh-CN.md#start)准备工作位置、启用 Aimship 并开始首个需求;已有交付可按[续接说明](docs/guide.zh-CN.md#continue)继续。
|
|
59
|
+
可查看[引导示例](docs/guide.zh-CN.md#start)及[交付目录参考](docs/guide.zh-CN.md#delivery-structure)。开发者还可[运行合成交付](CONTRIBUTING.md#executable-examples),其中有真实本地断言,但不代表 AI 工具行为已验证。
|
|
70
60
|
|
|
71
61
|
<a id="update"></a>
|
|
72
62
|
<a id="update-or-stop-using-aimship"></a>
|
|
73
63
|
<a id="升级与宿主管理"></a>
|
|
74
64
|
<a id="升级与-ai-开发工具管理"></a>
|
|
75
|
-
##
|
|
65
|
+
## 升级与工具管理
|
|
76
66
|
|
|
77
|
-
|
|
67
|
+
保存当前工作后,使用同一个安装命令:
|
|
78
68
|
|
|
79
69
|
```sh
|
|
80
|
-
npx aimship@
|
|
70
|
+
npx aimship@latest install
|
|
81
71
|
```
|
|
82
72
|
|
|
83
|
-
|
|
73
|
+
已有工具登记、配置、自定义内容和交付文件都会保留。普通更新无需重复 `--host` 或 `--data-root`,没有后台自动升级。升级后新建会话或刷新工具规则,启用 Aimship,继续原交付目录。
|
|
84
74
|
|
|
85
|
-
|
|
75
|
+
兼容的交付协议可以继续使用,不重写旧文档,也不使未变内容的有效审批失效。Preview 17/18 旧状态通过精确协议映射接续,不推定更早的未验证状态兼容。安装本身不修改交付记录。破坏性或未确认兼容的切换会说明影响,并在修改活动安装前停止。强烈建议先完成进行中的交付;明确接受风险是[高级恢复选项](#installation-troubleshooting),不代表允许写入不支持的状态。npx 的下载确认与此独立。
|
|
86
76
|
|
|
87
77
|
<a id="添加宿主"></a>
|
|
88
78
|
<a id="添加-ai-开发工具"></a>
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
例如,已有 Codex 安装后再接入 Cursor:
|
|
79
|
+
接入另一个工具,保留已有登记:
|
|
92
80
|
|
|
93
81
|
```sh
|
|
94
|
-
npx aimship@
|
|
82
|
+
npx aimship@latest install --host cursor
|
|
95
83
|
```
|
|
96
84
|
|
|
97
|
-
|
|
85
|
+
此操作也会安装当前推荐版本。找不到入口时查看 [AI 工具设置](docs/guide.zh-CN.md#hosts)。
|
|
98
86
|
|
|
99
|
-
|
|
87
|
+
不下载、不升级,直接查询本机安装:
|
|
100
88
|
|
|
101
89
|
```sh
|
|
102
90
|
node "$HOME/.aimship/bin/aimship.mjs" --version
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
该命令直接查询已安装的本机入口,`installedActiveVersion` 是实际生效版本。查询不会下载包或升级安装。
|
|
106
|
-
|
|
107
|
-
### 检查安装状态
|
|
108
|
-
|
|
109
|
-
```sh
|
|
110
91
|
node "$HOME/.aimship/bin/aimship.mjs" status
|
|
111
92
|
```
|
|
112
93
|
|
|
113
|
-
|
|
94
|
+
`installedActiveVersion` 是实际生效的软件版本。健康检查涵盖受管理文件和配置,不能确认 AI 工具已加载 Skill。查看交付进度请打开该目录的 `status.md`。若使用自定义 `--home`,请使用该位置下的本机入口。
|
|
114
95
|
|
|
115
|
-
|
|
96
|
+
### 可选资料位置
|
|
116
97
|
|
|
117
|
-
|
|
98
|
+
首次安装可附加 `--data-root "./MyDeliveries"` 指定其他交付根目录。相对路径从执行目录解析。后续安装保留配置,不搬迁已有交付;这里是资料位置,不是代码仓库。
|
|
118
99
|
|
|
119
100
|
<a id="uninstall"></a>
|
|
120
101
|
## 卸载
|
|
121
102
|
|
|
122
|
-
|
|
103
|
+
断开一个工具并保留核心,或移除全部受管理软件:
|
|
123
104
|
|
|
124
105
|
```sh
|
|
125
|
-
npx aimship@
|
|
106
|
+
npx aimship@latest uninstall --host cursor
|
|
107
|
+
npx aimship@latest uninstall
|
|
126
108
|
```
|
|
127
109
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
移除所有受管理的软件和 AI 工具入口:
|
|
131
|
-
|
|
132
|
-
```sh
|
|
133
|
-
npx aimship@preview uninstall
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
完整卸载保留交付资料、用户代码、自定义文件和必要配置。修改过或不属于它管理的文件会保留并报告。完全移除后,稳定入口也会移除;重启 AI 工具会话以清除缓存规则。删除交付历史是另一项需要明确提出的操作。
|
|
110
|
+
两条命令是不同操作,按需要选择。其他已登记工具需要的共享入口会保留;断开最后一个工具仍保留核心,完整卸载才移除稳定入口和受管理版本。两者都保留交付资料、用户代码、自定义文件与必要配置。修改过或不属于它管理的文件会保留并报告。之后刷新工具会话以清除缓存规则。
|
|
137
111
|
|
|
138
112
|
<a id="installation-troubleshooting"></a>
|
|
139
113
|
## 安装与升级问题
|
|
140
114
|
|
|
141
115
|
| 现象 | 处理方式 |
|
|
142
116
|
| --- | --- |
|
|
143
|
-
|
|
|
144
|
-
|
|
|
145
|
-
|
|
|
146
|
-
| 版本与预期不一致 |
|
|
147
|
-
|
|
|
117
|
+
| 入口缺失或重复 | 按 [AI 工具设置](docs/guide.zh-CN.md#hosts)刷新发现;安装健康不代表已经加载。 |
|
|
118
|
+
| 管理位置被修改或占用 | 保留内容,明确处理归属或定制冲突后再试。 |
|
|
119
|
+
| 安装中断 | 查询 `status`,排除原因后在同一安装位置重跑完全相同的待恢复包,恢复途中不换版本。 |
|
|
120
|
+
| 版本与预期不一致 | 查询 `installedActiveVersion`,通过 `install` 更新,再刷新会话规则。 |
|
|
121
|
+
| 更新要求明确确认 | 先完成进行中的交付。如果明确接受所述风险,给相同目标包的 `install` 附加 `--accept-upgrade-risk`。不支持的交付状态仍不能写入。 |
|
|
122
|
+
| 下载失败或包不存在 | 检查包、确切版本和 registry;下载失败不算安装成功。 |
|
|
148
123
|
|
|
149
|
-
|
|
124
|
+
<details>
|
|
125
|
+
<summary>高级:指定包与恢复</summary>
|
|
150
126
|
|
|
151
|
-
|
|
152
|
-
|
|
127
|
+
将 `VERSION` 替换为实际已发布或待恢复的版本,例如 `npx aimship@VERSION install`。明确接受升级风险时,在这个确切命令后附加 `--accept-upgrade-risk`。`npx --yes` 只确认下载/运行包,不能代替该参数。
|
|
128
|
+
|
|
129
|
+
已有本地安装包时,在其目录执行 `npx --offline file:./aimship-VERSION.tgz install --host codex`。源码与隔离安装检查见[贡献指南](CONTRIBUTING.md#isolated-installation-check)。
|
|
130
|
+
|
|
131
|
+
</details>
|
|
153
132
|
|
|
154
|
-
|
|
133
|
+
## 反馈与开发
|
|
155
134
|
|
|
156
|
-
|
|
135
|
+
[使用指南](docs/guide.zh-CN.md)涵盖审核、变更、接续与验收;[CLI 协议](docs/cli.md)供 Agent 和工具开发者查阅。开发与主动测试说明见[贡献指南](CONTRIBUTING.md)。
|
|
136
|
+
|
|
137
|
+
<a id="适用场景与示例"></a>
|
|
138
|
+
通过 [GitHub Issues](https://github.com/clsx/aimship/issues)反馈具体问题和建议,附最小复现,不公开私有交付数据。采用 [MIT 许可证](LICENSE)。
|
package/docs/cli.md
CHANGED
|
@@ -4,7 +4,7 @@ Commands emit JSON. Errors include code/message/recovery and use a nonzero exit.
|
|
|
4
4
|
|
|
5
5
|
## Create and bind
|
|
6
6
|
|
|
7
|
-
`bind` returns fixed CLI/core/stage paths, the bound version/schema, separate executing toolVersion/toolSchemaVersion, and configured data root. On a version switch, rebind through the stable launcher and read the returned fixed CLI help; a retained older executable may return stale schema metadata. Keep that binding for the session. Installation does not create deliveries.
|
|
7
|
+
`bind` returns fixed CLI/core/stage paths, the bound software version/schema/protocolVersion, separate executing toolVersion/toolSchemaVersion, and configured data root. On a version switch, rebind through the stable launcher and read the returned fixed CLI help; a retained older executable may return stale schema metadata. Keep that binding for the session. Installation does not create deliveries.
|
|
8
8
|
|
|
9
9
|
Default initialization must use a distinct delivery directory under the bound dataRoot; the root itself is not a delivery. The current project or supplied-material directory is not an explicit location choice. A different user-selected directory requires init input `storage: {"mode":"explicit","decisionRef":"source/location-decision.md"}` with a nonempty readable local record of that choice. The tool validates evidence access, not the truth of the decision. Do not supply dataRoot in input. No access means report the intended location and stop saving, without falling back elsewhere. Existing deliveries outside the root remain readable and recoverable.
|
|
10
10
|
|
|
@@ -32,7 +32,7 @@ node '<stable-launcher>' bind
|
|
|
32
32
|
node '<fixed-cli>' init '<delivery-dir>' --rules-version '<bound-version>' --input '<init.json>'
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
init uses schema 4, allocates deliveryId and revision 0, creates `.aimship/state.json`, and generates `status.md`. Add that ID to the document information at the next save point. Identity/version/timestamps, documentDigests are managed; never supply them in a patch. `writeResult` is return metadata, not state input. Its receipt contains actual identity, directory/state/status paths, bound version, current stage and next action; statusPath is null when view repair is needed. Verify the saved files before presenting the user receipt. Repeated init returns ALREADY_EXISTS; read/resume the same delivery and render a missing view instead of replaying creation.
|
|
35
|
+
init uses schema 4 and delivery protocol 1, allocates deliveryId and revision 0, creates `.aimship/state.json`, and generates `status.md`. Add that ID to the document information at the next save point. Identity/version/timestamps, protocolVersion and documentDigests are managed; never supply them in a patch. `writeResult` is return metadata, not state input. Its receipt contains actual identity, directory/state/status paths, bound version, current stage and next action; statusPath is null when view repair is needed. Verify the saved files before presenting the user receipt. Repeated init returns ALREADY_EXISTS; read/resume the same delivery and render a missing view instead of replaying creation.
|
|
36
36
|
|
|
37
37
|
`documentPolicy.authority`: null while the requirement authority is undecided; authored → prd.md; adopted → the registered retained PRD file in source/ (mode input); editable → an explicitly authorized existing PRD. All three use kind prd and goalRef points to its actual path. `interaction`, `prototypeCovered`, `interface` (none/http/other) and target `databases` IDs record artifact conditions. They do not select a preset route. Prototype coverage is descriptive; a prototype is optional unless selected in stageRuns, in which case its completion and applicable input check or AI-output approval remain required. Register the first real PRD and switch goalRef in the same update; this reference-only transition does not need a fabricated goal decision. Actual goal/endpoint changes still require goal.decisionRef. Early frame/requirements work can be active without the draft; done requires it. Design requires adopted, checked input requirements or approved AI-authored requirements and an agreed endpoint, but may analyze unknown technical conditions. Resolve those conditions before completing design or starting dependent contract/cases/plan/implementation. Required document approvals remain checked even when producer runs are absent; existing stage ordering and review semantics are unchanged.
|
|
38
38
|
|
|
@@ -77,7 +77,13 @@ The delivery identity, view digest and state revision are stored in `.aimship/vi
|
|
|
77
77
|
## Rule updates
|
|
78
78
|
|
|
79
79
|
|
|
80
|
-
Bind the active tool and reload its rules before work.
|
|
80
|
+
Bind the active tool and reload its rules before work. The session's `--rules-version`, executing tool and active installation must match exactly. `protocolVersion` separately identifies compatible state, approval and stage semantics; schema/policy validation still applies. Package metadata declares `aimship.protocolVersion`. Current protocol 1 precisely recognizes states from `0.1.0-preview.17` and `0.1.0-preview.18` without a protocol field. No other missing-field version is inferred compatible.
|
|
81
|
+
|
|
82
|
+
`rulesVersion` records the last saving tool. Reads/checks/resume and render do not rewrite state or adopt metadata. The next ordinary revision-checked update, or explicit recovery of a supported backup, records protocol 1 and the current saving tool; it preserves documents, delivery identity, progress and approval validity for unchanged content. A schema/policy/protocol mismatch or unrecognized legacy version blocks writes (`PROTOCOL_MISMATCH`); a mixed session/tool/active installation blocks writes (`VERSION_MISMATCH`). Old tools may reject records saved by a newer tool: downgrading is not a document migration or a guarantee of reverse compatibility.
|
|
83
|
+
|
|
84
|
+
The installer compares source/target package declarations, not user deliveries. Compatible switches proceed normally. Breaking or unverified switches return `UPGRADE_CONFIRMATION_REQUIRED` before pending-manifest, entry or active-pointer writes. The JSON includes versions, compatibility, actual impact and recovery; it does not claim to know which deliveries are ongoing. There is no interactive prompt. To explicitly accept the reported installation risk, rerun the same target package's `install --accept-upgrade-risk`. `npx --yes` is only npm's package-download/run consent. Acceptance does not bypass unsupported delivery writes. Interrupted accepted installs record that acceptance in the pending manifest and resume using the same exact package. Reinstalling the active version needs no new protocol decision. Legacy targets with exact software-version guards can still block continuation despite matching semantic protocol.
|
|
85
|
+
|
|
86
|
+
Software installation does not authorize inventing approvals, rewriting user documents or hand-editing state metadata.
|
|
81
87
|
|
|
82
88
|
Goal changes need a new goal.decisionRef; prior goals/outcomes are retained. A met goal requires goalConclusionRef; acceptance needs its own acceptanceRef. Merge and deployment refs remain independent. Reopening increments cycle, appends closureRefs and resets acceptance. See the [README](../README.md#update) for software lifecycle operations and the [Guide](guide.md#hosts) for host-discovery limits.
|
|
83
89
|
|
package/docs/guide.md
CHANGED
|
@@ -402,7 +402,7 @@ For duplicate entries, check their sources and versions while preserving custom
|
|
|
402
402
|
|
|
403
403
|
Save current work before [upgrading through the README](../README.md#update). Activate Aimship again afterward and check the actual bound version and delivery state; installation itself does not update delivery records.
|
|
404
404
|
|
|
405
|
-
|
|
405
|
+
Compatible protocol updates preserve existing documents and approvals of unchanged content; the next normal save records the current software version. Preview 17/18 states have a verified mapping. Unknown protocols remain blocked. If installation reports a breaking or unverified switch, finish ongoing deliveries before upgrading; accepting that risk never bypasses delivery-state protection. Use ordinary Markdown and searchable numbers, preserve actual test records and inspect the impact of substantive changes.
|
|
406
406
|
|
|
407
407
|
<a id="45-installation-and-maintenance-references"></a>
|
|
408
408
|
<a id="installation-and-maintenance-references"></a>
|
package/docs/guide.zh-CN.md
CHANGED
|
@@ -400,7 +400,7 @@ AI 只能核对已知且可访问的交付,不会自动锁定或调度其他
|
|
|
400
400
|
|
|
401
401
|
升级前保存当前工作,再按 README [升级](../README.zh-CN.md#update)。升级后重新启用 Aimship,核对实际绑定版本和交付状态;安装本身不会更新交付记录。
|
|
402
402
|
|
|
403
|
-
|
|
403
|
+
兼容协议更新保留已有文档及未变内容的有效审批,下一次正常保存才记录当前软件版本。Preview 17/18 状态有已验证映射,未知协议仍会受阻。安装提示破坏性或未确认兼容时,先完成进行中的交付再升级;明确接受安装风险也不放开交付状态保护。继续使用普通 Markdown 和可搜索编号,保留实际测试记录,实质变更仍核对影响。
|
|
404
404
|
|
|
405
405
|
<a id="45-安装与维护参考"></a>
|
|
406
406
|
<a id="安装与维护参考"></a>
|
package/lib/cli.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import os from 'node:os';
|
|
|
8
8
|
import { exists, fail, noSymlinks, readJSON } from './io.mjs';
|
|
9
9
|
import { install, installPaths, installationStatus, installationVersion, readConfig, uninstall } from './install.mjs';
|
|
10
10
|
import { SCHEMA_VERSION, STAGES, renderState, assertInstallationComplete, assertRules, checkReferences, initState, readState, recoverState, updateState, validateState } from './state.mjs';
|
|
11
|
+
import { PROTOCOL_VERSION, packageProtocol } from './protocol.mjs';
|
|
11
12
|
|
|
12
13
|
const HELP = {
|
|
13
14
|
usage: 'node <aimship.mjs> <command> [delivery-directory] [options]',
|
|
@@ -22,12 +23,13 @@ const HELP = {
|
|
|
22
23
|
check: 'check <dir> [--rules-version <version>] [--all]. Check structure and current input references (or every registered reference). Checks ordinary Markdown sections and searchable numbers in new local Markdown; reused and unsupported fragments are explicitly not checked. Does not prove semantics, remote access or validation results.',
|
|
23
24
|
resume: 'resume <dir> [--rules-version <version>] [--delivery-id <known-id>]. Read state and return fixed rule paths, required inputs, warnings, and continuation text; never execute work or rewrite state.',
|
|
24
25
|
recover: 'recover <dir> --revision <backup-revision> --rules-version <version>. Restore state.previous.json only over missing/corrupt state; preserve corrupted bytes. Inspect actual artifacts afterward; nothing reruns.',
|
|
25
|
-
install: 'install --host <codex|cursor|claude-code> [--host ...] [--data-root <dir>] [--home <dir>].
|
|
26
|
+
install: 'install --host <codex|cursor|claude-code> [--host ...] [--data-root <dir>] [--home <dir>] [--accept-upgrade-risk]. First install requires a host; later installs retain registrations. Breaking or unverified protocol switches stop before mutation unless explicitly accepted; npx --yes is unrelated. Confirmation never permits incompatible state writes. Installed tools default to their owning home; --home overrides for isolated testing. Data root is configured, never created.',
|
|
26
27
|
uninstall: 'uninstall [--host <host> ...] [--home <dir>]. --host disconnects selected hosts, retaining shared core even when no hosts remain. Omit --host for full product uninstall. Delivery data, config, custom and modified/unowned files are retained. Installed tools default to their owning home; explicit --home overrides it.',
|
|
27
28
|
help: 'help [command]. Every command supports --help. --version reports the executing tool version and installed active version separately. install is the only installation/update entry; update modifies delivery state.'
|
|
28
29
|
},
|
|
29
30
|
stateProtocol: {
|
|
30
31
|
schemaVersion: SCHEMA_VERSION,
|
|
32
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
31
33
|
documentReview: {reviewPolicy:1, reviewHistory:'Append-only {id,kind,document,version,digest,sourceRef,summary}. self-check requires result: passed|failed; approved requires decision: explicit-approval; editorial requires previousDigest. Read review.documents for current digests. Tools validate records, not user identity. See docs/cli.md#document-review-protocol.'},
|
|
32
34
|
deliveryPolicy: 'New states default to {version:1}; partialRef identifies an explicitly limited scope, null restores standard scope with a new goal decision. Completion is derived separately from goal/acceptance.',
|
|
33
35
|
sessions: 'run.sessionGroup is an advisory context group, auto-filled for new runs; reviewOf names implementation runs and overall:true identifies overall review. read/check/resume expose session/completion. Supplied PRD/prototype mode input uses the actual request basisRef and self-check, not human approval. next.questionRef identifies a concrete question on a blocked stage (clear with null). Completed stages return stop with await-review, await-clarification or await-continue, even within one group; suggestNewSession is advisory. Failed view saving returns reconcile-save. No new approval or automatic execution.',
|
|
@@ -39,7 +41,7 @@ const HELP = {
|
|
|
39
41
|
stageInstance: { id: 'stable-instance-id', stage: '<stage>', scope: 'batch or whole-delivery scope', progress: 'planned', inputRefs: [], outputRefs: [], resultRef: 'review.md#REV-001 (required when done)' },
|
|
40
42
|
references: 'delivery-relative/path#optional-fragment, repo:<registered-id>/<repo-relative-path>#fragment, or HTTP(S) URL. Local paths cannot escape their root. Repository registration: {id, path: absolute-path, role}.',
|
|
41
43
|
topLevel: {
|
|
42
|
-
managed: 'schemaVersion, revision, deliveryId, rulesVersion, createdAt, updatedAt, goalHistory, documentDigests, writeResult. Do not supply these in init/update.',
|
|
44
|
+
managed: 'schemaVersion, protocolVersion, revision, deliveryId, rulesVersion, createdAt, updatedAt, goalHistory, documentDigests, writeResult. Do not supply these in init/update. rulesVersion records the last saving tool; protocolVersion identifies compatible delivery semantics. Legacy 17/18 without it map precisely to protocol 1. Reads never adopt metadata; the next normal save records current tool/protocol without rewriting documents or revoking unchanged approvals.',
|
|
43
45
|
requiredInit: 'language: en|zh-CN and goal: {summary, endpoint?}; save nonempty initialization notes in .aimship/init.md before init. Defaults: endpoint standard delivery, goalRef .aimship/init.md, empty documents, and null documentPolicy conditions/authority. Register PRD and switch goalRef when drafted; no empty templates.',
|
|
44
46
|
language: 'en or zh-CN from explicit user preference, otherwise this delivery conversation. Required in current state; retained on update/render/resume.',
|
|
45
47
|
storage: 'Init-only {mode: explicit, decisionRef: delivery-relative-readable-file} for a user-selected location. Otherwise a strict child of configured dataRoot. Returned dataRoot is managed; ordinary workspace/material location is not a choice.',
|
|
@@ -71,8 +73,8 @@ function parse(argv) {
|
|
|
71
73
|
const item = argv[i];
|
|
72
74
|
if (!item.startsWith('--')) { positional.push(item); continue; }
|
|
73
75
|
const name = item.slice(2);
|
|
74
|
-
if (!['help', 'version', 'all', 'preserve-edits', 'input', 'rules-version', 'delivery-id', 'revision', 'host', 'home', 'data-root'].includes(name)) fail('USAGE', `Unknown option: ${item}`, 'Run help for command usage.');
|
|
75
|
-
const value = ['help', 'version', 'all', 'preserve-edits'].includes(name) ? true : argv[++i];
|
|
76
|
+
if (!['help', 'version', 'all', 'preserve-edits', 'accept-upgrade-risk', 'input', 'rules-version', 'delivery-id', 'revision', 'host', 'home', 'data-root'].includes(name)) fail('USAGE', `Unknown option: ${item}`, 'Run help for command usage.');
|
|
77
|
+
const value = ['help', 'version', 'all', 'preserve-edits', 'accept-upgrade-risk'].includes(name) ? true : argv[++i];
|
|
76
78
|
if (value === undefined || (typeof value === 'string' && value.startsWith('--'))) fail('USAGE', `Missing value for ${item}`, 'Provide the option value.');
|
|
77
79
|
if (name === 'host') (options.hosts ??= []).push(value);
|
|
78
80
|
else { if (Object.hasOwn(options, name)) fail('USAGE', `Repeated option: ${item}`, 'Provide one value; only --host is repeatable.'); options[name] = value; }
|
|
@@ -104,7 +106,7 @@ async function runtimeFor(sourceRoot, options, explicitRoot) {
|
|
|
104
106
|
activeVersion = active.version;
|
|
105
107
|
} else if (!installPending) fail('NOT_INSTALLED', 'No active installation exists.', 'Install the exact package explicitly.');
|
|
106
108
|
}
|
|
107
|
-
return { version: pkg.version, activeVersion, installPending, releaseRoot: sourceRoot, installRoot: root, launcher: root ? path.join(root, 'bin', 'aimship.mjs') : path.join(sourceRoot, 'bin', 'aimship.mjs') };
|
|
109
|
+
return { version: pkg.version, protocolVersion: packageProtocol(pkg), activeVersion, installPending, releaseRoot: sourceRoot, installRoot: root, launcher: root ? path.join(root, 'bin', 'aimship.mjs') : path.join(sourceRoot, 'bin', 'aimship.mjs') };
|
|
108
110
|
}
|
|
109
111
|
async function binding(runtime) {
|
|
110
112
|
assertInstallationComplete(runtime);
|
|
@@ -122,11 +124,11 @@ async function binding(runtime) {
|
|
|
122
124
|
const protocolPath=path.join(root,'lib/state.mjs'); await noSymlinks(protocolPath);
|
|
123
125
|
const targetProtocol = await import(pathToFileURL(protocolPath).href);
|
|
124
126
|
if (!Number.isSafeInteger(targetProtocol.SCHEMA_VERSION) || targetProtocol.SCHEMA_VERSION<1) fail('INCOMPLETE_RELEASE','Bound release does not export its state protocol.','Repair the exact release before creating state.');
|
|
125
|
-
return { version: pkg.version, schemaVersion: targetProtocol.SCHEMA_VERSION, toolVersion:runtime.version, toolSchemaVersion:SCHEMA_VERSION, launcherProtocol: 1, releaseRoot: root, corePath, stageDirectory: path.join(root, 'skills', 'aimship', 'references', 'stages'), cliPath: path.join(root, 'bin', 'aimship.mjs'), launcherPath: runtime.launcher, nodePath: process.execPath, dataRoot, configPath, customPath: runtime.installRoot ? path.join(runtime.installRoot, 'custom') : null, configurationStatus: runtime.installRoot ? 'installed' : 'source-default' };
|
|
127
|
+
return { version: pkg.version, protocolVersion: packageProtocol(pkg), schemaVersion: targetProtocol.SCHEMA_VERSION, toolVersion:runtime.version, toolSchemaVersion:SCHEMA_VERSION, launcherProtocol: 1, releaseRoot: root, corePath, stageDirectory: path.join(root, 'skills', 'aimship', 'references', 'stages'), cliPath: path.join(root, 'bin', 'aimship.mjs'), launcherPath: runtime.launcher, nodePath: process.execPath, dataRoot, configPath, customPath: runtime.installRoot ? path.join(runtime.installRoot, 'custom') : null, configurationStatus: runtime.installRoot ? 'installed' : 'source-default' };
|
|
126
128
|
}
|
|
127
129
|
function allowed(command, options) {
|
|
128
130
|
const common = ['help', 'version'];
|
|
129
|
-
const perCommand = { render: ['revision','rules-version','preserve-edits','home'], scan:['home'], status: ['home'], bind: ['home'], init: ['input', 'rules-version', 'home'], read: ['rules-version', 'delivery-id', 'home'], update: ['input', 'rules-version', 'revision', 'home'], check: ['all', 'rules-version', 'home'], resume: ['rules-version', 'delivery-id', 'home'], recover: ['rules-version', 'revision', 'home'], install: ['hosts', 'home', 'data-root'], uninstall: ['hosts', 'home'], help: [] };
|
|
131
|
+
const perCommand = { render: ['revision','rules-version','preserve-edits','home'], scan:['home'], status: ['home'], bind: ['home'], init: ['input', 'rules-version', 'home'], read: ['rules-version', 'delivery-id', 'home'], update: ['input', 'rules-version', 'revision', 'home'], check: ['all', 'rules-version', 'home'], resume: ['rules-version', 'delivery-id', 'home'], recover: ['rules-version', 'revision', 'home'], install: ['hosts', 'home', 'data-root','accept-upgrade-risk'], uninstall: ['hosts', 'home'], help: [] };
|
|
130
132
|
for (const key of Object.keys(options)) if (![...common, ...(perCommand[command] ?? [])].includes(key)) fail('USAGE', `Option --${key} is not valid for ${command}.`, `Run help ${command}.`);
|
|
131
133
|
}
|
|
132
134
|
export async function run(argv, context) {
|
|
@@ -148,7 +150,7 @@ export async function run(argv, context) {
|
|
|
148
150
|
allowed(command, options);
|
|
149
151
|
if (['install', 'uninstall', 'bind', 'status'].includes(command) ? positional.length !== 1 : positional.length !== 2) fail('USAGE', 'Unexpected or missing positional arguments.', `Run help ${command}.`);
|
|
150
152
|
if (command === 'status') return { command, data: { toolVersion: pkg.version, ...await installationStatus({ home: installHome }) } };
|
|
151
|
-
if (command === 'install') return { command, data: await install(context.sourceRoot, { home: installHome, hosts: options.hosts, dataRoot: options['data-root'] }) };
|
|
153
|
+
if (command === 'install') return { command, data: await install(context.sourceRoot, { home: installHome, hosts: options.hosts, dataRoot: options['data-root'], acceptUpgradeRisk: options['accept-upgrade-risk'] }) };
|
|
152
154
|
if (command === 'uninstall') return { command, data: await uninstall({ home: installHome, hosts: options.hosts }) };
|
|
153
155
|
if (command === 'scan') return {command,data:await scanDeliveries(path.resolve(positional[1]))};
|
|
154
156
|
const runtime = await runtimeFor(context.sourceRoot, options, context.installRoot);
|
|
@@ -171,7 +173,7 @@ export async function run(argv, context) {
|
|
|
171
173
|
const missing = references.filter(x => !['exists', 'not-checked'].includes(x.status));
|
|
172
174
|
if (command === 'check') {
|
|
173
175
|
if (missing.length) fail('REFERENCE_CHECK', 'Required references are missing or inaccessible.', 'Repair the specific registered paths; do not scan the disk or overwrite source materials.', { references, versionMismatch: mismatch });
|
|
174
|
-
if (mismatch) fail(
|
|
176
|
+
if (mismatch) fail(mismatch.code, 'State is structurally valid, but the session or delivery protocol is incompatible.', mismatch.recovery, mismatch);
|
|
175
177
|
if (documents.issues.length) fail('DOCUMENT_CHECK','Document mapping or identities need repair.','Inspect listed issues.',documents);
|
|
176
178
|
return { command, data: { structure: 'valid', review:read.review, completion:read.completion, session:read.session, references, documents, view:read.view, documentChanges:read.documentChanges, semantics: 'not-checked' } };
|
|
177
179
|
}
|
package/lib/install.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import { randomUUID } from 'node:crypto';
|
|
5
5
|
import { atomicWrite, exists, fail, fileHash, hash, inside, json, listFiles, noSymlinks, readJSON, withLock } from './io.mjs';
|
|
6
|
+
import { upgradeCompatibility } from './protocol.mjs';
|
|
6
7
|
|
|
7
8
|
export const HOSTS = ['codex', 'cursor', 'claude-code'];
|
|
8
9
|
const validVersion = version => typeof version === 'string' && /^[0-9]+\.[0-9]+\.[0-9]+(?:-[a-zA-Z0-9.-]+)?$/.test(version);
|
|
@@ -137,7 +138,7 @@ export async function releaseFiles(source) {
|
|
|
137
138
|
if (!inside(source, path.resolve(source, name))) fail('INVALID_RELEASE', 'Package path escapes root.', 'Use ordinary in-root files.');
|
|
138
139
|
result[name] = await fileHash(path.join(source, name));
|
|
139
140
|
}
|
|
140
|
-
for (const required of REQUIRED_RELEASE_FILES) if (!result[required]) fail('INCOMPLETE_RELEASE', `Required release file is missing: ${required}`, 'Use the complete package; do not install partial source.');
|
|
141
|
+
for (const required of [...REQUIRED_RELEASE_FILES,...(pkg.aimship ? ['lib/protocol.mjs'] : [])]) if (!result[required]) fail('INCOMPLETE_RELEASE', `Required release file is missing: ${required}`, 'Use the complete package; do not install partial source.');
|
|
141
142
|
return { pkg, files: result };
|
|
142
143
|
}
|
|
143
144
|
const launcherText = `#!/usr/bin/env node
|
|
@@ -295,6 +296,28 @@ export async function install(source, options = {}) {
|
|
|
295
296
|
if (!hosts.length && !fromVersion) fail('HOST_REQUIRED', 'Choose at least one host for the first install.', 'Repeat install --host codex, --host cursor, or --host claude-code; --host is repeatable.');
|
|
296
297
|
if (!hosts.every(x => HOSTS.includes(x))) fail('INVALID_HOST', 'Unknown install host.', `Supported hosts: ${HOSTS.join(', ')}.`);
|
|
297
298
|
if (manifest.pending && manifest.pending.version !== pkg.version) fail('PENDING_INSTALL', `Installation of ${manifest.pending.version} is incomplete.`, 'Resume the same exact package install before selecting a different version.');
|
|
299
|
+
let previousPackage = fromVersion ? { version: fromVersion } : null;
|
|
300
|
+
if (fromVersion && fromVersion !== pkg.version) {
|
|
301
|
+
const previousFile = path.join(p.root,'releases',fromVersion,'package.json');
|
|
302
|
+
try {
|
|
303
|
+
await noSymlinks(previousFile);
|
|
304
|
+
previousPackage = await readJSON(previousFile);
|
|
305
|
+
if (previousPackage.version !== fromVersion || await fileHash(previousFile) !== manifest.releases[fromVersion]?.files['package.json']) previousPackage = { version: fromVersion, aimship: null };
|
|
306
|
+
} catch (error) {
|
|
307
|
+
if (error.code === 'SYMLINK') throw error;
|
|
308
|
+
previousPackage = { version: fromVersion, aimship: null };
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
const compatibility = upgradeCompatibility(previousPackage,pkg);
|
|
312
|
+
const riskAccepted = options.acceptUpgradeRisk === true || (resumed && manifest.pending.upgradeRiskAccepted === true);
|
|
313
|
+
if (compatibility.status !== 'compatible' && !riskAccepted) {
|
|
314
|
+
const breaking = compatibility.status === 'breaking';
|
|
315
|
+
fail('UPGRADE_CONFIRMATION_REQUIRED', breaking
|
|
316
|
+
? 'This update includes breaking changes. Compatibility with ongoing deliveries cannot be guaranteed. We strongly recommend completing all ongoing deliveries before upgrading.'
|
|
317
|
+
: 'Compatibility with the installed delivery protocol has not been verified. We strongly recommend completing all ongoing deliveries before upgrading.',
|
|
318
|
+
'Keep the current installation, or rerun the same install with --accept-upgrade-risk to explicitly accept this installation switch. npx --yes only accepts downloading a package.',
|
|
319
|
+
{ compatibility, warningZh: breaking ? '本次更新包含破坏性变更,无法保证进行中的交付与新版本兼容。强烈建议完成当前所有进行中的交付后再升级。' : '本次更新与当前交付协议的兼容性尚未确认。强烈建议完成当前所有进行中的交付后再升级。', impact: breaking ? 'The target cannot continue deliveries using the source protocol/version. Existing delivery files remain, but state progression is blocked.' : 'Existing delivery files remain. Continuation support is unverified; unsupported states cannot be written.', deliveryInspection: 'No delivery directories were scanned.' });
|
|
320
|
+
}
|
|
298
321
|
const desired = await desiredFiles(source, p, hosts, pkg.version); await verifyManaged(p, manifest, desired);
|
|
299
322
|
let repairsNeeded = !(await exists(path.join(p.root, 'releases', pkg.version))) || !(await exists(p.active)) || !(await exists(p.config)) || !(await exists(path.join(p.root, 'custom')));
|
|
300
323
|
// A present release directory can still need repair when only an internal managed file is missing.
|
|
@@ -302,7 +325,7 @@ export async function install(source, options = {}) {
|
|
|
302
325
|
for (const file of Object.keys(desired)) if (!await exists(path.join(p.home, file))) repairsNeeded = true;
|
|
303
326
|
const managed = Object.fromEntries(Object.entries(desired).map(([file, data]) => [file, hash(data)]));
|
|
304
327
|
const activeData = json({ version: pkg.version, launcherProtocol: 1 });
|
|
305
|
-
manifest.status = 'pending'; manifest.pending = { version: pkg.version, hosts, managed, previousActive: manifest.activeVersion ?? null, activeDigest: hash(activeData) };
|
|
328
|
+
manifest.status = 'pending'; manifest.pending = { version: pkg.version, hosts, managed, previousActive: manifest.activeVersion ?? null, activeDigest: hash(activeData), upgradeRiskAccepted: riskAccepted };
|
|
306
329
|
await atomicWrite(p.manifest, json(manifest));
|
|
307
330
|
try {
|
|
308
331
|
await prepareRelease(source, p, pkg, files, manifest);
|
|
@@ -317,7 +340,7 @@ export async function install(source, options = {}) {
|
|
|
317
340
|
if (manifest.activeVersion && manifest.activeVersion !== pkg.version) manifest.previousVersion = manifest.activeVersion;
|
|
318
341
|
manifest.activeVersion = pkg.version; manifest.activeDigest = hash(activeData); manifest.hosts = hosts; manifest.managed = managed; manifest.status = 'complete'; delete manifest.pending; delete manifest.lastError;
|
|
319
342
|
const preserved = await pruneReleases(p, manifest); await atomicWrite(p.manifest, json(manifest));
|
|
320
|
-
return { version: pkg.version, ...facts, activeVersion: pkg.version, result: resumed ? 'recovered' : (facts.action === 'reinstall' ? (repairsNeeded ? 'repaired' : 'unchanged') : 'success'), resumed, ...p, hosts, entries: hostDirectories(hosts).map(x => path.join(p.home, x, 'SKILL.md')), preserved, node: process.execPath, dataDirectoryCreated: false, reload: 'Reload host skill discovery or start a new session if its selector still shows an old entry.' };
|
|
343
|
+
return { version: pkg.version, ...facts, activeVersion: pkg.version, compatibility, upgradeRiskAccepted: compatibility.status !== 'compatible' && riskAccepted, result: resumed ? 'recovered' : (facts.action === 'reinstall' ? (repairsNeeded ? 'repaired' : 'unchanged') : 'success'), resumed, ...p, hosts, entries: hostDirectories(hosts).map(x => path.join(p.home, x, 'SKILL.md')), preserved, node: process.execPath, dataDirectoryCreated: false, reload: 'Reload host skill discovery or start a new session if its selector still shows an old entry.' };
|
|
321
344
|
} catch (error) {
|
|
322
345
|
manifest.lastError = { code: error.code ?? 'IO_ERROR', message: error.message }; await atomicWrite(p.manifest, json(manifest));
|
|
323
346
|
fail('INSTALL_INCOMPLETE', 'Installation is incomplete; existing delivery data was not touched.', 'Inspect details and retry the same exact package install. The manifest records pending paths; activation may still point to the previous complete release.', { cause: manifest.lastError, pending: manifest.pending, active: await exists(p.active) ? await readJSON(p.active) : null });
|
package/lib/protocol.mjs
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Change this when state, approval, or stage semantics become incompatible.
|
|
2
|
+
export const PROTOCOL_VERSION = 1;
|
|
3
|
+
// Historical facts remain fixed when PROTOCOL_VERSION advances.
|
|
4
|
+
const legacy = new Map([['0.1.0-preview.17', 1], ['0.1.0-preview.18', 1]]);
|
|
5
|
+
|
|
6
|
+
export function packageProtocol(pkg) {
|
|
7
|
+
if (Object.hasOwn(pkg, 'aimship')) return Number.isSafeInteger(pkg.aimship?.protocolVersion) && pkg.aimship.protocolVersion > 0 ? pkg.aimship.protocolVersion : null;
|
|
8
|
+
return legacy.get(pkg.version) ?? null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function stateProtocol(state) {
|
|
12
|
+
// A known protocol never substitutes for validating its schema and policies.
|
|
13
|
+
if (state.schemaVersion !== 4 || state.reviewPolicy !== 1 || state.deliveryPolicy?.version !== 1) return null;
|
|
14
|
+
if (Object.hasOwn(state, 'protocolVersion')) return state.protocolVersion;
|
|
15
|
+
return legacy.get(state.rulesVersion) ?? null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function upgradeCompatibility(from, to) {
|
|
19
|
+
const fromProtocol = from ? packageProtocol(from) : null, toProtocol = packageProtocol(to);
|
|
20
|
+
let status = 'compatible';
|
|
21
|
+
if (from && from.version !== to.version) {
|
|
22
|
+
if (fromProtocol === null || toProtocol === null) status = 'unverified';
|
|
23
|
+
// Legacy tools still demand exact software versions, even for protocol 1.
|
|
24
|
+
else if (!to.aimship || fromProtocol !== toProtocol) status = 'breaking';
|
|
25
|
+
}
|
|
26
|
+
return { status, fromProtocol, toProtocol };
|
|
27
|
+
}
|
package/lib/state.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import path from 'node:path';
|
|
|
7
7
|
import { inspectDocuments, documentChanges, hasLocator, artifactMap } from './documents.mjs';
|
|
8
8
|
import { savedResult, viewStatus, generateView } from './view.mjs';
|
|
9
9
|
import { randomUUID } from 'node:crypto';
|
|
10
|
+
import { PROTOCOL_VERSION, stateProtocol } from './protocol.mjs';
|
|
10
11
|
import { atomicWrite, exists, fail, inside, json, noSymlinks, readJSON, withLock } from './io.mjs';
|
|
11
12
|
|
|
12
13
|
export const SCHEMA_VERSION = 4;
|
|
@@ -32,6 +33,7 @@ export function validateState(state) {
|
|
|
32
33
|
requireThat(object(state), 'State must be an object.');
|
|
33
34
|
requireThat(['en','zh-CN'].includes(state.language), 'language must be en or zh-CN.');
|
|
34
35
|
requireThat(state.schemaVersion === SCHEMA_VERSION, 'Invalid schemaVersion.');
|
|
36
|
+
if (Object.hasOwn(state,'protocolVersion')) requireThat(Number.isSafeInteger(state.protocolVersion) && state.protocolVersion > 0, 'Invalid protocolVersion.');
|
|
35
37
|
requireThat(state.reviewPolicy===1 && state.deliveryPolicy?.version===1,'Current document and delivery approval policies are required.');
|
|
36
38
|
if (state.schemaVersion === SCHEMA_VERSION) {
|
|
37
39
|
requireThat(object(state.documentPolicy) && (['authored','adopted','editable'].includes(state.documentPolicy.authority) || (state.schemaVersion === 4 && state.documentPolicy.authority === null)), 'documentPolicy.authority is required.');
|
|
@@ -129,14 +131,15 @@ function checkRevision(state, revision) {
|
|
|
129
131
|
}
|
|
130
132
|
export function assertRules(state, rulesVersion, runtime) {
|
|
131
133
|
assertInstallationComplete(runtime);
|
|
132
|
-
if (!rulesVersion || rulesVersion !==
|
|
134
|
+
if (!rulesVersion || rulesVersion !== runtime.version || runtime.activeVersion !== runtime.version) fail('VERSION_MISMATCH', 'The session, tool, and active installation must use the same software version.', 'Bind the active version and reload its core and current stage; do not use an old session or tool after switching installations.', { session: rulesVersion, state: state.rulesVersion, tool: runtime.version, active: runtime.activeVersion, launcher: runtime.launcher });
|
|
135
|
+
if (stateProtocol(state) !== PROTOCOL_VERSION || (runtime.protocolVersion === undefined ? PROTOCOL_VERSION : runtime.protocolVersion) !== PROTOCOL_VERSION) fail('PROTOCOL_MISMATCH', 'This tool cannot continue the recorded delivery protocol.', 'Keep the delivery unchanged. Use a supported release; installation confirmation does not authorize incompatible state writes.', { state: state.rulesVersion, protocol: stateProtocol(state), supportedProtocol: PROTOCOL_VERSION, tool: runtime.version });
|
|
133
136
|
}
|
|
134
137
|
export function assertInstallationComplete(runtime) {
|
|
135
138
|
if (runtime.installPending) fail('INSTALL_PENDING', 'The installation is incomplete; new workflow writes and bindings are suspended.', 'Read state without progressing, then rerun install from the same exact pending package. After recovery, bind and reload its fixed core before continuing.', { pendingVersion: runtime.installPending.version, activeVersion: runtime.activeVersion, launcher: runtime.launcher });
|
|
136
139
|
}
|
|
137
140
|
export async function initState(directory, input, rulesVersion, runtime) {
|
|
138
141
|
assertInstallationComplete(runtime);
|
|
139
|
-
if (rulesVersion !== runtime.version || runtime.activeVersion !== runtime.version) fail('VERSION_MISMATCH', 'New delivery requires the active tool and
|
|
142
|
+
if (rulesVersion !== runtime.version || runtime.activeVersion !== runtime.version || (runtime.protocolVersion === undefined ? PROTOCOL_VERSION : runtime.protocolVersion) !== PROTOCOL_VERSION) fail('VERSION_MISMATCH', 'New delivery requires the active tool, rules version and supported protocol.', 'Bind the current installation and use its returned version.');
|
|
140
143
|
const p = statePaths(directory); await noSymlinks(p.internal);
|
|
141
144
|
if (await exists(p.statePath) || await exists(p.backup)) fail('ALREADY_EXISTS','Existing delivery or recovery material found.','Read/resume the existing delivery.');
|
|
142
145
|
requireThat(object(input) && ['en','zh-CN'].includes(input.language), 'New delivery input requires language en or zh-CN, from the explicit preference or current user conversation.');
|
|
@@ -144,10 +147,10 @@ export async function initState(directory, input, rulesVersion, runtime) {
|
|
|
144
147
|
await fs.mkdir(p.internal, { recursive: true });
|
|
145
148
|
return withLock(p.lock, async () => {
|
|
146
149
|
if (await exists(p.statePath)) fail('ALREADY_EXISTS', 'A delivery already exists here.', 'Read/resume this delivery or choose a different folder.');
|
|
147
|
-
const forbidden = ['schemaVersion', 'revision', 'deliveryId', 'rulesVersion', 'createdAt', 'updatedAt', 'goalHistory', 'documentDigests', 'writeResult'];
|
|
150
|
+
const forbidden = ['schemaVersion', 'protocolVersion', 'revision', 'deliveryId', 'rulesVersion', 'createdAt', 'updatedAt', 'goalHistory', 'documentDigests', 'writeResult'];
|
|
148
151
|
requireThat(object(input) && !forbidden.some(x => Object.hasOwn(input, x)), 'init input must not set tool-managed identity/version fields.');
|
|
149
152
|
const now = new Date().toISOString();
|
|
150
|
-
const state = { goalRef:'.aimship/init.md', documentPolicy:{authority:null,interaction:null,prototypeCovered:null,interface:null,databases:null}, documents:[], deliveryPolicy: {version:1}, reviewPolicy: 1, reviewHistory: [], cycle: 1, closureRefs: [], stageRuns: [], currentRun: null, openChanges: [], repositories: [], outcome: { goal: 'in-progress', acceptance: 'not-recorded' }, next: { action: text(input,'Clarify the request and prepare requirements for review.','澄清诉求并准备需求审核。'), inputRefs:['.aimship/init.md'] }, ...input, storage, schemaVersion: SCHEMA_VERSION, revision: 0, deliveryId: randomUUID(), rulesVersion, createdAt: now, updatedAt: now };
|
|
153
|
+
const state = { goalRef:'.aimship/init.md', documentPolicy:{authority:null,interaction:null,prototypeCovered:null,interface:null,databases:null}, documents:[], deliveryPolicy: {version:1}, reviewPolicy: 1, reviewHistory: [], cycle: 1, closureRefs: [], stageRuns: [], currentRun: null, openChanges: [], repositories: [], outcome: { goal: 'in-progress', acceptance: 'not-recorded' }, next: { action: text(input,'Clarify the request and prepare requirements for review.','澄清诉求并准备需求审核。'), inputRefs:['.aimship/init.md'] }, ...input, storage, schemaVersion: SCHEMA_VERSION, protocolVersion: PROTOCOL_VERSION, revision: 0, deliveryId: randomUUID(), rulesVersion, createdAt: now, updatedAt: now };
|
|
151
154
|
if (object(input.documentPolicy)) state.documentPolicy = {authority:null,interaction:null,prototypeCovered:null,interface:null,databases:null,...input.documentPolicy};
|
|
152
155
|
if (object(state.goal) && state.goal.endpoint == null) state.goal.endpoint = text(state,'Implementation, review, independent verification and delivery acceptance.','完成实现、评审、独立验证和交付验收。');
|
|
153
156
|
arrangeSessions(state); validateState(state); requireThat(state.deliveryPolicy?.version === 1, 'New deliveries require deliveryPolicy version 1.'); await inspectForWrite(directory,state); await enforceReviews(directory,state); await enforceCompletion(directory,state); await atomicWrite(p.statePath, json(state)); return savedResult(directory,state,{initialized:true});
|
|
@@ -185,9 +188,10 @@ export async function updateState(directory, patch, revision, rulesVersion, runt
|
|
|
185
188
|
const previous = read.state; validateState(previous); checkRevision(previous, revision);
|
|
186
189
|
requireThat(object(patch), 'Update input must be an object.');
|
|
187
190
|
assertRules(previous, rulesVersion, runtime);
|
|
188
|
-
const immutable = ['schemaVersion', 'revision', 'deliveryId', 'rulesVersion', 'createdAt', 'updatedAt', 'goalHistory', 'documentDigests', 'writeResult', 'storage'];
|
|
191
|
+
const immutable = ['schemaVersion', 'protocolVersion', 'revision', 'deliveryId', 'rulesVersion', 'createdAt', 'updatedAt', 'goalHistory', 'documentDigests', 'writeResult', 'storage'];
|
|
189
192
|
requireThat(!immutable.some(x => Object.hasOwn(patch, x)), 'Identity, revision, and historical metadata are tool-managed.');
|
|
190
193
|
const next = merge(previous, patch);
|
|
194
|
+
next.protocolVersion = PROTOCOL_VERSION; next.rulesVersion = runtime.version;
|
|
191
195
|
requireThat(previous.deliveryPolicy === undefined || next.deliveryPolicy?.version === previous.deliveryPolicy.version, 'Cannot remove or downgrade deliveryPolicy.');
|
|
192
196
|
arrangeSessions(next); validateState(next);
|
|
193
197
|
// Registering the first formal basis is not a new user scope decision.
|
|
@@ -214,13 +218,14 @@ export async function recoverState(directory, revision, rulesVersion, runtime) {
|
|
|
214
218
|
if (parsed) {
|
|
215
219
|
requireThat(parsed.deliveryPolicy?.version === backup.deliveryPolicy.version && parsed.reviewPolicy === backup.reviewPolicy, 'State and recovery copy must use the same policies.');
|
|
216
220
|
requireThat(parsed.schemaVersion === backup.schemaVersion, 'State and recovery copy must use the same schemaVersion.');
|
|
221
|
+
requireThat(stateProtocol(parsed) === stateProtocol(backup), 'State and recovery copy must use the same supported protocol.');
|
|
217
222
|
try { validateState(parsed); fail('STATE_VALID', 'Current state is valid; refusing rollback.', 'Use a normal revision-checked update to reconcile facts.'); }
|
|
218
223
|
catch (e) { if (!['INVALID_STATE','REVIEW_CHECK'].includes(e.code)) throw e; }
|
|
219
224
|
if (Number.isSafeInteger(parsed.revision)) corruptRevision = Math.max(corruptRevision, parsed.revision);
|
|
220
225
|
}
|
|
221
226
|
await atomicWrite(path.join(p.internal, `state.corrupt.${randomUUID()}.json`), raw);
|
|
222
227
|
}
|
|
223
|
-
const next = { ...backup, revision: corruptRevision + 1, updatedAt: new Date().toISOString(), recovery: { fromRevision: backup.revision, action: 'Inspect actual artifacts and unfinished effects before proceeding; nothing has been re-executed.' } };
|
|
228
|
+
const next = { ...backup, protocolVersion: PROTOCOL_VERSION, rulesVersion: runtime.version, revision: corruptRevision + 1, updatedAt: new Date().toISOString(), recovery: { fromRevision: backup.revision, action: 'Inspect actual artifacts and unfinished effects before proceeding; nothing has been re-executed.' } };
|
|
224
229
|
await atomicWrite(p.statePath, json(next)); return savedResult(directory,next);
|
|
225
230
|
});
|
|
226
231
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aimship",
|
|
3
|
-
"version": "0.1.0-preview.
|
|
3
|
+
"version": "0.1.0-preview.19",
|
|
4
4
|
"description": "Explicit, goal-driven delivery workflow for coding agents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -41,5 +41,8 @@
|
|
|
41
41
|
"bugs": {
|
|
42
42
|
"url": "https://github.com/clsx/aimship/issues"
|
|
43
43
|
},
|
|
44
|
-
"homepage": "https://github.com/clsx/aimship#readme"
|
|
44
|
+
"homepage": "https://github.com/clsx/aimship#readme",
|
|
45
|
+
"aimship": {
|
|
46
|
+
"protocolVersion": 1
|
|
47
|
+
}
|
|
45
48
|
}
|
|
@@ -6,9 +6,11 @@ A revision conflict means another writer or manual change intervened. Read and r
|
|
|
6
6
|
|
|
7
7
|
A path move requires the provided new location and identity evidence, not a disk-wide name search. Keep IDs stable. Repair references after checking the relevant repository/delivery; do not move or copy code. Preserve manual document edits and assess their intent and downstream impact before updating state.
|
|
8
8
|
|
|
9
|
-
A tool/
|
|
9
|
+
A tool/session/active-installation mismatch stops new stage advancement. Run bind through the stable installation launcher and read the returned fixed-version core and CLI help. Use that bound tool and protocol, not cached metadata from another executable. Preserve unfinished work and actual decisions; do not change state versions by hand or invent approvals. If the conversation cannot reliably refresh, provide a manual continuation prompt; do not open a new conversation automatically.
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
Compatible delivery protocols can continue under the current bound software. Legacy 17/18 states map precisely to protocol 1; unknown legacy versions or unsupported protocols stay blocked. Read/check/resume and render do not adopt state metadata; a normal revision-checked save records the current tool/protocol without rewriting documents or invalidating approvals of unchanged content. Do not downgrade assuming the old tool can read newer saved metadata. An installer risk confirmation permits only the software switch, never an unsupported delivery write.
|
|
13
|
+
|
|
12
14
|
Installation and data are separate. Explicit install manages product-owned files and AI-tool entries. Preserve user customizations and unrelated same-name skills; report the exact conflict. Uninstall retains delivery materials, customization and necessary configuration. Recovery retains a bounded previous installation, not every runtime forever. Consult the CLI help for implemented operations; do not bypass checks with raw state edits to obtain a success result.
|
|
13
15
|
|
|
14
16
|
## State and view recovery
|