aimship 0.1.0-preview.4 → 0.1.0-preview.8
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 +59 -0
- package/README.md +126 -19
- package/README.zh-CN.md +127 -20
- package/docs/cli.md +68 -32
- package/docs/examples.md +5 -0
- package/docs/guide.md +181 -30
- package/docs/guide.zh-CN.md +210 -0
- package/docs/installation.md +19 -108
- package/docs/reference.md +78 -0
- package/docs/reference.zh-CN.md +78 -0
- package/lib/approvals.mjs +153 -0
- package/lib/cli.mjs +29 -16
- package/lib/documents.mjs +156 -0
- package/lib/migrate.mjs +77 -0
- package/lib/state.mjs +96 -24
- package/lib/view.mjs +75 -0
- package/package.json +3 -2
- package/skills/aimship/SKILL.md +2 -2
- package/skills/aimship/references/artifact-map.json +172 -0
- package/skills/aimship/references/core.md +12 -6
- package/skills/aimship/references/document-patterns.md +32 -10
- package/skills/aimship/references/document-review.md +28 -0
- package/skills/aimship/references/hosts.md +4 -2
- package/skills/aimship/references/recovery.md +10 -0
- package/skills/aimship/references/sources-and-changes.md +8 -0
- package/skills/aimship/references/stages/contract.md +10 -2
- package/skills/aimship/references/stages/diagnose.md +10 -0
- package/skills/aimship/references/stages/frame.md +4 -0
- package/skills/aimship/references/stages/handoff.md +4 -0
- package/skills/aimship/references/stages/implement.md +6 -0
- package/skills/aimship/references/stages/plan.md +10 -2
- package/skills/aimship/references/stages/prototype.md +8 -0
- package/skills/aimship/references/stages/requirements.md +8 -0
- package/skills/aimship/references/stages/review.md +4 -0
- package/skills/aimship/references/stages/scope.md +8 -0
- package/skills/aimship/references/stages/tech-design.md +11 -1
- package/skills/aimship/references/stages/test-design.md +9 -1
- package/skills/aimship/references/stages/verify.md +6 -0
- package/skills/aimship/references/templates/change-log.md +14 -0
- package/skills/aimship/references/templates/diagnosis.md +39 -0
- package/skills/aimship/references/templates/handoff.md +34 -0
- package/skills/aimship/references/templates/plan.md +35 -0
- package/skills/aimship/references/templates/prd.md +39 -0
- package/skills/aimship/references/templates/review.md +19 -0
- package/skills/aimship/references/templates/scope.md +39 -0
- package/skills/aimship/references/templates/tech-design.md +40 -0
- package/skills/aimship/references/templates/testing.md +35 -0
package/docs/installation.md
CHANGED
|
@@ -1,117 +1,28 @@
|
|
|
1
|
-
# Installation
|
|
1
|
+
# Installation guide moved
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Installation and software lifecycle commands live in the README. The Guide covers using Aimship after installation. This page preserves the old entry points.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
- [English installation instructions](../README.md#install)
|
|
6
|
+
- [中文安装说明](../README.zh-CN.md#install)
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
<a id="installation-layout"></a>
|
|
9
|
+
<a id="registry-installation-after-publication"></a>
|
|
10
|
+
Installation options: [English](../README.md#install) · [中文](../README.zh-CN.md#install). Delivery layout: [English](guide.md#delivery-structure) · [中文](guide.zh-CN.md#delivery-structure).
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
<a id="source-and-local-packages"></a>
|
|
13
|
+
Local package installation: [English](../README.md#install) · [中文](../README.zh-CN.md#install). Source packaging: [Contributing](../CONTRIBUTING.md#isolated-installation-check).
|
|
10
14
|
|
|
11
|
-
|
|
15
|
+
<a id="isolated-trial"></a>
|
|
16
|
+
Isolated installation validation: [Contributing](../CONTRIBUTING.md#isolated-installation-check).
|
|
12
17
|
|
|
13
|
-
|
|
18
|
+
<a id="host-entry-and-reload"></a>
|
|
19
|
+
Host entry and reload: [English](guide.md#hosts) · [中文](guide.zh-CN.md#hosts).
|
|
14
20
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
```
|
|
21
|
+
<a id="add-a-host-inspect-and-update"></a>
|
|
22
|
+
Upgrade, add a host or inspect the installation: [English](../README.md#update) · [中文](../README.zh-CN.md#update).
|
|
18
23
|
|
|
19
|
-
|
|
24
|
+
<a id="disconnect-or-uninstall"></a>
|
|
25
|
+
Disconnect or uninstall: [English](../README.md#uninstall) · [中文](../README.zh-CN.md#uninstall).
|
|
20
26
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
Run these against your real home only when you intend to install. Repeat `--host` to connect multiple hosts in that operation. No login to npm is needed to consume a local tarball; npm still needs a writable cache. The installer reports the installation home, previous/target version, action/result, registered hosts and entry locations. A zero exit status is not proof that a host has loaded the skill.
|
|
26
|
-
|
|
27
|
-
### Isolated trial
|
|
28
|
-
|
|
29
|
-
These commands use one disposable installation and npm cache, not your real configuration. Put the candidate tarball in your current directory first:
|
|
30
|
-
|
|
31
|
-
```sh
|
|
32
|
-
trial=$(mktemp -d)
|
|
33
|
-
trial=$(cd "$trial" && pwd -P)
|
|
34
|
-
npx --yes --offline --cache "$trial/npm-cache" file:./aimship-0.1.0-preview.4.tgz install --home "$trial/home" --host codex
|
|
35
|
-
node "$trial/home/.aimship/bin/aimship.mjs" --version
|
|
36
|
-
node "$trial/home/.aimship/bin/aimship.mjs" status
|
|
37
|
-
node "$trial/home/.aimship/bin/aimship.mjs" install --host cursor
|
|
38
|
-
node "$trial/home/.aimship/bin/aimship.mjs" uninstall --host cursor
|
|
39
|
-
node "$trial/home/.aimship/bin/aimship.mjs" uninstall
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
`--home` redirects only Aimship's managed installation. It does not make the running host discover this home, log in, or inherit permissions. The installed launcher and fixed release CLI default later management operations to their own installation home. An explicit `--home` overrides that target. After inspection, the disposable directory can be removed by its owner; do not substitute a real home for this temporary variable.
|
|
43
|
-
|
|
44
|
-
## Registry installation after publication
|
|
45
|
-
|
|
46
|
-
This candidate has not been published. After the maintainer confirms the package identity and publishes this exact preview, the proposed initial command is:
|
|
47
|
-
|
|
48
|
-
```sh
|
|
49
|
-
npx aimship@preview install --host codex
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
The `preview` tag selects the preview channel. To select an exact version, replace `@preview` with its version number. Confirm the package installation when npm prompts. npm obtains the installer; it copies a complete local release, so subsequent use does not depend on that npm cache. Do not use an unversioned command and assume it selects the preview channel. A missing package/version is a distribution failure, not a successful installation.
|
|
53
|
-
|
|
54
|
-
## Host entry and reload
|
|
55
|
-
|
|
56
|
-
| Host | Managed entry | Explicit use | After install, update or disconnect |
|
|
57
|
-
| --- | --- | --- | --- |
|
|
58
|
-
| Codex | `~/.agents/skills/aimship/` with `agents/openai.yaml` | Select Aimship; CLI `$aimship` | Start a new conversation or restart the CLI, then inspect its actual skill selector. If stale, restart the app. |
|
|
59
|
-
| Cursor | Same `.agents/skills/aimship/` entry | `/aimship` | Start a new agent session; inspect Customize → Skills in the editor. Restart the host if the list remains stale. |
|
|
60
|
-
| Claude Code | `~/.claude/skills/aimship/` | `/aimship` | Start a new session and inspect slash-command discovery; restart the host if stale. |
|
|
61
|
-
|
|
62
|
-
These host integrations are experimental; end-to-end host behavior and cross-host continuation have not yet been verified. Check skill discovery and directory access in your host before starting a delivery. Each host needs its own authentication, model availability and permission to access materials and code. Do not disable permission controls or grant unrelated directories. No cloud synchronization is installed or enabled.
|
|
63
|
-
|
|
64
|
-
Codex and Cursor share the managed entry. Cursor also documents compatibility discovery from Claude/Codex directories. Aimship registers only selected hosts; it cannot guarantee that an unselected compatible host will not display an entry. If two same-name entries appear, inspect their paths and installation version rather than deleting unrelated files. Shared entry presence is distinct from registration, host discovery, login and permission. See the official [Cursor directories](https://cursor.com/docs/skills) and [Codex skill locations](https://learn.chatgpt.com/docs/build-skills).
|
|
65
|
-
|
|
66
|
-
After an update, a running conversation may retain old rules. Save current facts, reload the bound core and current stage at a stable point, and use the existing compatibility/adoption process; start a new conversation if context cannot reliably refresh. Version mismatch stops new workflow writes. Never infer that installation instantly changed an active conversation.
|
|
67
|
-
|
|
68
|
-
## Add a host, inspect and update
|
|
69
|
-
|
|
70
|
-
Reuse the installed core without fetching another package:
|
|
71
|
-
|
|
72
|
-
```sh
|
|
73
|
-
node "$HOME/.aimship/bin/aimship.mjs" install --host cursor
|
|
74
|
-
node "$HOME/.aimship/bin/aimship.mjs" --version
|
|
75
|
-
node "$HOME/.aimship/bin/aimship.mjs" status
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
Repeating the same install is safe for unchanged managed files. Adding a host preserves existing registrations. `status` reads Aimship's known installation files and managed entries; it does not scan projects or query host accounts. It separates registered hosts, missing/modified files, shared entries, pending installation and active version from unverified host capabilities. It is diagnostic, not a semantic delivery validator.
|
|
79
|
-
|
|
80
|
-
To update from a new local release, run that release's `install` command with the same installation home. No `--host` is necessary for an existing installation: registered hosts are retained, and a core whose last host was disconnected remains a core-only installation. After publication, the explicit preview-channel update is:
|
|
81
|
-
|
|
82
|
-
```sh
|
|
83
|
-
npx aimship@preview install
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
There is one installation/update operation: `install`. `update` in the low-level CLI modifies delivery state and is **not** a software updater. The installer reports old and target versions and the result, updates the shared core and selected entries, and preserves configuration, custom content and every delivery. Completed deliveries are not traversed or rewritten. No automatic update runs in the background.
|
|
87
|
-
|
|
88
|
-
## Disconnect or uninstall
|
|
89
|
-
|
|
90
|
-
Disconnect one host, leaving the core and other registrations usable:
|
|
91
|
-
|
|
92
|
-
```sh
|
|
93
|
-
node "$HOME/.aimship/bin/aimship.mjs" uninstall --host cursor
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
If another registered host needs a shared entry, that file is retained and reported. Disconnecting the last host retains the core so version/status and later host connection remain available. A compatible host may still discover an entry used by another host; this command does not change that host's settings.
|
|
97
|
-
|
|
98
|
-
Remove all managed programs and entries:
|
|
99
|
-
|
|
100
|
-
```sh
|
|
101
|
-
node "$HOME/.aimship/bin/aimship.mjs" uninstall
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
Full uninstall retains all delivery materials, user code, custom files and necessary configuration. Modified/unowned files are preserved and listed. No option purges delivery history. The launcher is removed on complete removal; inspect the returned result. Restart existing host sessions to discard cached rules.
|
|
105
|
-
|
|
106
|
-
## Troubleshooting and recovery
|
|
107
|
-
|
|
108
|
-
- **Foreign or modified entry/release:** read the reported path, keep its contents and resolve ownership or merge customizations explicitly. Do not force-overwrite it to obtain a successful exit status.
|
|
109
|
-
- **Partial installation:** `status` can identify pending work. Rerun the exact pending package against the same home after correcting the reported cause. Binding/new workflow writes remain suspended until recovery completes. Do not choose a different version mid-recovery.
|
|
110
|
-
- **Launcher unavailable:** run `node bin/aimship.mjs status --home <installation-home>` from the exact source/package installer. A missing launcher or damaged diagnostic module cannot diagnose itself. `status` reports facts without repairing them; only after inspecting the cause should you rerun the exact installer or reconcile retained files.
|
|
111
|
-
- **Unexpected version:** use the stable launcher for the installed active version. A CLI inside an older release can report a different tool version; reload the current binding before continuing. A source checkout is not automatically an installed release.
|
|
112
|
-
- **Package not found/authentication error:** confirm the exact package/version and public registry. Installation of a public release does not itself prove publish rights. Do not paste credentials into a bug report.
|
|
113
|
-
- **Skill absent or duplicated:** inspect the actual host selector and returned entry paths after reloading. A file check alone cannot diagnose host discovery; retain this as unverified until observed.
|
|
114
|
-
- **Directory denied:** authorize only the needed delivery/code roots in that host. Shared material storage does not share permissions. Report the exact denied operation rather than relocating user materials or changing security settings.
|
|
115
|
-
- **Different host/session:** explicitly resume the same absolute delivery directory and inspect state/current inputs. Do not copy materials, reset the goal or run two writers. See [the user guide](guide.md).
|
|
116
|
-
|
|
117
|
-
The initial data root can be selected with `--data-root` on installation. Existing configuration is retained on later installs; no language setting or second installation mode is introduced. Low-level state/version/recovery contracts are in [the CLI protocol](cli.md).
|
|
27
|
+
<a id="troubleshooting-and-recovery"></a>
|
|
28
|
+
Installation problems: [English](../README.md#installation-troubleshooting) · [中文](../README.zh-CN.md#installation-troubleshooting). Delivery continuation: [English](guide.md#continue) · [中文](guide.zh-CN.md#continue).
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Aimship Delivery Document Reference
|
|
2
|
+
|
|
3
|
+
[中文](reference.zh-CN.md) · [Guide](guide.md) · [README](../README.md)
|
|
4
|
+
|
|
5
|
+
Use this reference to look up document generation conditions, stage outputs and identifier rules. Start with the Guide for everyday use.
|
|
6
|
+
|
|
7
|
+
Use the [annotated directory tree in the Guide](guide.md#delivery-structure) as the layout reference. Formal documents are directly in the request directory, new prototypes in `prototype/`, preserved inputs in `source/`, and machine records/evidence in `.aimship/`. There is no extra `docs/` or `sql/` layer within a delivery.
|
|
8
|
+
|
|
9
|
+
## Choose the requirement authority
|
|
10
|
+
|
|
11
|
+
| Input condition | Requirement document | Prototype handling |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| An authoritative PRD/prototype is supplied for adoption | `scope.md` records adopted versions, scope, gaps, decisions and acceptance; originals remain unchanged | Reuse covered interactions; create a prototype for uncovered interaction changes within the commissioned endpoint |
|
|
14
|
+
| An idea, discussion, screenshots or scattered references | Author `prd.md`, including scope and acceptance | Create a prototype when the commissioned work changes interactions and existing material does not cover them |
|
|
15
|
+
| Technical refactoring or a known-cause fix | Author `prd.md` describing the technical goal, boundaries, behavior constraints and acceptance | No prototype when there is no interaction change |
|
|
16
|
+
| Independent investigation, review-only or verification-only | PRD/Scope defines that commission and its endpoint | Do not invent a future implementation or prototype |
|
|
17
|
+
|
|
18
|
+
`prd.md` and `scope.md` are alternatives for the requirement authority, not duplicate descriptions to create together. If you explicitly commission maintenance of an existing editable authoritative PRD and it fully expresses this delivery's scope, authorization and endpoint, register and reuse it. Earlier authorship alone does not make adopted material editable.
|
|
19
|
+
|
|
20
|
+
Preserve incoming versions in `source/`, including externally supplied PRDs, prototypes and images. Generated formal documents stay in the locations above. External originals are not rewritten when requirements change. A URL alone may not preserve a version; the agent records the available source/version and any access gaps. Reading text, inspecting a visual, interacting with a prototype and editing its original format are distinct capabilities.
|
|
21
|
+
|
|
22
|
+
## Stage outputs and content
|
|
23
|
+
|
|
24
|
+
Stage names are stable identifiers. This table is a catalog, not a fixed sequence; the accepted arrangement selects stages and dependencies. Every delivery retains the original request, a requirement/scope authority and state records, even when no separate requirements stage is needed. Sharing `plan.md` or `testing.md` does not complete both stages automatically: each stage retains its own status and result. Documents are drafted when their stage begins, and file creation alone does not establish completion.
|
|
25
|
+
|
|
26
|
+
| Stage | When used | Output and content |
|
|
27
|
+
| --- | --- | --- |
|
|
28
|
+
| `frame` | The problem or intended result needs clarification | Goal/background section of PRD/Scope: problem, outcome, non-goals and assumptions |
|
|
29
|
+
| `requirements` | Requirements need authoring or adoption | `prd.md`: goals, scope, requirements, states, acceptance and arrangement; or `scope.md`: source adoption, scope, exclusions, decisions, acceptance and arrangement |
|
|
30
|
+
| `scope` | Commission boundaries need explicit resolution | Update PRD scope or Scope goals/boundaries; no second requirements document |
|
|
31
|
+
| `prototype` | Required interaction changes are not covered by existing material | New HTML at `prototype/index.html` with related assets; other agreed formats or reused entries are registered |
|
|
32
|
+
| `diagnose` | An independent investigation is explicitly arranged | `diagnosis.md`: goal, symptoms, environment, evidence, causes, unknowns and conclusion |
|
|
33
|
+
| `tech-design` | Technical solution needs designing | `tech-design.md`: basis, architecture, modules, data/contracts, errors, risks and requirement coverage; SQL is produced and reviewed in contract after this design is approved |
|
|
34
|
+
| `contract` | Interfaces or database changes need formal artifacts | HTTP: `openapi.yaml` or the registered authority; agreed non-HTTP format; independent SQL per target, reviewed individually |
|
|
35
|
+
| `test-design` | Test cases need authoring | `testing.md`: basis, environment, cases/assertions and requirement/acceptance coverage |
|
|
36
|
+
| `plan` | Before any implementation, including one small batch | `plan.md`: basis, batches, tasks, validation approach, execution section and remaining work |
|
|
37
|
+
| `implement` | Execute the accepted plan | Code in its repositories; actual task status, objects and deviations in the same `plan.md` |
|
|
38
|
+
| `review` | Review a batch, the integrated result or a review-only commission | `review.md`: review basis, each review's scope/findings/resolution and conclusion |
|
|
39
|
+
| `verify` | Execute versioned cases after the required review, or a verification-only commission | Append execution batches/RUNs, actual results/evidence, retests and conclusion to the same `testing.md` |
|
|
40
|
+
| `handoff` | Deliver the agreed endpoint | `handoff.md`: goal, artifacts, usage, review/verification results, limits and completion/acceptance/integration outcomes |
|
|
41
|
+
|
|
42
|
+
During development, test-design precedes plan; each document follows the [review flow](guide.md#review-documents-and-continue). `plan` completes before the first implementation. Each implementation batch is reviewed, followed by overall review and independent verification. Test code can be authored during implementation; there is no full testing gate imposed after each batch. For one batch, one review may cover both batch and overall scope if explicitly recorded. Verification-only work may adopt existing versioned cases without claiming to have executed `test-design`.
|
|
43
|
+
|
|
44
|
+
Ordinary debugging and known-cause bugs do not add `diagnose`. Fix implementation defects in implementation, design errors in technical design, requirement gaps in PRD/Scope, and incorrect expectations in versioned cases. A major unknown requiring separate investigation needs a recorded stage adjustment; dependent work waits.
|
|
45
|
+
|
|
46
|
+
## SQL, changes and progress
|
|
47
|
+
|
|
48
|
+
| Artifact | Generation rule and content |
|
|
49
|
+
| --- | --- |
|
|
50
|
+
| `database.sql` | Required when database structures or data change; one database target uses this single root-level file |
|
|
51
|
+
| `database-<id>.sql` | Multiple independent database targets get one file each; use a stable target ID |
|
|
52
|
+
| `change-log.md` | Record meaningful changes: reason, old/new meaning, decision, affected documents/work and remaining actions |
|
|
53
|
+
| `status.md` | Generated reading entry: goal, progress, blockers, next action and document links |
|
|
54
|
+
| `.aimship/state.json` | Managed state: goal/stages, document references, version binding and associated repositories/worktrees/code locations |
|
|
55
|
+
|
|
56
|
+
SQL identifies the target, dialect, version/schema basis and execution conditions, and links ordered statements to requirements/design. It is prepared and individually reviewed in contract after technical design approval and before test cases. A Markdown SQL snippet or migration reference does not replace this deliverable. Generating SQL does not authorize executing it or changing a repository's migration mechanism.
|
|
57
|
+
|
|
58
|
+
Existing engineering HTTP contracts remain authoritative; avoid a second competing copy. Project current and target versions are recorded in PRD/Scope with their engineering source and observation basis; an unspecified version remains unspecified. `plan.md` records actual changes, while review and test results identify the exact commit/build or uncommitted content. Each repository has its own version and object references. These differ from Aimship’s rules version, state schema and save revision. The handoff records final code objects and actual merge/release status; a target version does not establish publication.
|
|
59
|
+
|
|
60
|
+
## Fixed outlines and traceable identifiers
|
|
61
|
+
|
|
62
|
+
New documents follow [shared templates and rules](../skills/aimship/references/document-patterns.md). Section order and stable anchors are retained; visible headings can be translated. Inapplicable sections explain why. Short tasks use the same outline, with shorter content. Unselected stages do not produce empty documents, and there is no arbitrary small-task merging.
|
|
63
|
+
|
|
64
|
+
| Identifier | Meaning |
|
|
65
|
+
| --- | --- |
|
|
66
|
+
| `REQ-001`, `AC-001` | Requirement and acceptance criterion |
|
|
67
|
+
| `B-001`, `TASK-001` | Implementation batch and task |
|
|
68
|
+
| `PAGE-001`, `Q-001` | Prototype page and tracked question |
|
|
69
|
+
| `TC-001`, `RUN-001` | Test case and actual verification run |
|
|
70
|
+
| `REV-001`, `CHG-001` | Review and meaningful change |
|
|
71
|
+
|
|
72
|
+
Each series starts at `001` and advances beyond its historical maximum, including retired items; three digits are the minimum width. An ID is never reused for a different obligation. Within a namespace and version, an identity has one effective definition; other documents reference it. Classification or layout changes retain the ID, while splits/merges create new IDs linked to the originals.
|
|
73
|
+
|
|
74
|
+
References connect requirements/acceptance to design, tasks, code objects, reviews, case versions and actual runs. These relationships may be many-to-many: `REQ-001` and `TC-001` are not related merely because their numbers match. Shared rules, permissions and quality requirements receive independent identities when independently testable. External IDs retain their spelling and source/version namespace; sources without IDs receive local mappings to their original locations. Changed case meaning gets a new version; earlier failures and their case/code versions remain traceable.
|
|
75
|
+
|
|
76
|
+
For example: `REQ-001 → AC-002 → TC-004@1 → RUN-003` records the explicit requirement-to-result relationship. A RUN also identifies the tested code/build and evidence; the case ID alone does not prove execution.
|
|
77
|
+
|
|
78
|
+
The tools check structure, references and basic coverage. The agent must still assess whether the content and assertions actually satisfy the requirement; valid metadata alone is not proof.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Aimship 交付文档参考
|
|
2
|
+
|
|
3
|
+
[English](reference.md) · [使用指南](guide.zh-CN.md) · [README](../README.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
本文供需要查阅文档生成条件、阶段产物及编号规则时使用;日常操作请先阅读使用指南。
|
|
6
|
+
|
|
7
|
+
目录位置以 [使用指南的注释目录树](guide.zh-CN.md#delivery-structure)为准:正式文档直接放在需求目录,新原型放在 `prototype/`,原始资料放在 `source/`,内部状态和证据放在 `.aimship/`。交付内不再增加 `docs/` 或 `sql/` 层级。
|
|
8
|
+
|
|
9
|
+
## 需求依据与输入条件
|
|
10
|
+
|
|
11
|
+
| 输入条件 | 需求文档 | 原型处理 |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| 提供权威 PRD/原型供本次采用 | 用 `scope.md` 记录采用版本、本次范围、缺口、决定和验收,保留原件 | 已覆盖的交互复用;委托范围内有未覆盖的交互变化时补原型 |
|
|
14
|
+
| 只有想法、讨论、截图或零散参考 | 自编 `prd.md`,其中包含范围和验收 | 委托范围内涉及交互变化、且现有材料未覆盖时生成原型 |
|
|
15
|
+
| 技术重构或已知原因的修复 | 自编 `prd.md`,说明技术目标、边界、行为约束和验收 | 不涉及交互变化就不生成原型 |
|
|
16
|
+
| 独立调查、只做 review 或只做验证 | PRD/Scope 只定义本次委托及终点 | 不虚构后续实施需求或原型 |
|
|
17
|
+
|
|
18
|
+
`prd.md` 和 `scope.md` 是需求依据的两种选择,不应默认同时创建两份重复描述。如果你明确委托维护一份已有、可编辑的权威 PRD,且它完整表达本次范围、授权和终点,可以登记复用。仅凭“以前由我们编写”不能认定采用的原件可以修改。
|
|
19
|
+
|
|
20
|
+
外来的 PRD、原型、图片等按采用版本保留在 `source/`,自己生成的正式文档按上述位置存放。需求变化时不改写外部原件。单独一个链接未必能保留资料版本,Agent 应记录可用来源、版本和访问缺口。提取文本、查看视觉内容、操作原型和编辑原格式,是不同能力。
|
|
21
|
+
|
|
22
|
+
## 阶段产物与内容
|
|
23
|
+
|
|
24
|
+
阶段名称是稳定标识。下表是阶段目录,不是强制顺序;实际选用哪些阶段及其依赖,由接受后的执行安排决定。即使无需单独执行需求阶段,每次交付仍保留原始委托、需求/范围依据和状态记录。共用 `plan.md` 或 `testing.md` 不代表两个阶段同时完成,各阶段分别记录状态和结果。文档在对应阶段开始时形成草稿,文件已创建不等于阶段已完成。
|
|
25
|
+
|
|
26
|
+
| 阶段 | 使用条件 | 产物及内容 |
|
|
27
|
+
| --- | --- | --- |
|
|
28
|
+
| `frame` 问题定义 | 问题或预期结果仍需澄清 | PRD/Scope 的目标与背景章节:问题、目标、非目标和假设 |
|
|
29
|
+
| `requirements` 需求 | 需要编写或采用需求 | `prd.md`:目标、范围、需求、状态、验收和执行安排;或 `scope.md`:资料采用、本次范围、排除项、决定、验收和执行安排 |
|
|
30
|
+
| `scope` 范围确认 | 需要明确本次委托边界 | 更新 PRD 的范围或 Scope 的目标/边界,不再新建另一份需求说明 |
|
|
31
|
+
| `prototype` 原型 | 所需交互变化未被现有资料覆盖 | 新 HTML 使用 `prototype/index.html`,相关资源放在同目录;其他约定格式或复用入口单独登记 |
|
|
32
|
+
| `diagnose` 调查定位 | 明确安排了独立调查 | `diagnosis.md`:目标、现象、环境、证据、原因、未知项和结论 |
|
|
33
|
+
| `tech-design` 技术方案 | 需要设计技术实现 | `tech-design.md`:依据、架构、模块、数据/契约、异常、风险和需求覆盖;通过后在 contract 生成并审核所需 SQL |
|
|
34
|
+
| `contract` 契约及 SQL | 接口或数据库变化需要正式产物 | HTTP 使用 `openapi.yaml` 或已登记的工程权威契约;非 HTTP 使用约定格式;每个目标库的独立 SQL 逐份审核 |
|
|
35
|
+
| `test-design` 测试设计 | 需要编写测试用例 | `testing.md`:依据、环境、用例/断言,以及需求和验收覆盖 |
|
|
36
|
+
| `plan` 实施计划 | 任何实施开始前,包括单个小批次 | `plan.md`:依据、批次、任务、验证安排、执行记录章节和剩余工作 |
|
|
37
|
+
| `implement` 实施 | 执行已确定的计划 | 在代码仓库实施;同一份 `plan.md` 记录实际任务状态、代码对象和偏差 |
|
|
38
|
+
| `review` 审核 | 审核某批、整体成果,或单独受托 review | `review.md`:审核依据、各次审核范围、发现与处理、结论 |
|
|
39
|
+
| `verify` 验证 | 必要 review 后执行带版本用例,或单独受托验证 | 在同一份 `testing.md` 追加执行批次/RUN、实际结果、证据、重测和结论 |
|
|
40
|
+
| `handoff` 交接 | 交付约定终点的成果 | `handoff.md`:目标、产物、使用方式、审核/验证结果、限制,以及完成/验收/集成状态 |
|
|
41
|
+
|
|
42
|
+
开发流程中,test-design 先于 plan;文档按[审核流程](guide.zh-CN.md#逐份审核与自动衔接)处理。`plan` 必须先于首次实施完成。每批实施后 review,全部完成后整体 review,再独立验证。实施时可以编写测试代码,但不额外设置逐批全量测试门禁。只有一批时,一次 review 可以明确覆盖批次和整体范围。单独委托验证时,可以采用已有、明确版本的用例,不声称重新执行过 `test-design`。
|
|
43
|
+
|
|
44
|
+
普通开发调试、已知原因的 Bug 不增加 `diagnose`。实现错误回到实施,设计错误回到技术方案,需求缺口回到 PRD/Scope,错误的测试预期回到带版本的用例。出现需要独立调查的重大未知项时,应记录阶段调整,相关依赖工作暂停。
|
|
45
|
+
|
|
46
|
+
## SQL、变更和进度
|
|
47
|
+
|
|
48
|
+
| 产物 | 生成规则与内容 |
|
|
49
|
+
| --- | --- |
|
|
50
|
+
| `database.sql` | 数据库结构或数据发生变化时需要;一个数据库目标在需求根目录使用这一份文件 |
|
|
51
|
+
| `database-<id>.sql` | 多个独立数据库目标各一份,`id` 使用稳定的目标标识 |
|
|
52
|
+
| `change-log.md` | 有重要变更时记录原因、旧/新含义、决定、影响文档与工作,以及剩余动作 |
|
|
53
|
+
| `status.md` | 自动生成的阅读入口,显示目标、进度、阻塞、下一步和文档链接 |
|
|
54
|
+
| `.aimship/state.json` | 工具维护的状态,包含目标/阶段、文档引用、版本绑定及关联仓库/worktree/代码位置 |
|
|
55
|
+
|
|
56
|
+
SQL 标明目标、方言、版本/结构依据、执行条件,按顺序组织语句并关联需求和设计,在技术方案通过后的 contract 阶段、测试用例之前准备并逐份审核。Markdown 中的 SQL 片段或迁移文件引用不能替代它。生成 SQL 不代表获准执行数据库操作或修改工程迁移机制。
|
|
57
|
+
|
|
58
|
+
已有工程 HTTP 契约继续作为权威来源,避免维护另一份相互冲突的副本。项目当前版本和目标版本在 PRD/Scope 中记录,并注明工程来源和观察依据;未设置版本时如实说明。`plan.md` 记录实际改动,审核与测试结果定位准确的 commit、构建或未提交内容。多仓库分别记录版本和对象引用,与 Aimship 规则版本、状态格式及保存 revision 区分。交接记录最终代码对象和真实合并/发布状态,目标版本不代表已发布版本。
|
|
59
|
+
|
|
60
|
+
## 固定大纲和可追溯编号
|
|
61
|
+
|
|
62
|
+
新文档遵循[统一模板和规则](../skills/aimship/references/document-patterns.md),保留章节顺序和稳定锚点,可翻译展示标题。不适用章节说明原因,小需求使用相同大纲、缩短内容。未选择的阶段不生成空文档,也不按“小需求”随意合并文件。
|
|
63
|
+
|
|
64
|
+
| 编号 | 含义 |
|
|
65
|
+
| --- | --- |
|
|
66
|
+
| `REQ-001`、`AC-001` | 需求、验收标准 |
|
|
67
|
+
| `B-001`、`TASK-001` | 实施批次、任务 |
|
|
68
|
+
| `PAGE-001`、`Q-001` | 原型页面、待决问题 |
|
|
69
|
+
| `TC-001`、`RUN-001` | 测试用例、实际验证执行 |
|
|
70
|
+
| `REV-001`、`CHG-001` | 审核、重要变更 |
|
|
71
|
+
|
|
72
|
+
各类编号从 `001` 开始,在包含已废弃条目的历史最大值后递增,三位是最小补零宽度。旧编号不用于另一项义务。同一命名空间和适用版本内,一个编号只有一处有效定义,其他文档引用该定义。分类或排版变化保留编号,拆分/合并则创建新编号并关联原编号。
|
|
73
|
+
|
|
74
|
+
需求/验收通过引用关联到设计、任务、代码对象、审核、用例版本和实际执行,允许多对多关联;不能因数字相同就认定 `REQ-001` 对应 `TC-001`。可独立测试的公共规则、权限和质量要求应具有独立身份。外部编号保留原样,并携带来源/版本命名空间;无原编号时建立指向原文位置的本地映射。用例含义改变后产生新版本,先前失败及当时的用例和代码版本仍可追溯。
|
|
75
|
+
|
|
76
|
+
例如,`REQ-001 → AC-002 → TC-004@1 → RUN-003` 表示明确的需求到结果关联。RUN 同时记录被测代码/构建及证据,仅存在用例编号不能证明已执行。
|
|
77
|
+
|
|
78
|
+
工具检查结构、引用和基础覆盖;Agent 仍需判断正文和断言是否真正满足需求。元信息合法不等于内容正确。
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
2
|
+
import { artifactMap, safeRead, anchors } from './documents.mjs';
|
|
3
|
+
import { fail, hash } from './io.mjs';
|
|
4
|
+
|
|
5
|
+
const text = value => typeof value === 'string' && value.trim().length > 0;
|
|
6
|
+
const reject = message => fail('REVIEW_CHECK', message, 'Read review status. Preserve history, self-check the current document, and record only an actual explicit user decision.');
|
|
7
|
+
const kinds = ['self-check', 'approved', 'changes-requested', 'editorial'];
|
|
8
|
+
export function validateReviewState(state) {
|
|
9
|
+
if (state.reviewPolicy === undefined) return;
|
|
10
|
+
if (state.reviewPolicy !== 1 || !Array.isArray(state.reviewHistory)) reject('Unsupported review policy or missing reviewHistory.');
|
|
11
|
+
const ids = new Set();
|
|
12
|
+
for (const r of state.reviewHistory) {
|
|
13
|
+
if (!r || !text(r.id) || ids.has(r.id) || !kinds.includes(r.kind) || !text(r.document) || !text(r.version) || !/^[a-f0-9]{64}$/.test(r.digest ?? '') || !text(r.sourceRef) || !text(r.summary)) reject('Review records need unique ID, kind, document, version, digest, sourceRef and summary.');
|
|
14
|
+
ids.add(r.id);
|
|
15
|
+
if (r.kind === 'self-check' && !['passed','failed'].includes(r.result)) reject('Self-check records must distinguish passed from failed.');
|
|
16
|
+
if (r.kind === 'approved' && r.decision !== 'explicit-approval') reject('Approval requires an explicit user approval decision; questions, silence and requests to see results are not approval.');
|
|
17
|
+
if (r.kind === 'editorial' && !/^[a-f0-9]{64}$/.test(r.previousDigest ?? '')) reject('Editorial reconciliation needs the previous approved digest.');
|
|
18
|
+
}
|
|
19
|
+
for (const [index, run] of state.stageRuns.entries()) {
|
|
20
|
+
if (run.dependsOn !== undefined && (!Array.isArray(run.dependsOn) || new Set(run.dependsOn).size !== run.dependsOn.length || !run.dependsOn.every(id => state.stageRuns.slice(0, index).some(r => r.id === id)))) reject(`${run.id}: dependencies must be unique preceding run IDs.`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// Shared execution/result sections are outside the approved design surface.
|
|
24
|
+
// Reused formats have no inferred boundary: the entire pinned file is reviewed.
|
|
25
|
+
export async function reviewFingerprint(directory, state, doc) {
|
|
26
|
+
const stop = doc.mode === 'new' ? { plan: 'execution', testing: 'runs' }[doc.kind] : null;
|
|
27
|
+
let body = await safeRead(directory, state, doc.path, stop ? 'utf8' : null);
|
|
28
|
+
if (body === null) return null;
|
|
29
|
+
if (stop) {
|
|
30
|
+
const at = body.search(new RegExp(`<a\\s+id=["']${stop}["']\\s*><\\/a>`));
|
|
31
|
+
if (at < 0) return null;
|
|
32
|
+
body = body.slice(0, at);
|
|
33
|
+
}
|
|
34
|
+
const assets = [];
|
|
35
|
+
for (const ref of doc.assetRefs ?? []) { const bytes = await safeRead(directory,state,ref,null); if (bytes === null) return null; assets.push([ref,hash(bytes)]); }
|
|
36
|
+
return hash(assets.length ? JSON.stringify([hash(body),assets]) : body);
|
|
37
|
+
}
|
|
38
|
+
function decisionFor(state, doc, digest) {
|
|
39
|
+
let approved = null, checked = null, latest = null;
|
|
40
|
+
for (const r of state.reviewHistory ?? []) {
|
|
41
|
+
if (r.document !== doc.path) continue;
|
|
42
|
+
if (r.kind === 'changes-requested') { approved = null; checked = null; }
|
|
43
|
+
if (['approved','editorial'].includes(r.kind)) approved = r;
|
|
44
|
+
if (r.version === doc.version && r.digest === digest) {
|
|
45
|
+
latest = r;
|
|
46
|
+
if (r.kind === 'self-check') { checked = r.result === 'passed' ? r : null; if (r.result === 'failed') approved = null; }
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (approved?.version !== doc.version || approved?.digest !== digest) approved = null;
|
|
50
|
+
return { approved, checked, latest };
|
|
51
|
+
}
|
|
52
|
+
export async function reviewStatus(directory, state) {
|
|
53
|
+
if (state.reviewPolicy !== 1) return { compatibility: 'review-adoption-required', canProceed: false, pending: [], runs: [], documents: [], instruction: 'Preserve old results. Enable reviewPolicy: 1 with an empty reviewHistory after compatibility assessment; no historical approval is inferred.' };
|
|
54
|
+
const map = await artifactMap(), documents = [], evidenceIssues = [], evidenceCache = new Map();
|
|
55
|
+
for (const r of state.reviewHistory ?? []) {
|
|
56
|
+
if (!evidenceCache.has(r.sourceRef)) { try { evidenceCache.set(r.sourceRef,await evidence(directory,state,r.sourceRef)); } catch { evidenceCache.set(r.sourceRef,null); } }
|
|
57
|
+
if (!r.sourceDigest || evidenceCache.get(r.sourceRef) !== r.sourceDigest) evidenceIssues.push(r.id);
|
|
58
|
+
}
|
|
59
|
+
for (const doc of state.documents ?? []) {
|
|
60
|
+
if (['source','change-log','review','handoff'].includes(doc.kind)) continue;
|
|
61
|
+
let digest = null;
|
|
62
|
+
try { digest = await reviewFingerprint(directory, state, doc); } catch { /* Missing input remains blocked. */ }
|
|
63
|
+
const { approved, checked, latest } = decisionFor(state, doc, digest);
|
|
64
|
+
documents.push({ path: doc.path, version: doc.version, digest, selfCheck: checked?.id ?? null, approval: approved?.id ?? null, status: !digest ? 'unavailable' : approved && (evidenceIssues.includes(approved.id) || (checked && evidenceIssues.includes(checked.id))) ? 'evidence-changed' : approved ? 'approved' : latest?.kind === 'changes-requested' ? 'changes-requested' : latest?.kind === 'self-check' && latest.result === 'failed' ? 'self-check-failed' : checked ? 'pending' : 'self-check-required' });
|
|
65
|
+
}
|
|
66
|
+
const runs = [], requirement = state.documentPolicy?.authority === 'adopted' ? 'scope' : 'prd';
|
|
67
|
+
for (const [i, run] of state.stageRuns.entries()) {
|
|
68
|
+
const spec = map.stages[run.stage];
|
|
69
|
+
let required = [];
|
|
70
|
+
if (spec?.humanReview) {
|
|
71
|
+
const kind = spec.kind === 'requirement' ? requirement : spec.kind;
|
|
72
|
+
required = (state.documents ?? []).filter(d => d.kind === kind || (run.stage === 'contract' && d.kind === 'sql')).map(d => d.path);
|
|
73
|
+
}
|
|
74
|
+
const declared = run.dependsOn ?? (i ? [state.stageRuns[i - 1].id] : []);
|
|
75
|
+
const producers = state.stageRuns.slice(0, i).filter(r => r.outputRefs.some(out => run.inputRefs.some(input => out.split('#')[0] === input.split('#')[0]))).map(r => r.id);
|
|
76
|
+
const development = ['requirements','scope','tech-design','contract','test-design','plan','implement'];
|
|
77
|
+
const prerequisites = development.includes(run.stage) ? state.stageRuns.slice(0,i).filter(r=>development.includes(r.stage) && development.indexOf(r.stage)<development.indexOf(run.stage)).map(r=>r.id) : [];
|
|
78
|
+
const dependsOn = [...new Set([...declared, ...producers, ...prerequisites])];
|
|
79
|
+
const blockedBy = dependsOn.filter(id => !runs.find(r => r.id === id)?.ready);
|
|
80
|
+
const waiting = required.filter(file => documents.find(d => d.path === file)?.status !== 'approved');
|
|
81
|
+
const ready = run.progress === 'done' && !['needs-review','unknown'].includes(run.applicability) && !blockedBy.length && !waiting.length && (!spec?.humanReview || required.length > 0);
|
|
82
|
+
runs.push({ id: run.id, stage: run.stage, progress: run.progress, applicability: run.applicability, documents: required, waiting, dependsOn, blockedBy, ready });
|
|
83
|
+
}
|
|
84
|
+
const current = runs.find(r => r.id === state.currentRun);
|
|
85
|
+
const pending = runs.filter(r => r.progress !== 'planned').flatMap(r => r.waiting.map(document => ({ run: r.id, document, status: documents.find(d => d.path === document)?.status })));
|
|
86
|
+
const nextRun = runs.find(r => !r.ready);
|
|
87
|
+
const nextStage = current ? runs[runs.indexOf(current) + 1]?.stage ?? null : nextRun?.stage ?? null;
|
|
88
|
+
return { compatibility: 'current', documents, runs, pending, evidenceIssues, currentRun: current?.id ?? null, nextRun: nextRun?.id ?? null, nextStage, canProceed: current ? !current.blockedBy.length && !(current.progress === 'done' && !current.ready) && current.progress !== 'blocked' && !['needs-review','unknown'].includes(current.applicability) : !pending.length && !runs.some(r=>r.progress==='blocked' || ['needs-review','unknown'].includes(r.applicability)), identity: 'Record validation only; user identity, intent and source authenticity are not authenticated.' };
|
|
89
|
+
}
|
|
90
|
+
async function evidence(directory, state, ref) {
|
|
91
|
+
if (!text(ref) || ref.includes('\\') || ref.split('#')[0].split('/').includes('..') || ref.startsWith('/') || (/^[a-zA-Z]:/.test(ref) && !ref.startsWith('repo:'))) reject('Review evidence must stay inside a registered root.');
|
|
92
|
+
let body;
|
|
93
|
+
try { body = await safeRead(directory, state, ref); } catch { reject(`Cannot read review evidence ${ref}.`); }
|
|
94
|
+
if (!text(body)) reject(`Preserve locally readable review evidence for ${ref}.`);
|
|
95
|
+
const fragment = ref.split('#')[1];
|
|
96
|
+
if (fragment && !anchors(body).includes(fragment)) reject(`Review evidence anchor not found: ${ref}.`);
|
|
97
|
+
return hash(body);
|
|
98
|
+
}
|
|
99
|
+
export async function enforceReviews(directory, state, previous) {
|
|
100
|
+
validateReviewState(state);
|
|
101
|
+
if (state.reviewPolicy !== 1) reject('Adopt the review protocol before workflow updates; old results remain readable but are not approvals.');
|
|
102
|
+
const oldHistory = previous?.reviewHistory ?? [];
|
|
103
|
+
for (const old of previous?.documents ?? []) if (oldHistory.some(r=>r.document===old.path) && !state.documents.some(d=>d.path===old.path && d.kind===old.kind)) reject(`Preserve reviewed document registration ${old.path}.`);
|
|
104
|
+
if (state.reviewHistory.length < oldHistory.length || !oldHistory.every((r, i) => isDeepStrictEqual(r, state.reviewHistory[i]))) reject('Preserve review history unchanged and in order.');
|
|
105
|
+
const prefix = { ...state, reviewHistory: [...oldHistory] };
|
|
106
|
+
for (const r of state.reviewHistory.slice(oldHistory.length)) {
|
|
107
|
+
const doc = state.documents.find(d => d.path === r.document);
|
|
108
|
+
if (!doc || r.version !== doc.version || r.digest !== await reviewFingerprint(directory, state, doc)) reject(`${r.id}: review must bind the current registered document version and content.`);
|
|
109
|
+
const prior = decisionFor(prefix, doc, r.digest);
|
|
110
|
+
if (r.kind === 'approved' && !prior.checked) reject(`${r.id}: self-check the same content before recording approval.`);
|
|
111
|
+
if (r.kind === 'editorial' && (!prior.checked || !decisionFor(prefix, doc, r.previousDigest).approved)) reject(`${r.id}: editorial reconciliation needs a self-check and the previous approval at the same version.`);
|
|
112
|
+
r.sourceDigest = await evidence(directory, state, r.sourceRef);
|
|
113
|
+
prefix.reviewHistory.push(r);
|
|
114
|
+
}
|
|
115
|
+
// Only the initial selected development sequence is ordered; later rework can recur.
|
|
116
|
+
const order = ['requirements','scope','tech-design','contract','test-design','plan','implement'];
|
|
117
|
+
const first = stage => state.stageRuns.findIndex(r => r.stage === stage);
|
|
118
|
+
for (let i = 0; i < order.length; i++) for (let j = i + 1; j < order.length; j++) {
|
|
119
|
+
const a = first(order[i]), b = first(order[j]);
|
|
120
|
+
if (order[i] === 'requirements' && order[j] === 'scope') continue;
|
|
121
|
+
if (a >= 0 && b >= 0 && a > b && !previous?.stageRuns.some(r => r.id === state.stageRuns[b].id && r.progress !== 'planned')) reject(`${order[i]} must precede ${order[j]} in the initial development arrangement.`);
|
|
122
|
+
}
|
|
123
|
+
const restoring = new Set(state.stageRuns.filter(r=>r.applicability === 'current' && previous?.stageRuns.some(old=>old.id===r.id && old.applicability==='needs-review')).map(r=>r.id));
|
|
124
|
+
let status = await reviewStatus(directory, state);
|
|
125
|
+
// Invalidate only registered producer/dependency paths. Independent branches retain their facts.
|
|
126
|
+
const affected = new Set();
|
|
127
|
+
for (const doc of state.documents) {
|
|
128
|
+
const oldDoc = previous?.documents?.find(d => d.path === doc.path);
|
|
129
|
+
const latest = oldHistory.filter(r => r.document === doc.path && ['approved','editorial'].includes(r.kind)).at(-1);
|
|
130
|
+
const now = status.documents.find(d => d.path === doc.path);
|
|
131
|
+
if (!oldDoc || !latest || !now) continue;
|
|
132
|
+
const changed = latest.digest !== now.digest || latest.version !== doc.version;
|
|
133
|
+
const editorial = state.reviewHistory.slice(oldHistory.length).some(r=>r.document===doc.path && r.kind==='editorial' && r.digest===now.digest);
|
|
134
|
+
const requested = state.reviewHistory.slice(oldHistory.length).some(r=>r.document===doc.path && (r.kind==='changes-requested' || (r.kind==='self-check' && r.result==='failed')));
|
|
135
|
+
if ((!changed && !requested) || editorial) continue;
|
|
136
|
+
if (changed && !state.openChanges.some(c => c.status === 'open')) reject(`${doc.path}: record the material change and impact in an open CHG, or self-check an editorial reconciliation.`);
|
|
137
|
+
if (changed && now.status === 'approved' && doc.version === latest.version) reject(`${doc.path}: a material revision needs a new document version; use editorial only for unchanged meaning.`);
|
|
138
|
+
for (const r of status.runs.filter(r => r.documents.includes(doc.path) && r.progress !== 'planned')) affected.add(r.id);
|
|
139
|
+
}
|
|
140
|
+
for (const r of status.runs) if (r.dependsOn.some(id => affected.has(id))) affected.add(r.id);
|
|
141
|
+
for (const r of state.stageRuns) if (affected.has(r.id) && r.progress !== 'planned') r.applicability = 'needs-review';
|
|
142
|
+
status = await reviewStatus(directory, state);
|
|
143
|
+
const previousStatus = previous?.reviewPolicy === 1 ? await reviewStatus(directory, previous) : null;
|
|
144
|
+
for (const run of state.stageRuns) {
|
|
145
|
+
const old = previous?.stageRuns.find(r => r.id === run.id);
|
|
146
|
+
const progressing = ['active','done'].includes(run.progress) && (!old || old.progress !== run.progress || old.resultRef !== run.resultRef || restoring.has(run.id));
|
|
147
|
+
if (restoring.has(run.id) && run.applicability === 'needs-review') reject(`${run.id}: current document or dependency still needs review.`);
|
|
148
|
+
if (progressing && status.runs.find(r => r.id === run.id).blockedBy.length) reject(`${run.id}: prerequisite completion, approval or affected dependency is unresolved.`);
|
|
149
|
+
if (old && old.progress !== 'planned' && previousStatus && !previousStatus.runs.find(r=>r.id===old.id).dependsOn.every(id=>status.runs.find(r=>r.id===run.id).dependsOn.includes(id))) reject(`${run.id}: preserve started dependencies; record revised work as a new run.`);
|
|
150
|
+
}
|
|
151
|
+
if (state.outcome.goal === 'met' && previous?.outcome.goal !== 'met' && status.runs.some(r => !r.ready)) reject('Resolve selected stages and document approvals before declaring the goal met.');
|
|
152
|
+
return status;
|
|
153
|
+
}
|
package/lib/cli.mjs
CHANGED
|
@@ -1,19 +1,24 @@
|
|
|
1
|
+
import { migrateState, scanDeliveries } from './migrate.mjs';
|
|
2
|
+
import { inspectDocuments } from './documents.mjs';
|
|
1
3
|
import fs from 'node:fs/promises';
|
|
2
4
|
import path from 'node:path';
|
|
3
5
|
import os from 'node:os';
|
|
4
6
|
import { exists, fail, noSymlinks, readJSON } from './io.mjs';
|
|
5
7
|
import { install, installPaths, installationStatus, installationVersion, readConfig, uninstall } from './install.mjs';
|
|
6
|
-
import { SCHEMA_VERSION, STAGES, assertInstallationComplete, assertRules, checkReferences, initState, readState, recoverState, updateState, validateState } from './state.mjs';
|
|
8
|
+
import { SCHEMA_VERSION, STAGES, renderState, assertInstallationComplete, assertRules, checkReferences, initState, readState, recoverState, updateState, validateState } from './state.mjs';
|
|
7
9
|
|
|
8
10
|
const HELP = {
|
|
9
11
|
usage: 'node <aimship.mjs> <command> [delivery-directory] [options]',
|
|
10
12
|
commands: {
|
|
13
|
+
render: 'render <dir> --revision <N> --rules-version <version> [--preserve-edits]. Explicitly rebuild status.md; preserve manual edits before replacement. Does not replay a state update.',
|
|
14
|
+
migrate: 'migrate <dir> --revision <old-N> --rules-version <active-version> --input <json-file|->. Input {compatibilityRef}; preserve legacy state then move to schema 2. Adopt rules separately; documents require reviewed reconciliation.',
|
|
15
|
+
scan: 'scan <material-root>. Read immediate delivery directories in both layouts, deduplicate IDs and report conflicts/unknown records. No global search or write.',
|
|
11
16
|
status: 'status [--home <dir>]. Read only Aimship-managed paths and fixed entries. Report registration, shared entry presence/integrity, active version, pending work and config problems. Host loading, login and access permissions remain unverified; no directories are created.',
|
|
12
17
|
bind: 'Resolve active installation once. Returns immutable release/core/stage/CLI paths plus configured dataRoot, configPath and customPath. Source mode returns the default data root with null config/custom paths. No directories are created. Optional --home is for isolated testing.',
|
|
13
18
|
init: 'init <dir> --input <json-file|-> --rules-version <version>. Create state once; input requires goal {summary, endpoint} and goalRef. Does not create templates.',
|
|
14
19
|
read: 'read <dir> [--rules-version <version>]. Read-only JSON access; unknown schemas remain readable.',
|
|
15
20
|
update: 'update <dir> --revision <N> --rules-version <version> --input <json-file|->. Recursively merge objects; arrays replace in full. Identity/history fields are managed. null is a literal value, not deletion.',
|
|
16
|
-
check: 'check <dir> [--rules-version <version>] [--all]. Check structure and current input references (or every registered reference). Does not prove semantics,
|
|
21
|
+
check: 'check <dir> [--rules-version <version>] [--all]. Check structure and current input references (or every registered reference). Checks explicit anchors/identity metadata in new local Markdown; reused and unsupported fragments are explicitly not checked. Does not prove semantics, remote access or validation results.',
|
|
17
22
|
resume: 'resume <dir> [--rules-version <version>]. Read state and return fixed rule paths, required inputs, warnings, and continuation text; never execute work or rewrite state.',
|
|
18
23
|
checkpoint: 'checkpoint <dir> --revision <N> --rules-version <old-version> --input <json-file|->. Save only checkpoint {summary, ref?} and next when a version mismatch interrupts ongoing work. Cannot complete work.',
|
|
19
24
|
adopt: 'adopt <dir> --revision <N> --rules-version <active-version> --input <json-file|->. Input is {compatibilityRef}; records the assessed stable-point rule switch without changing business history.',
|
|
@@ -24,16 +29,17 @@ const HELP = {
|
|
|
24
29
|
},
|
|
25
30
|
stateProtocol: {
|
|
26
31
|
schemaVersion: SCHEMA_VERSION,
|
|
32
|
+
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.'},
|
|
27
33
|
stages: STAGES,
|
|
28
34
|
progress: ['planned', 'active', 'blocked', 'done'],
|
|
29
35
|
result: ['passed', 'failed', 'not-run', 'unknown', 'mixed'],
|
|
30
36
|
applicability: ['current', 'needs-review', 'unknown'],
|
|
31
37
|
outcome: { goal: ['in-progress', 'met', 'not-met', 'unknown'], acceptance: ['not-recorded', 'accepted', 'declined', 'unknown'] },
|
|
32
|
-
stageInstance: { id: 'stable-instance-id', stage: '<stage>', scope: 'batch or whole-delivery scope', progress: 'planned', inputRefs: [], outputRefs: [], resultRef: '
|
|
38
|
+
stageInstance: { id: 'stable-instance-id', stage: '<stage>', scope: 'batch or whole-delivery scope', progress: 'planned', inputRefs: [], outputRefs: [], resultRef: 'review.md#rev-001-v1 (required when done)' },
|
|
33
39
|
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}.',
|
|
34
40
|
topLevel: {
|
|
35
|
-
managed: 'schemaVersion, revision, deliveryId, rulesVersion, createdAt, updatedAt, goalHistory, rulesHistory. Do not supply these in init/update.',
|
|
36
|
-
requiredInit: 'goal: {summary, endpoint}; goalRef: current requirements or Scope reference. Save the
|
|
41
|
+
managed: 'schemaVersion, revision, deliveryId, rulesVersion, createdAt, updatedAt, goalHistory, rulesHistory, documentDigests, identityHistory, retiredIdentities, legacyRunIds, writeResult. Do not supply these in init/update.',
|
|
42
|
+
requiredInit: 'goal: {summary, endpoint}; goalRef: current requirements or Scope reference. Save source/request.md and the registered PRD/Scope draft before init; documentPolicy and documents are required.',
|
|
37
43
|
stageRuns: 'Ordered array of stageInstance objects, default []. Every ID is unique; currentRun must match one of these IDs or be null. Stage types may repeat with distinct IDs.',
|
|
38
44
|
currentRun: 'Instance ID or null (default). Do not invent an ID without registering its instance in stageRuns.',
|
|
39
45
|
repositories: 'Array of {id, path: absolute-path, role, ...optional-version-facts}, default []. No repository is required for a design-only goal.',
|
|
@@ -45,10 +51,12 @@ const HELP = {
|
|
|
45
51
|
},
|
|
46
52
|
minimalInit: {
|
|
47
53
|
goal: { summary: 'Repair the search empty-state message', endpoint: 'Implement, review, and verify the agreed repair' },
|
|
48
|
-
goalRef: '
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
54
|
+
goalRef: 'prd.md',
|
|
55
|
+
documentPolicy: {authority:'authored',interaction:false,prototypeCovered:false,interface:'none',databases:[]},
|
|
56
|
+
documents: [{kind:'prd',path:'prd.md',mode:'new',version:'1'}],
|
|
57
|
+
stageRuns: [{ id: 'requirements-1', stage: 'requirements', scope: 'Search empty-state repair', progress: 'planned', inputRefs: ['prd.md'], outputRefs: [] }],
|
|
58
|
+
currentRun: 'requirements-1',
|
|
59
|
+
next: { action: 'Draft the confirmed requirements', inputRefs: ['prd.md'] }
|
|
52
60
|
},
|
|
53
61
|
safety: 'Done is execution progress only. Results, applicability, goal attainment, acceptance, merge, and deployment are independent. Preserve old result references with priorResultRef/historyResultRefs. Goal revision needs a new goal.decisionRef; prior goals are retained automatically.'
|
|
54
62
|
}
|
|
@@ -59,8 +67,8 @@ function parse(argv) {
|
|
|
59
67
|
const item = argv[i];
|
|
60
68
|
if (!item.startsWith('--')) { positional.push(item); continue; }
|
|
61
69
|
const name = item.slice(2);
|
|
62
|
-
if (!['help', 'version', 'all', 'input', 'rules-version', 'revision', 'host', 'home', 'data-root'].includes(name)) fail('USAGE', `Unknown option: ${item}`, 'Run help for command usage.');
|
|
63
|
-
const value = ['help', 'version', 'all'].includes(name) ? true : argv[++i];
|
|
70
|
+
if (!['help', 'version', 'all', 'preserve-edits', 'input', 'rules-version', 'revision', 'host', 'home', 'data-root'].includes(name)) fail('USAGE', `Unknown option: ${item}`, 'Run help for command usage.');
|
|
71
|
+
const value = ['help', 'version', 'all', 'preserve-edits'].includes(name) ? true : argv[++i];
|
|
64
72
|
if (value === undefined || (typeof value === 'string' && value.startsWith('--'))) fail('USAGE', `Missing value for ${item}`, 'Provide the option value.');
|
|
65
73
|
if (name === 'host') (options.hosts ??= []).push(value);
|
|
66
74
|
else { if (Object.hasOwn(options, name)) fail('USAGE', `Repeated option: ${item}`, 'Provide one value; only --host is repeatable.'); options[name] = value; }
|
|
@@ -111,7 +119,7 @@ async function binding(runtime) {
|
|
|
111
119
|
}
|
|
112
120
|
function allowed(command, options) {
|
|
113
121
|
const common = ['help', 'version'];
|
|
114
|
-
const perCommand = { status: ['home'], bind: ['home'], init: ['input', 'rules-version', 'home'], read: ['rules-version', 'home'], update: ['input', 'rules-version', 'revision', 'home'], check: ['all', 'rules-version', 'home'], resume: ['rules-version', 'home'], checkpoint: ['input', 'rules-version', 'revision', 'home'], adopt: ['input', 'rules-version', 'revision', 'home'], recover: ['rules-version', 'revision', 'home'], install: ['hosts', 'home', 'data-root'], uninstall: ['hosts', 'home'], help: [] };
|
|
122
|
+
const perCommand = { render: ['revision','rules-version','preserve-edits','home'], migrate:['revision','rules-version','input','home'], scan:['home'], status: ['home'], bind: ['home'], init: ['input', 'rules-version', 'home'], read: ['rules-version', 'home'], update: ['input', 'rules-version', 'revision', 'home'], check: ['all', 'rules-version', 'home'], resume: ['rules-version', 'home'], checkpoint: ['input', 'rules-version', 'revision', 'home'], adopt: ['input', 'rules-version', 'revision', 'home'], recover: ['rules-version', 'revision', 'home'], install: ['hosts', 'home', 'data-root'], uninstall: ['hosts', 'home'], help: [] };
|
|
115
123
|
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}.`);
|
|
116
124
|
}
|
|
117
125
|
export async function run(argv, context) {
|
|
@@ -135,25 +143,30 @@ export async function run(argv, context) {
|
|
|
135
143
|
if (command === 'status') return { command, data: { toolVersion: pkg.version, ...await installationStatus({ home: installHome }) } };
|
|
136
144
|
if (command === 'install') return { command, data: await install(context.sourceRoot, { home: installHome, hosts: options.hosts, dataRoot: options['data-root'] }) };
|
|
137
145
|
if (command === 'uninstall') return { command, data: await uninstall({ home: installHome, hosts: options.hosts }) };
|
|
146
|
+
if (command === 'scan') return {command,data:await scanDeliveries(path.resolve(positional[1]))};
|
|
138
147
|
const runtime = await runtimeFor(context.sourceRoot, options, context.installRoot);
|
|
139
148
|
if (command === 'bind') return { command, data: await binding(runtime) };
|
|
140
149
|
const directory = path.resolve(positional[1]); const rulesVersion = options['rules-version'];
|
|
150
|
+
if (command === 'render') return {command,data:await renderState(directory,revisionOf(options.revision),rulesVersion,runtime,options['preserve-edits'])};
|
|
151
|
+
if (command === 'migrate') return {command,data:await migrateState(directory,revisionOf(options.revision),rulesVersion,runtime,await inputJSON(options.input))};
|
|
141
152
|
if (command === 'init') return { command, data: await initState(directory, await inputJSON(options.input), rulesVersion, runtime) };
|
|
142
153
|
if (['update', 'checkpoint', 'adopt'].includes(command)) return { command, data: await updateState(directory, await inputJSON(options.input), revisionOf(options.revision), rulesVersion, runtime, command) };
|
|
143
154
|
if (command === 'recover') return { command, data: await recoverState(directory, revisionOf(options.revision), rulesVersion, runtime) };
|
|
144
155
|
const read = await readState(directory);
|
|
145
156
|
let mismatch = null;
|
|
146
157
|
if (!read.readOnly) { try { assertRules(read.state, rulesVersion, runtime); } catch (error) { mismatch = { code: error.code, ...error.details, recovery: error.recovery }; } }
|
|
147
|
-
if (command === 'read') return { command, data: { state: read.state, readOnly: read.readOnly, versionMismatch: mismatch, statePath:
|
|
148
|
-
if (read.readOnly) return { command, data: { state: read.state, readOnly: true, canProceed: false, reason: 'Unknown schema: no structure checking, mutation, or automatic work is permitted.' } };
|
|
158
|
+
if (command === 'read') return { command, data: { state: read.state, readOnly: read.readOnly, versionMismatch: mismatch, statePath: read.statePath, review:read.review, view: read.view, documentChanges: read.documentChanges, layoutConflict:read.conflict??false, layoutMismatch:read.layoutMismatch??false, candidates:read.candidates, migration:read.migration?.status??null } };
|
|
159
|
+
if (read.readOnly) return { command, data: { state: read.state, readOnly: true, canProceed: false, reason: read.conflict?'Conflicting state layouts require explicit reconciliation.':'Unknown schema: no structure checking, mutation, or automatic work is permitted.', candidates:read.candidates } };
|
|
149
160
|
validateState(read.state); const references = await checkReferences(directory, read.state, options.all);
|
|
161
|
+
const documents=await inspectDocuments(directory,read.state);
|
|
150
162
|
const missing = references.filter(x => !['exists', 'not-checked'].includes(x.status));
|
|
151
163
|
if (command === 'check') {
|
|
152
164
|
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 });
|
|
153
165
|
if (mismatch) fail('VERSION_MISMATCH', 'State is structurally valid, but rules are not bound consistently.', mismatch.recovery, mismatch);
|
|
154
|
-
|
|
166
|
+
if (documents.issues.length) fail('DOCUMENT_CHECK','Document mapping or identities need repair.','Inspect listed issues.',documents);
|
|
167
|
+
return { command, data: { structure: 'valid', review:read.review, references, documents, view:read.view, documentChanges:read.documentChanges, semantics: 'not-checked' } };
|
|
155
168
|
}
|
|
156
169
|
const currentRun = read.state.stageRuns.find(x => x.id === read.state.currentRun) ?? null;
|
|
157
170
|
const blocked = currentRun?.progress === 'blocked';
|
|
158
|
-
return { command, data: { state: read.state, binding: await binding(runtime), readOnly: false, canProceed: !mismatch && missing.length === 0 && !blocked, versionMismatch: mismatch, references, currentRun, continuation: `Use Aimship to continue ${directory}; first read
|
|
171
|
+
return { command, data: { state: read.state, binding: await binding(runtime), readOnly: false, review:read.review, canProceed: read.review.canProceed && read.state.documentPolicy?.authority !== 'legacy' && !mismatch && missing.length === 0 && !blocked && !documents.issues.length && !read.documentChanges.length && read.view.status === 'current' && (!read.migration || read.migration.status === 'complete'), statePath:read.statePath, view:read.view, documentChanges:read.documentChanges, documents, versionMismatch: mismatch, references, currentRun, continuation: `Use Aimship to continue ${directory}; first read ${read.statePath} and the current inputs it references, then follow the confirmed arrangement. Inspect unfinished effects and related artifacts before repeating any action.`, warnings: ['Resume does not rerun work, tests, migrations, or other side effects.', 'Structural readiness does not establish authorization, business readiness, or goal completion.', ...(blocked ? [`Current run ${currentRun.id} is recorded as blocked. Resolve its documented blocker before progressing this work.`] : []), ...(missing.length ? ['Some required paths must be repaired.'] : []), ...(read.state.checkpoint ? ['Inspect the saved checkpoint and current artifacts.'] : [])] } };
|
|
159
172
|
}
|