@entrorove/skill 0.1.61 → 0.1.64
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/CAPABILITY-MAP.md +7 -2
- package/CHANGELOG.md +15 -0
- package/IDE-EXECUTION.md +39 -6
- package/REUSE-DISCIPLINE.md +1 -1
- package/SKILL.md +6 -2
- package/cli-input.mjs +22 -0
- package/cli.mjs +2 -8
- package/cloud-task-client.mjs +10 -2
- package/cloud-task-images.mjs +39 -0
- package/ide-exit.mjs +49 -9
- package/ide-hook.mjs +2 -2
- package/local-contracts.mjs +4 -1
- package/local-discipline-gate.mjs +14 -3
- package/local-discipline-repository.mjs +11 -5
- package/local-finder-plist.mjs +87 -0
- package/local-finder-store.mjs +115 -0
- package/local-mutation-authority.mjs +11 -0
- package/local-mutation-completion-evidence.mjs +83 -0
- package/local-mutation-consumption.mjs +39 -14
- package/local-mutation-environment-observation.mjs +85 -0
- package/local-mutation-finder-observation.mjs +58 -0
- package/local-mutation-wire.mjs +28 -9
- package/local-preserved-mutation-protection.mjs +8 -1
- package/local-preserved-mutation-wire.mjs +7 -3
- package/local-scope-files.mjs +3 -1
- package/local-scope-font.mjs +45 -0
- package/local-scope-repository.mjs +16 -3
- package/local-scope-zip.mjs +93 -0
- package/manifest.json +1 -1
- package/member-producer-repair-authority.mjs +48 -0
- package/model-images.d.mts +9 -0
- package/model-images.mjs +87 -0
- package/mutation-challenge-refresh.mjs +51 -0
- package/mutation-client.mjs +45 -27
- package/mutation-connection-state.mjs +17 -0
- package/package.json +18 -2
- package/release-state.json +52 -36
- package/release.mjs +12 -4
- package/releases.json +18 -0
- package/remote-client.mjs +1 -1
- package/remote-transport.mjs +48 -16
- package/scope-amendment-client.mjs +1 -1
- package/scope-amendment-consumption.mjs +32 -0
- package/scope-amendment-mutation.mjs +3 -4
- package/scope-amendment-observer.mjs +7 -3
- package/scope-amendment-remote.mjs +12 -2
- package/scope-amendment-retirement.mjs +54 -0
- package/scope-amendment-state.mjs +7 -4
- package/scope-amendment-wire.mjs +3 -2
- package/task-entry-client.mjs +1 -0
- package/workflow-observation-client.mjs +11 -0
- package/workflow-observation-connection.mjs +2 -1
- package/workflow-observation-remote.mjs +26 -1
- package/workflow-observation-wire.mjs +6 -3
package/CAPABILITY-MAP.md
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
# 能力对账图(WP-02 / AC-20)
|
|
2
2
|
|
|
3
|
+
中英文退出治理声明:`entrorove exit` 或当前会话的实际用户明确退出原文,只结束本轮治理,保留源码、原清单顺序、审批及全部失败记录;不取消或完成剩余业务,不从翻译问题、引用、截图、条件句或报错推断退出。英文示例:`From now on, do not use the skill to execute the remaining tasks.`
|
|
4
|
+
|
|
5
|
+
直接 SDK 会话缺少 IDE 记录时,`exit` 仅为当前已支持主机/会话和实际目录保存 inactive/user-exited 标记,不创建 flow、任务、审批或回执。已有原目录绑定及历史保留;损坏、不匹配或不安全的记录仍明确拒绝覆盖。
|
|
6
|
+
|
|
3
7
|
## 2026-10-17 增量:受限本地编辑与停止恢复
|
|
4
8
|
|
|
5
9
|
| 能力 | 正常公开入口 | 验证与边界 |
|
|
6
10
|
| --- | --- | --- |
|
|
7
11
|
| 成员受限编辑 | `mutation-connect <repositoryRoot>`、`local mutation-delegate-prepare` | 原任务、冻结修改声明及租约绑定;观察、编辑、独立验证三把密钥分离。运行时依赖保持未知且只读,只允许现存文件的受限修改;不授予通用执行、合并或交付权限 |
|
|
8
|
-
| 连续编辑结算 | `mutation-settle <repositoryRoot>`、`local mutation-consumption-attest`、服务端 `mutation-pass-settle` | 实际写后 M 回执与 fresh S
|
|
9
|
-
| 沙箱关闭后的本地验收 | `workflow-observe-step <repositoryRoot>`、`member-observation-admit/report/status`、`workflow-verdict` | 服务器签名 member-local 计划中,原冻结命令和测试真实通过后签发独立保存的 localAcceptance
|
|
12
|
+
| 连续编辑结算 | `mutation-settle <repositoryRoot>`、`local mutation-consumption-attest`、服务端 `mutation-pass-settle` | 实际写后 M 回执与 fresh S 后验确认原 pass;原成功审计/账本全链且自然过期后,可独立记录完成检查之后、完整结构验证的非目标 Finder 元数据变化,仅结算原写入,不改原基线或赋写权;零写入仍须全仓一致,单次关闭后沿原 `previousPassId` 申请新权限;不计完成或放行测试 |
|
|
13
|
+
| 沙箱关闭后的本地验收 | `workflow-observe-step <repositoryRoot>`、`member-observation-admit/report/status`、`workflow-verdict` | 服务器签名 member-local 计划中,原冻结命令和测试真实通过后签发独立保存的 localAcceptance;已 ACK 的下游正常失败可由原 producer 追加受限返工,实际新 M 与原测试通过后追加复验记录,只对实际源码清单受影响的后续已验收阶段追加只读复验。嵌套 producer 的结算观察保留已确认上游修复摘要;原失败与精确 ACK 可报告已知拒绝,保留 pending 审计且不推断无效果或授予重发权。旧计划/租约/通过/失败保留;原 O 无 V 回执,明确未进行独立验证。原范围、审批、S/M 分离及未知依赖保护保留 |
|
|
10
14
|
| 续跑入口 | 原 `execution-claim`、原 flow 恢复 | 成员受限范围返回原字段的 `localAction`,客户端串联真实快照、签名、云端通行证及本地安装;网络结果不明时恢复原请求 |
|
|
11
15
|
| 同一阻断停止 | IDE `Stop` 回调 | 真实重复阻断可等待;状态查询及检查点不能重置阻断,宿主重复 Stop 不再生成无限续跑。原 flow、需求队列与恢复数据保留 |
|
|
12
16
|
|
|
@@ -44,6 +48,7 @@
|
|
|
44
48
|
| 旧版保证 | 当前入口与行为 |
|
|
45
49
|
| --- | --- |
|
|
46
50
|
| 每个任务选择云端/本地,原生确认不可用时仍须展示问题 | `cloud-task` 返回 Confirm Protocol 和 `chatFallback`;确认才提交,退出保留原任务并采用本地方案 |
|
|
51
|
+
| 云端图片输入与 DeepSeek V4.1 | `cloud-task` / 原 `start` 云端回调的 `images`;`deepseek-flash` | 显式附件经真实字节校验后作为用户图像块传入;原目标与有序图片摘要绑定,按账号隔离;图片不授予执行权限,未验证模型通道明确拒绝 |
|
|
47
52
|
| 实际进度必须进入 IDE | `cloud-watch` 订阅本人任务 SSE,输出独立 IDE 事件;调用方必须把公开消息和摘要展示到对话进度 |
|
|
48
53
|
| 执行提示与推广可见 | `智能执行中,请勿关闭!` 与服务端选中的真实推广独立输出;保留推广 HTTPS 链接,按事件 ID 去重 |
|
|
49
54
|
| 断线恢复原任务 | `cloud-watch` 带原 `taskId/afterId` 只读续接;`status` 与 `feedback` 查询原请求,不重发业务 POST |
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.64 — 2026-10-19
|
|
4
|
+
|
|
5
|
+
- 统一交付原合同连续执行与失败恢复修复:受限完整响应、原请求结算恢复、过期挑战与未用授权处理、已批准依赖与二进制核验、系统元数据观察;新增图片输入并适配 DeepSeek V4.1 Flash,完善中英文明确退出及无 IDE 记录的离线退出。保留范围、签名、预算及失败和未知记录;缺少进程终局证据的观察仍拒绝重跑。
|
|
6
|
+
- Deliver original-contract continuation and recovery fixes for bounded complete responses, original-request settlement, expired challenges and unused grants, approved dependencies, verified binary inputs and system metadata observations. Add image inputs for DeepSeek V4.1 Flash and bilingual explicit offline exit, including sessions without an IDE record. Preserve scope, signatures, budgets, failures and unknown outcomes; observations without terminal process evidence still cannot rerun.
|
|
7
|
+
|
|
8
|
+
## 0.1.63 — 2026-10-18
|
|
9
|
+
|
|
10
|
+
- 修复精确大范围补正的组合回执保存;原 M 授权支持真实二进制字节,保留源代码分析、原路径及结算校验。范围风险提高必须另经原账号明确重评。
|
|
11
|
+
- Retain combined receipts for exact large scope amendments and write actual binary bytes through the original M authority, preserving source analysis, paths and settlement checks. Higher scope risk requires explicit original-owner reassessment.
|
|
12
|
+
|
|
13
|
+
## 0.1.62 — 2026-10-18
|
|
14
|
+
|
|
15
|
+
- 补齐本地新项目的受控创建与原生产步骤内迭代:原账号审批精确不存在路径和生产节点,S 与独立 M 签名校验真实基线,保留源文件只读保护、合同预算及失败历史。支持 PHP、JSON、原生模板、WXML、WXSS,通过实际创建、结算、测试和最终本地验收;钩子仍可关闭。
|
|
16
|
+
- Support governed creation and iteration of new local project files within their original producer and accepted plan. Bind exact absent paths and producers to owner approval, actual S and separate M evidence, protected sources, contract budgets and immutable failures. Cover PHP, JSON, native templates, WXML and WXSS through real writes, settlement, tests and final local acceptance while keeping hooks optional.
|
|
17
|
+
|
|
3
18
|
## 0.1.61 — 2026-10-18
|
|
4
19
|
|
|
5
20
|
- 钩子改为可选:默认安装关闭钩子,IDE 可直接调用 SDK;只有明确选用技能的原会话与仓库启用拦截,普通查询、旧激活状态及无法归属的事件不再接管其他任务。保留账号、写入范围、签名和真实本地验收校验。
|
package/IDE-EXECUTION.md
CHANGED
|
@@ -19,7 +19,9 @@ Approved Codex `exec_command` reads and SDK calls run with `/bin/sh` and `login:
|
|
|
19
19
|
|
|
20
20
|
For a Codex task-to-task report explicitly authorized by the human user, the guard passes the exact `mcp__codex_app__send_message_to_thread` tool through unchanged, including a static `tools.mcp__codex_app__send_message_to_thread({...})` call inside `functions.exec`. Codex still decides whether the user authorized the message. Its text and response grant no Entrorove flow, file, shell or task-creation authority.
|
|
21
21
|
|
|
22
|
-
To stop governance only in the current IDE session after an explicit user decision, run `entrorove exit`. It needs no network, credential, agreement or hook callback and preserves original evidence and queues. Other sessions remain unchanged. `flow-cancel` and `flow-abandon` settle a flow; they do not express a decision to stop using the skill.
|
|
22
|
+
To stop governance only in the current IDE session after an explicit user decision, run `entrorove exit`. It needs no network, credential, agreement or hook callback and preserves original evidence and queues. Direct SDK use may have no IDE session record: in that case exit creates only an inactive `user-exited` marker for the actual supported host/session and current physical directory, without creating a flow, task, approval or receipt. An existing record keeps its original repository and history; invalid or unsafe records are rejected rather than replaced. Other sessions remain unchanged. `flow-cancel` and `flow-abandon` settle a flow; they do not express a decision to stop using the skill.
|
|
23
|
+
|
|
24
|
+
Direct stop decisions and their exit acknowledgements support Chinese and English; keep the name `Entrorove` unchanged in both languages. One English decision is `From now on, do not use the skill to execute the remaining tasks.` A complete English declaration is: `The user has explicitly decided: report the errors to the skill maintainers and stop using the skill for the remaining tasks from now on. End only the current Entrorove governance session; preserve the source code, the original task-list order, approvals, and all failure records. This does not mean that the remaining business tasks are cancelled or completed.` The equivalent Chinese declaration is: `用户明确决定:错误问题发送给技能修改人员,从现在开始不使用技能来执行剩余任务。仅结束本轮Entrorove治理;保留源码、原清单顺序、审批和全部失败记录,不代表剩余业务任务取消或完成。` These decisions are acted on only as actual user prompts in the affected session. A translation/adaptation question, quotation, screenshot, hypothetical condition or failure report is not an exit decision. Exit records no business completion, acceptance, cancellation or implicit approval and does not send a report to anyone.
|
|
23
25
|
|
|
24
26
|
To stop using Entrorove on this computer, run `entrorove uninstall` directly. It works offline, removes only the recorded Entrorove host hooks and pristine global skill, and leaves other host settings, credentials and task records in place. A hand-edited file is reported as preserved rather than deleted. This command does not inspect or change another project.
|
|
25
27
|
|
|
@@ -35,6 +37,13 @@ For a new engineering task, run `npx --yes --prefer-online @entrorove/skill@late
|
|
|
35
37
|
|
|
36
38
|
For an explicit cloud planning request without an existing start, `cloud-task` accepts `{"goal":"<actual goal>","ideTaskId":"<current IDE task ID, when available>"}` on stdin and returns the same choice. Do not start both routes for one task.
|
|
37
39
|
|
|
40
|
+
Images attached to a cloud planning task use `images: [{"path":"<explicit absolute image path>","detail":"original"}]` or `images: [{"url":"data:image/png;base64,<canonical bytes>","detail":"auto"}]`. For ordinary `invoke` task entry, keep `images` at the envelope's top level, outside the immutable `input.task`; the original cloud-choice callback carries them forward. Render the returned choice before sending. File paths are read only after `use`, and a local choice retains the attachment references without reading or uploading them. Never scan for images, copy a local path into a remote URL, omit attachments silently, or describe a text-only classification as image analysis.
|
|
41
|
+
|
|
42
|
+
Entrorove currently verifies this image channel for **DeepSeek V4.1 Flash (1017)**, API ID `deepseek-flash`. The pricing page's `(1)` is a footnote, not an API suffix. Older DeepSeek choices are removed; existing legacy selections upgrade while preserving their encrypted key and enabled state. Unverified model image channels fail explicitly before a provider request. Entrorove's own bounds are four images, 2 MiB each, 8 MiB total, and 8192 pixels per side; actual JPEG/PNG/GIF/WebP headers, dimensions, inline MIME, canonical base64 and byte limits are checked before dispatch. The provider decodes the actual content. Use only explicit user attachments.
|
|
43
|
+
|
|
44
|
+
The authenticated API accepts inline data URLs, never remote URLs or server file paths. It sends text and `image_url` blocks together in the user message. Original goal and ordered image hashes, media types, sizes and detail modes bind the title and completion. Public task reads contain only that image manifest; image bytes remain in the single in-memory classification attempt, not the database, events or usage records. Provider-reported prompt usage includes images; no estimated image tokens are reported as actual usage. A failed or unknown attempt retains its original recovery rules and must not be resubmitted just to resend images.
|
|
45
|
+
|
|
46
|
+
|
|
38
47
|
With `sandboxChoice: "use"` (or the protocol callback's `answer: "use"`), immediately display the returned acceptance message, for example **云端任务 08921 · 已进入**. Use the returned `taskNumber` verbatim: it matches the homepage sandbox tab and notification. Retain the UUID `taskId` separately for API queries; never display the IDE chat ID or invent a number instead. Then run the returned `nextAction`, `cloud-watch`. It subscribes to that account's authoritative SSE stream, returns newline-delimited `entrorove.ide-event/1.0` events, then one `entrorove.cloud-watch/1.0` result. **Mirror each new event's public `message` and optional `detail` into visible IDE progress/commentary.** Deduplicate by task ID and event ID, including the acceptance message repeated in the stream or final result. The same rule applies to normal invocation `ideMessages` and notice events. A JSON field or collapsed command log does not satisfy this display requirement. Show `智能执行中,请勿关闭!` and the selected promotion on separate lines; preserve the promotion's actual HTTPS link. Use only the server's selected promotion; do not insert a local test advertisement.
|
|
39
48
|
|
|
40
49
|
Display the actual entering, allocation, thinking, public output and exit events as they arrive. Do not generate model reasoning, invent intermediate events or replace `reported` with verified completion. The public model summary is the planning output available to show. `云端沙箱输出完毕` means the current planning response has arrived; inspect its real task/verification status before dependent execution. Exit here closes the progress subscription and does not cancel the persisted task.
|
|
@@ -71,7 +80,7 @@ For `workType: "merge"`, include `baselineRef` with the concrete branch, tag, co
|
|
|
71
80
|
|
|
72
81
|
The v2 capability map is recorded in [`CAPABILITY-MAP.md`](CAPABILITY-MAP.md). It maps every legacy domain and operation to its v2 equivalent, narrower semantics, or an explicitly documented uncovered area; adapters must not silently substitute an uncovered operation.
|
|
73
82
|
|
|
74
|
-
For an explicit domain operation, use `npx @entrorove/skill native <domain> <operation>` with its input object on stdin. The supported domains are `aimlock`, `blueprint`, `calctool`, `swarm`, `validator`, `mergeguard`, `archguard` and `confirm-protocol`. This public adapter discovers that domain's actual server schema, validates the input, invokes the registered operation once, and verifies any required automatic feedback. Discovery, help and status observations follow the server's published feedback contract. A failed gate or unknown outcome must never be replaced with another executor or reported as local success; fix the original cause and recover the original request through its read-only status.
|
|
83
|
+
For an explicit domain operation, use `npx @entrorove/skill native <domain> <operation>` with its input object on stdin. The supported domains are `aimlock`, `blueprint`, `calctool`, `swarm`, `validator`, `mergeguard`, `archguard` and `confirm-protocol`. This public adapter discovers that domain's actual server schema, validates the input, invokes the registered operation once, and verifies any required automatic feedback. Discovery, help and status observations follow the server's published feedback contract. A failed gate or unknown outcome must never be replaced with another executor or reported as local success; fix the original cause and recover the original request through its read-only status. For a scope challenge preparation whose authenticated original GET returns `request-not-found`, `scope-amend` discovers `scope-challenge-retire`. This separate lifecycle request retains the exact original envelope, account, approved proposal and receiver. Its transaction can create a current signed retirement fence only if no original admission or nonce exists. A late original POST reads that terminal fence and cannot issue a nonce. The original GET and feedback acknowledgement then preserve the retirement before a fresh approved challenge; no scope application, M settlement, write permission or business result is inferred. A lost retirement response is recovered by that retirement request's original GET without replaying either POST.
|
|
75
84
|
|
|
76
85
|
Aimlock exposes `brain-plan`, `brain-check`, `brain-validate` and `brain-status`. Create a plan with the actual goal, scope `contract`, `requirements` and ordered `steps`; retain its returned `requestId` and `plan.digest`. Subsequent operations take only that original `planRequestId` (plus `paths` for `brain-check`); the server loads the authenticated account's stored plan. Never submit a reconstructed plan or another account's request. Planning validation checks scope, requirements and dependencies; it grants no mutation authority and never certifies business delivery.
|
|
77
86
|
|
|
@@ -89,26 +98,46 @@ If the original selection omitted actual components or inspection files, use the
|
|
|
89
98
|
|
|
90
99
|
Present the returned scope-revision question and submit the original owner's actual answer through its bound `interaction-answer`. Approval is specific to that complete proposal; it does not approve a plan or perform a write. Then run `npx @entrorove/skill scope-amend <original repositoryRoot>` with `{flowId,proposalId,proposalDigest}` on stdin. It loads the **existing** S key and original repository/entry/import-context binding, obtains a W challenge, scans the real complete file set into a separate candidate snapshot and submits signed evidence. It never creates a replacement signer, answers approval itself, or overwrites the original scope history.
|
|
91
100
|
|
|
101
|
+
When exact new paths imply a higher risk, the service includes `riskReview` in the immutable proposal and explicitly shows the old and proposed risk in its original-owner question. Only that actual approval raises the exact scope risk ceiling; the original task, mode, path contract and consumed file/token/line/time budgets remain unchanged. An older approval lacking this risk question cannot authorize an upgrade: follow `risk-reassessment-required` with the same complete role map in a new `workflow-scope-review` on the original flow. Keep the old approval and failed request history; never lower observed risk or repeat an unchanged rejected apply.
|
|
102
|
+
|
|
92
103
|
Only a successful native gate and the original approved contracts and remaining budgets can activate the immutable scope revision. Keep the original Blueprint, plan identities, task order and failure receipts. Submit the complete corrected plan with the returned `scopeRevisionDigest` under its canonical new request identity. Exact read-only files remain read-only, even if included in the manifest or a broad write contract. An unknown request outcome is recovered with the original GET; do not replay an uncertain POST or restart the flow.
|
|
93
104
|
|
|
94
105
|
After an amended task's actual guarded writes, follow its returned `scope-amend` refresh action, which adds the original stored `gatePassPath` to the same `{flowId,proposalId,proposalDigest}`. The connector preserves the real M consumption receipt and observes new S facts without another write. It appends facts under the same approved role revision, then the original `mutation-settle` and local observation can continue. Changed read-only files, a different pass or extra writes remain blocked. A proof refresh cannot change permissions or reset a consumed budget. Live `workflow-scope-status` observes the current head; an uncertain mutating request is still recovered only through its original GET.
|
|
95
106
|
|
|
107
|
+
During that refresh, a static relative import whose exact target is still absent from the approved create list remains an explicit unresolved observation. Its real importing source and absent candidates stay recorded, with `complete: false`; no dependency node or execution permission is invented. The original M writes may settle under their existing contract. When a later original M actually creates that dependency, comparison may restore the earlier unknown only for an unchanged importing source and an exact consumed absent target. The real new S graph and resolved edge remain in the ledger. Unapproved missing paths, unsafe references and unknown external dependencies retain their errors.
|
|
108
|
+
|
|
109
|
+
Source line statistics recognize complete checksum-verified sfnt fonts and bounded ordinary ZIP containers from their bytes. ZIP inspection verifies local and central records, stored/Deflate content lengths and CRCs in memory; it does not extract or execute entries. Encrypted, ZIP64, unsupported or damaged containers retain conservative text counts. Binary recognition never removes the outer file's bytes, hash, file count or byte-derived token accounting and never changes the approved budget.
|
|
110
|
+
|
|
96
111
|
Keep snapshot before-images distinct from a frozen execution plan's expected final file hashes. Controlled writes consume the original approved authority; the runner then verifies the actual planned final bytes. Never edit a signed plan or silently substitute a newer snapshot after an unknown or failed gate. Existing changes outside that authority require explicit revalidation of the same original flow.
|
|
97
112
|
|
|
98
113
|
Computed runtime imports remain explicitly unresolved with their actual source paths and hashes. A member's scope-only signer may submit these observations to enter restricted planning; the cloud returns `planningOnly: true` and `requires-complete-import-graph-before-mutation`. Do not treat this as full dependency verification, execution permission, or business completion. Obtain the limited original-task editing delegation below when eligible, or resolve and verify the dependency contract. Never set `complete` manually or replace the original signer or paths.
|
|
99
114
|
|
|
100
115
|
### Restricted member editing
|
|
101
116
|
|
|
102
|
-
|
|
117
|
+
`local guarded-write` accepts exactly one of `content` (text or JSON) and `contentBase64` (canonical base64, at most 16,000,000 encoded characters). The latter writes decoded bytes for an already authorized PNG, font or archive path; it grants no additional path, create/modify role or execution authority. Decoded source files still pass the original UTF-8, imports, reuse and contract checks. Binary bytes retain their exact SHA-256 through the M write receipt, fresh S observation and settlement. Do not save base64 text as a binary file or write assets outside the original pass.
|
|
118
|
+
|
|
119
|
+
When the accepted plan returns a `localAction` for `mutation-connect`, run `npx @entrorove/skill mutation-connect <original repositoryRoot>` with that action's JSON `input` on stdin. The input contains the original `taskId`, `flowId`, `stepId`, `leaseId`, `generation`, scope `runnerId` and exact `paths`. Use the same local home, account and scope signer. This route requires the matching accepted frozen write policy and a live original lease; it does not create a second task or invoke a generic execution authority.
|
|
103
120
|
|
|
104
121
|
The connector discovers the public cloud verification key through the authenticated official HTTPS capability endpoint and fixes it in private local storage. An explicit operator pin must match, and a changed key or origin is rejected rather than silently replaced. Subsequent local mutation calls load this public pin offline; discovery and `unlock` do not depend on that cache.
|
|
105
122
|
|
|
106
|
-
The connector creates a separate local editing key, requests the cloud's original signed snapshot challenge, copies and verifies the actual files, signs their fresh observation, obtains a limited cloud pass and installs it. The local editing key differs from both the scope observation key and the independent validator key. It is stored privately outside the repository; no manual key configuration is required. An uncertain network result returns a structured blocked result with the original request ID. Reusing the identical connector input restores its saved stage through the original status GET and keeps its existing challenge and attestation; it never replaces an uncertain mutation POST.
|
|
123
|
+
The connector creates a separate local editing key, requests the cloud's original signed snapshot challenge, copies and verifies the actual files or their exact absence, signs their fresh observation, obtains a limited cloud pass and installs it. The local editing key differs from both the scope observation key and the independent validator key. It is stored privately outside the repository; no manual key configuration is required. An uncertain network result returns a structured blocked result with the original request ID. Reusing the identical connector input restores its saved stage through the original status GET and keeps its existing challenge and attestation; it never replaces an uncertain mutation POST.
|
|
107
124
|
|
|
108
|
-
Use the returned `gatePassPath` with the discovered `local guarded-write` contract and preserve its exact flow, step and repository.
|
|
125
|
+
Use the returned `gatePassPath` with the discovered `local guarded-write` contract and preserve its exact flow, step and repository. A `preserved-existing-modify` pass covers only existing declared modify targets. Runtime loader sources and observed project configuration stay protected by their original hashes and physical file identity. That route's local preflight rejects changed imports, removed definitions, public contract changes, new unknown dependencies and unsupported source formats. Unknown dependency coverage remains unknown after an eligible edit.
|
|
126
|
+
|
|
127
|
+
New project files use a separate explicit role, including PHP, JSON, native templates, WXML and WXSS. Before freezing the plan, read `workflow-scope-status`, then submit the complete `workflow-scope-review` file map. Keep existing references as `read-only` with their real hashes. Each absent output uses `access: create`, its exact `producerNodeRef`, original `requirementRefs`, a concrete `reason`, and `sha256: SHA256("entrorove.missing-file/1.0")`. This hash represents real absence, never future contents. The original contract must already allow new files at those paths. Obtain the original owner's exact server-issued approval and run `scope-amend` with its proposal identity and original S signer. Do not create placeholders, change the original gate or start a replacement flow. Existing references or dependencies cannot be relabeled as creations. Exact output slots have a bounded role count; they do not expand existing-source read quotas, token limits or the original changed-line contract.
|
|
128
|
+
|
|
129
|
+
Bind the accepted `scopeRevisionDigest` in `workflow-plan`. The producer manifest contains each output with that absent baseline and a matching `discipline.writes` entry of `kind: create`. Only its original compiled producer may write it. Descendants may include the output read-only after that producer's actual local acceptance; actual hashes come from the immutable M settlement chain. Do not put unrelated or future outputs in an earlier step's input manifest. `mutation-connect` returns a `planned-file-create` pass only after S and M prove absence; guarded creation is exclusive and cannot overwrite an existing file or follow a symlink ancestor. After creation, refresh S, settle M and run the actual frozen tests as below. A further pass may return `planned-file-modify` only for files proven created by that same original producer and accepted plan, with its current authorized lease and actual settled baseline. This permits local development of those new outputs without weakening the protected existing-file modify route. Requirement, reuse, deletion, contract, scope, signature and repository checks still apply; unresolved dependencies remain unresolved.
|
|
109
130
|
|
|
110
131
|
After the pass's actual writes finish, follow its scope refresh action: an amended scope uses `scope-amend` with the original stored `gatePassPath`; an unchanged original scope uses its complete original `scope-connect` input, including `importContexts`. Run `npx @entrorove/skill mutation-settle <original repositoryRoot>` with `{taskId,flowId,stepId,gatePassPath}`. The local M signer observes the real completed consumption, and the service requires a fresh S scan captured after those writes. This settlement preserves both before and after hashes, unchanged modes, pass nonces and the original lease. It never creates an architecture verification event. Further edits use the returned `localAction`, whose `previousPassId` identifies that exact settled pass; do not resend an already consumed write.
|
|
111
132
|
|
|
133
|
+
An expired issued pass with zero actual writes may use this same settlement entry only after its original ledger, every unwritten target and whole-repository bytes and modes prove unchanged. The signed receipt records `writes: []` and the exact unwritten set; pending effects before expiry, missing evidence, drift and links remain refused. Preserve prior usage-review refusals. The result closes that original pass once and returns exact remaining-path guidance; it grants no writes, new test process or completion. Obtain fresh authority through the returned original `localAction`; an expired lease uses only live same-lease continuation. Unknown closure outcomes use the original GET and ACK.
|
|
134
|
+
|
|
135
|
+
An unrelated file appearing after a pass expires does not erase an already verified controlled write. General addition recovery requires every completed original write's successful post-check audit and intact execution-ledger chain, matching actual targets and protected sources, and only new single-link regular non-executable files physically born after both the pass expiry and last successful completion. No cache directory, caller flag or generated-file label grants this recovery. Other existing-file changes, missing completion evidence, unknown physical writes, protected-source drift and linked or executable additions remain blocked. The SDK preserves the snapshot, pass, write ledger and failures, and records exact new-file hashes, modes, physical identities and completion evidence in a separate M-signed `environmentObservation`, attributed `unattributed-post-write-additions`.
|
|
136
|
+
|
|
137
|
+
The original expired pass can also settle its already completed writes after later Finder metadata changes. This requires the same successful completion evidence, exact targets/protected sources, and only single-link non-executable `.DS_Store` files outside those targets and protected sources. Each current file must pass bounded allocator partition, B-tree, record-type and embedded binary-property-list validation; the filename or eight-byte marker alone never suffices. Existing metadata must retain its original mode; additions must be born after completion, and every observed metadata modification/creation time must be after completion. These changes may precede the pass's expiry, but recovery itself requires natural expiry. Limits are 1 MiB per file, 8 MiB total and 512 differences. Unsupported formats, malformed bytes, deletions, links, protected metadata, ordinary existing-file changes and zero-write passes remain refused. No project file is deleted or restored and no original snapshot or ledger is edited. The separate observation retains each original comparison entry/hash and actual current hash, format result and physical identity, attributed `unattributed-post-write-finder-metadata` (or `unattributed-post-write-environment` when combined with independently eligible later additions). A historical comparison hash has no retained content: the observation explicitly does not claim that old content's format was validated. Neither the claimed author nor Finder process is authenticated.
|
|
138
|
+
|
|
139
|
+
Every environment observation keeps `grantsWriteAuthority`, `claimsRepositoryUnchanged` and `claimsAuthorizedEnvironmentChange` false; `environmentObservationAcknowledgedByServer` is also false. It is not included in the server settlement acknowledgement. The remote request settles only the original actual authorized writes; subsequent writes/tests still require fresh original S/M authority. Keep these observations visible in recovery and delivery records. Normal guarded writes and whole-repository discipline checks continue to detect all metadata drift.
|
|
140
|
+
|
|
112
141
|
If settlement reports `ENTROROVE_SCOPE_AFTER_WRITE_REQUIRED`, refresh only the original scope and retry the same consumption request and receipt. A lost network response is different: rerunning the identical connector restores its original GET result without a replacement POST or signature.
|
|
113
142
|
|
|
114
143
|
With cloud sandbox disabled, the returned `observationAction` runs `npx @entrorove/skill workflow-observe-step <original repositoryRoot>`. A mutable step uses `{taskId,flowId,stepId,leaseId,generation,settlementId}` and the service updates source hashes only through accepted M settlements. An explicitly frozen read-only step uses `{taskId,flowId,stepId,leaseId,generation}`; its signed admission declares `sourceKind: original-read-only-plan` and contains no settlement ID or digest. Both paths require the original frozen command, executable hash, environment, limits and current signed source manifest. The local runner checks actual source and protected dependencies before and after execution, including repository bytes and modes. No validator private key is needed. The raw observation stays `status: incomplete`, `runner: local`, `receipt: null`, `independentValidation: pending`; local acceptance is represented separately below. Reentry preserves a completed process result; an unknown process outcome never authorizes an automatic second launch.
|
|
@@ -119,6 +148,8 @@ For an enrolled member with the account's cloud sandbox disabled, `workflow-plan
|
|
|
119
148
|
|
|
120
149
|
Use each returned original `localAction` and `observationAction`. Only an actual successful original process, passing checks and unchanged protected repository can receive a server-signed `localAcceptance`. The wrapper reports `local-accepted` and `independentValidation: not-performed`, while its raw observation remains intact. The CLI and IDE hook verify that signature and the original request, lease and result binding. Local dependencies carry `localDependencies`; formal `verifiedDependencies` remain empty. A failed process, changed bytes, forged signature or unknown outcome cannot advance the next step.
|
|
121
150
|
|
|
151
|
+
After an acknowledged downstream process exits normally with failure and all repository protection checks pass, `task-resume` may offer `producerRepairActions` for an actually accepted original ancestor. Select only the real correction paths from that producer's `eligiblePaths` in its returned `payloadTemplate`, then use the original `continue/execution-claim` with `producerRepair`. The service rechecks fresh signed S, the frozen plan, original producer roles, successful prior acceptance, failed descendant ACK and unresolved effects. It appends a bounded grant on that producer's unchanged lease and generation. Follow the returned original M action, settle the actual correction and run the unchanged producer command. Its new successful result appends acceptance; old plans, leases, acceptances and failures remain intact. An expired grant can renew only through its exact returned predecessor. Only previously accepted downstream stages whose actual source manifest was changed by that ancestor repair require bounded test-only revalidation. Their returned observation input includes the signed `dependencyRepairDigest`; it cannot grant writes or replace a cached old failure. Flow status exposes signed producer repair references for client preflight while the original downstream flow step stays unchanged. Unknown requests use their original GET and ACK.
|
|
152
|
+
|
|
122
153
|
After all original steps and requirement checkpoints have passed, invoke the returned `workflow-verdict`. A signed `entrorove.member-local-workflow-acceptance/1.0` completes local acceptance of that same task. Display “本地执行与测试已验收;未进行独立验证”. Formal V, Blueprint independent criteria, Swarm, merge and handoff reject local plans. Do not generate a validator key, claim independent verification, alter failed history or create a replacement flow.
|
|
123
154
|
|
|
124
155
|
The installed hook verifies the original signed local result before continuing or ending the task. Final acceptance and acknowledged truthful feedback permit restoring its queued parent and cleaning superseded snapshots for this flow, preserving original, final and referenced rollback copies. Cloud resource expiry can clean that resource while a valid original local plan continues; explicit cancellation and already terminal workflows retain their original termination.
|
|
@@ -127,10 +158,12 @@ For `ENTROROVE_PLAN_VALIDATION_REQUIRED`, correct only the identified proposal f
|
|
|
127
158
|
|
|
128
159
|
This command runs with the host OS user's permissions, not inside an OS sandbox. Its repository checks do not prove that external files, networks or databases were unchanged: `executionIsolation` remains `host-permissions`, and `externalEffects` remains `unverified`. The independent supervisor enforces the admitted start deadline and command timeout and reclaims its own process group if the CLI disappears. Only the original approved command can be launched; these observations grant no general shell or project-write permission.
|
|
129
160
|
|
|
130
|
-
For a lost `mutation-settle` response, rerun that exact command and input to restore its original HTTP request. For observation network recovery, rerun the identical `workflow-observe-step` input or invoke `member-observation-status` with `{observationId}` through the normal `invoke` entry; both preserve the original process identity. A feedback acknowledgement confirms receipt of a result, not independent verification of that result. Exact host approval means the existing guard callback for the tool call, not another request for human business approval.
|
|
161
|
+
For a lost `mutation-settle` response, rerun that exact command and input to restore its original HTTP request. For observation network recovery, rerun the identical `workflow-observe-step` input or invoke `member-observation-status` with `{observationId}` through the normal `invoke` entry; both preserve the original process identity. A terminal failed result with its exact acknowledged receipt is reported as `ENTROROVE_LOCAL_OBSERVATION_REJECTED`; its pending binding, result and receipt remain in the original journal. This confirms failure without inferring no effects or authorizing another POST or process. Repair the cause and follow fresh original `task-resume` guidance. When an original producer also depends on repaired ancestors, settlement observation guidance retains their exact accepted `dependencyRepairDigest`; never supply that digest manually. A feedback acknowledgement confirms receipt of a result, not independent verification of that result. Exact host approval means the existing guard callback for the tool call, not another request for human business approval.
|
|
131
162
|
|
|
132
163
|
An editing pass does not certify test execution, independent validation, merge or delivery. Keep those existing verification requirements and distinguish an actual edited file from a verified business result. A missing independent runner is an explicit prerequisite; never substitute the editing key for that runner or describe a local edit as completed delivery.
|
|
133
164
|
|
|
165
|
+
An expired unissued member M challenge can recover only through the original `mutation-connect` input. The SDK first recovers any original pending issue with authenticated GET and real feedback acknowledgement. The server requires the exact expired signed challenge, closed acknowledged requests, no issued or unsettled pass, the same original producer and predecessor settlement, and fresh unchanged scope/identity checks. It appends a new signed challenge bound to that expired predecessor. The SDK archives the old challenge, snapshot, attestation and failures before making a new snapshot. Issued passes and unknown requests cannot use this refresh. A lost refresh response permits only its original GET; never edit the journal or repeat an uncertain POST.
|
|
166
|
+
|
|
134
167
|
For a decision, include `input.task.interaction` with `type` (`confirm`, `choice`, `multi` or `input`), a `question`, `options` (two or more with unique `id` and `label`, or an empty array for input), and a `callback` containing its `operation` and `payload`. Medium- and high-risk decisions also need a `riskDescription`. The decision waits for an explicit answer; it never chooses a default on timeout.
|
|
135
168
|
|
|
136
169
|
Use `npx @entrorove/skill status <requestId>` after a connection loss. It performs a read-only GET lookup and never resends a POST. Use `npx @entrorove/skill flows` to list recoverable flow steps. `npx @entrorove/skill usage [flowId]` reads measured consumption grouped per host and model and, with an original flow, its delivered stages; it never displays an invented quota or a predicted duration. `npx @entrorove/skill billing` exports measured cost per account, host, model and task with that task's delivery stages. A declared host adapter may report the host's own remaining quota through the same module that describes its guard; when it cannot, the answer stays explicitly `unknown` and only this skill's measured ledger is used. `GET /api/skill/flows/:id/status` reports `delivery` with delivered, total and remaining stages. Independent plans count independently verified steps; member-local plans count only signed local acceptances of original business steps and explicitly report that basis. A v2 flow keeps its original `flowId`; local acceptance does not change formal step status to `verified`.
|
package/REUSE-DISCIPLINE.md
CHANGED
|
@@ -8,7 +8,7 @@ Snapshot scans stream whole-repository integrity hashes and bound semantic work
|
|
|
8
8
|
- D-2: A rewrite must cite a concrete existing defect at file:line. “Clearer”, “more maintainable” and “more general” are not evidence. A necessary sensitive exception is submitted through `continue` → `workflow-discipline-review` on the original planning phase. Display the whole concrete proposal. Approval must be the original account's stored Confirm Protocol answer and audit, never a caller's `accepted:true`.
|
|
9
9
|
- D-3: A parallel path requires frozen `policy.parallelPaths` and actual same-input, fieldwise equivalence evidence. Use the existing GoldenBaseline to pin baseline bytes and source hash. The approved runner reads the baseline and actual process output; the original output cannot be supplied anew by the implementation. Missing evidence yields incomplete, mismatch yields blocked with field names. Baselines come from the existing path's actual independent samples, not the new implementation's self-consistency tests.
|
|
10
10
|
- D-4: `discipline-review` exposes symbols, actual references, unresolved sources, scan scope and artifact digest. States are in-use, unused and unknown. Exported/external or dynamic references remain unknown, including reflective/computed access. Unknown equals in-use for preservation. A deletion must reference the original usage review, named symbols and task requirement; known in-use deletion additionally needs the explicit approved exception. Zero static references alone never prove business removability.
|
|
11
|
-
- D-5: The cloud-signed `disciplinePolicy.writes` declares modify/create/delete/move/format with reason and original requirement IDs. Deletes and moves need independent explicit approval. `guarded-delete` and `guarded-move` compare the exact original declaration and physical baseline before action. Moves record explicit source/destination/digest events. Formatting on unchanged semantic lines is refused. The rollback snapshot is limited to pass paths; `discipline-check` compares the whole repository, including ignored files, excluding only `.git` and `node_modules`. Outside-scope bytes or modes refuse verification. No self-score grants permission.
|
|
11
|
+
- D-5: The cloud-signed `disciplinePolicy.writes` declares modify/create/delete/move/format with reason and original requirement IDs. Deletes and moves need independent explicit approval. `guarded-delete` and `guarded-move` compare the exact original declaration and physical baseline before action. Moves record explicit source/destination/digest events. Formatting on unchanged semantic lines is refused. The rollback snapshot is limited to pass paths; `discipline-check` compares the whole repository, including ignored files and Finder metadata, excluding only `.git` and `node_modules`. Outside-scope bytes or modes refuse verification. Expired completed-write consumption has the narrowly evidenced environment-observation recovery in IDE-EXECUTION.md; it neither changes this comparison nor grants later writes. No self-score grants permission.
|
|
12
12
|
- D-6: Future abstractions require concrete named fields from current requirements. Speculative extension justifications without those fields are refused.
|
|
13
13
|
|
|
14
14
|
Pause for an actual same-semantic new definition, existing data/interface semantic change, data deletion/migration/rebuild, or unsubstantiated rewrite. Submit the exact proposal and reasons through the original flow. Existing data actions must pin their actual pre-execution snapshot artifact in the frozen runner manifest. Never delete to “try” a diagnosis.
|
package/SKILL.md
CHANGED
|
@@ -5,7 +5,7 @@ description: Execute a task selected for Entrorove through explicit SDK operatio
|
|
|
5
5
|
|
|
6
6
|
# entrorove · 熵巡
|
|
7
7
|
|
|
8
|
-
Version: 0.1.
|
|
8
|
+
Version: 0.1.64.
|
|
9
9
|
|
|
10
10
|
Entrorove applies only to the task for which it is selected; reading these instructions, discovering capabilities or installing the package never selects or governs another task. Hooks are optional. Reading this document or calling an entry once does not execute the task. On every later turn, including after compaction, continue the original task and its required next operation before dependent work. Keep the original goal, scope, account, flow, cloud task and failure records. Never describe planning, a reported step or model output as verified delivery.
|
|
11
11
|
|
|
@@ -21,7 +21,7 @@ Entrorove applies only to the task for which it is selected; reading these instr
|
|
|
21
21
|
When the model produces a task contract, preserve its `workType`, `risk`, `difficulty`, `crossModule`, `parallel`, `unclear`, `architectureContract`, optional `baselineRef`, and optional `interaction` as returned. Valid work types are `implementation`, `documentation`, `calculation`, `merge`, `review`, `decision`, and `diagnosis`. A `merge` requires a concrete `baselineRef` identifying the branch, tag, commit, or review baseline; keep that baseline unchanged through independent verification. A `decision` requires `interaction`, including a risk description for medium or high risk. Do not fill missing required fields by guessing.
|
|
22
22
|
4. After real cloud acceptance, immediately display **云端任务 <taskNumber> · 已进入**. Preserve `taskNumber` verbatim: it is the homepage sandbox tab number. Keep its UUID `taskId` separately. Run the returned `cloud-watch`, show each real public event and its cursor, and continue the original accepted flow from status. A command log alone is insufficient. No accepted cloud task means no cloud ID or model-execution claim.
|
|
23
23
|
5. For an accepted `flowId`, execute the returned `nextStep` with its required payload. Discover the schema before constructing input. An empty payload is valid only when that operation's schema allows it. Reconnect and refresh only the original flow. A changed snapshot is a binding failure to investigate; do not invent a fresh successful baseline, rebuild a flow to evade it, or erase the old failure. If a fresh scope proof returns `ENTROROVE_SCOPE_PLANNING_REASSESSMENT_REQUIRED` before plan freeze, follow its explicit `workflow-scope-reassess` recovery and retry the original complete request; a local reassessment or snapshot refresh alone does not renew the accepted gate. If the terminal flow truly owns a stale task slot, use the explicit `flow-abandon` lifecycle operation described below before routing the original task again.
|
|
24
|
-
6. All governed mutations use the original authority: `local guarded-write`, `local guarded-delete`, `local guarded-move`, or the original frozen approved runner plan. Native patches, scripts, terminal writes, database mutations and unknown MCP operations must not substitute for the controlled entry. User-authorized task messages use the host's message tool and remain subject to host approval; sending a report does not change the flow or grant execution authority. **A failed gate or unknown outcome must never be replaced with another executor or reported as local success.** Query the original request read-only, repair the cause, and obtain the original flow's valid authority before proceeding.
|
|
24
|
+
6. All governed mutations use the original authority: `local guarded-write`, `local guarded-delete`, `local guarded-move`, or the original frozen approved runner plan. Native patches, scripts, terminal writes, database mutations and unknown MCP operations must not substitute for the controlled entry. User-authorized task messages use the host's message tool and remain subject to host approval; sending a report does not change the flow or grant execution authority. **A failed gate or unknown outcome must never be replaced with another executor or reported as local success.** Query the original request read-only, repair the cause, and obtain the original flow's valid authority before proceeding. If the original scope challenge preparation is absent from authenticated status, `scope-amend` may use the discovered `scope-challenge-retire` lifecycle operation. The server validates the same approved proposal and receiver, atomically fences only an unadmitted challenge, and records a signed retirement without executing that challenge. Recover and acknowledge the original retirement result with GET before a fresh challenge attempt; retain both requests and all failures. An admitted request, issued nonce, scope application or mutation has no missing-challenge retirement path. An uncertain retirement itself permits only its original GET.
|
|
25
25
|
For a member scope, follow a returned `localAction` before submitting execution evidence. `mutation-connect` obtains only the original task's limited editing pass; it keeps unresolved loader files and project configuration read only. After actual writes, refresh the original scope and run `mutation-settle`. Use the returned `previousPassId` for further edits. In a signed member-local plan, `workflow-observe-step` runs the original approved command and tests without a validator private key. A server-signed `localAcceptance` advances local dependencies; a failed test does not. Preserve the raw observation and its absent independent receipt. See [IDE-EXECUTION.md](IDE-EXECUTION.md#local-ide-acceptance).
|
|
26
26
|
7. Read [REUSE-DISCIPLINE.md](REUSE-DISCIPLINE.md) when modifying an existing system. The comparison baseline is the existing implementation in that same repository. It never means another project, another account, or copying external systems.
|
|
27
27
|
8. Read [DELIVERY-BOUNDARIES.md](DELIVERY-BOUNDARIES.md) before claiming a delivered stage or showing consumption. A delivered stage requires a clean boundary and uses `delivered-stage-N`. Blocked or unknown work can wait with recovery data intact; it is not a delivered stage. Display consumption only with a measured baseline.
|
|
@@ -31,6 +31,10 @@ When explicitly enabled and selected, the Codex guard preserves this binding ove
|
|
|
31
31
|
|
|
32
32
|
After the same real blocked result occurs twice, diagnose it and use its supported recovery path. Do not send a third identical execution call. Read-only status and checkpoint acknowledgements do not count as execution progress. A blocked task may wait with its original flow, queue and recovery data intact; Stop must not force repeated automatic continuation of the same blocked turn.
|
|
33
33
|
|
|
34
|
+
Chinese and English explicit stop decisions and exit acknowledgements are supported, including `From now on, do not use the skill to execute the remaining tasks.` Keep the name `Entrorove` unchanged in every language. Preserve the original source, task-list order, approvals and all failure records. Ending the current governance session neither cancels nor completes the remaining business work. See the complete bilingual declaration in [IDE-EXECUTION.md](IDE-EXECUTION.md). Questions about translating or adapting that declaration, quotations, screenshots, conditions and failure reports do not select exit.
|
|
35
|
+
|
|
36
|
+
Direct SDK sessions may have no IDE record. After an actual user exit decision, `entrorove exit` records only an inactive exit marker for the current supported host/session and physical directory; it creates no flow, task, approval or receipt. Existing repository bindings and history remain, and unsafe or invalid records still reject exit explicitly.
|
|
37
|
+
|
|
34
38
|
If the original flow is terminal and must release its task slot, invoke the discovered `flow-cancel` or `flow-abandon` operation with that same `flowId`. `flow-cancel` records an owner cancellation and keeps the task bound for read-only recovery. `flow-abandon` records the termination, clears acceptance and checkpoint state, unbinds the task and returns `nextStep.operation: "start"` so the original task can be routed again. These operations are available through the server and `local flow-cancel` / `local flow-abandon`; they never reopen the old flow.
|
|
35
39
|
|
|
36
40
|
When a new user requirement arrives while the original task is unfinished, store its exact text in the private IDE queue. Use `queue` with `{"action":"status"}` to inspect it, then `{"action":"route","messageId":"...","mode":"queued"|"interrupt","priority":"background"|"normal"|"high"|"urgent"}`. An urgent interrupt is admitted only at a completed controlled-call checkpoint; it saves the original flow, task UUID, homepage number and request ID. After the inserted task reaches a verified end acknowledgement, the queue restores that exact parent context and its next operation. Unclassified text and unresolved calls block dependent execution, not waiting for recovery. Unknown outcomes permit only the original read-only recovery. Queue state is bounded, account/session/repository-bound and survives compaction and process restart; it cannot wake a powered-off host.
|
package/cli-input.mjs
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { MODEL_IMAGE_LIMITS } from './model-images.mjs';
|
|
2
|
+
|
|
3
|
+
export async function readCliInput(input, args) {
|
|
4
|
+
const maxBytes = ['cloud-task','invoke'].includes(args[0]) ? MODEL_IMAGE_LIMITS.requestBytes : 1_000_000;
|
|
5
|
+
let bytes = 0;
|
|
6
|
+
const chunks = [];
|
|
7
|
+
for await (const chunk of input) {
|
|
8
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
9
|
+
bytes += buffer.length;
|
|
10
|
+
if (bytes > maxBytes) throw new Error('Input is too large');
|
|
11
|
+
chunks.push(buffer);
|
|
12
|
+
}
|
|
13
|
+
let value;
|
|
14
|
+
try { value = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(Buffer.concat(chunks))); }
|
|
15
|
+
catch { throw new Error('Input must be valid JSON'); }
|
|
16
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Input must be a JSON object');
|
|
17
|
+
if (bytes > 1_000_000 && args[0] === 'invoke'
|
|
18
|
+
&& !(value.operation === 'cloud-task' || value.operation === 'start' && Array.isArray(value.images) && value.images.length)) {
|
|
19
|
+
throw new Error('Input is too large');
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
package/cli.mjs
CHANGED
|
@@ -23,6 +23,7 @@ import { assertIdeExecution, ideGuardBinding, settleBreakGlassSession } from './
|
|
|
23
23
|
import { writeBreakGlass } from './break-glass.mjs';
|
|
24
24
|
import { exitIdeSession } from './ide-exit.mjs';
|
|
25
25
|
import { installOptions } from './ide-command.mjs';
|
|
26
|
+
import { readCliInput } from './cli-input.mjs';
|
|
26
27
|
const markerName = '.entrorove-managed.json';
|
|
27
28
|
const installFiles = ['SKILL.md', 'CAPABILITY-MAP.md', 'AGREEMENT.md', 'manifest.json', 'IDE-EXECUTION.md', 'REUSE-DISCIPLINE.md', 'DELIVERY-BOUNDARIES.md'];
|
|
28
29
|
const markerSchema = 'entrorove.managed-install/1.0';
|
|
@@ -288,15 +289,8 @@ export async function uninstall({ home = os.homedir() } = {}) {
|
|
|
288
289
|
}
|
|
289
290
|
|
|
290
291
|
async function stdinJson() {
|
|
291
|
-
let raw = '';
|
|
292
|
-
for await (const chunk of process.stdin) {
|
|
293
|
-
raw += chunk;
|
|
294
|
-
if (raw.length > 1_000_000) throw new Error('Input is too large');
|
|
295
|
-
}
|
|
296
|
-
let value;
|
|
297
|
-
try { value = JSON.parse(raw); } catch { throw new Error('Input must be valid JSON'); }
|
|
298
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Input must be a JSON object');
|
|
299
292
|
const args = process.argv.slice(2);
|
|
293
|
+
const value = await readCliInput(process.stdin, args);
|
|
300
294
|
if (!['configure','agreement','review'].includes(args[0])) await assertIdeExecution(args, value);
|
|
301
295
|
return value;
|
|
302
296
|
}
|
package/cloud-task-client.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { parseTransportResponse, requestTransport, trustedOrigin } from './remot
|
|
|
4
4
|
import { displayIdeEvent, ideClientHeaders, ideTaskIdentity } from './ide-presentation.mjs';
|
|
5
5
|
import { verifyAutomaticFeedback } from './automatic-feedback-client.mjs';
|
|
6
6
|
import { sandboxTaskNumber } from './task-identity.mjs';
|
|
7
|
+
import { prepareCloudImages } from './cloud-task-images.mjs';
|
|
7
8
|
|
|
8
9
|
const CLOUD_TASK_SCHEMA = 'entrorove.cloud-task/1.0';
|
|
9
10
|
const CLOUD_STATUS_SCHEMA = 'entrorove.cloud-status/1.0';
|
|
@@ -38,7 +39,11 @@ function validateInput(input) {
|
|
|
38
39
|
if (input.task !== undefined && (!input.task || typeof input.task !== 'object' || Array.isArray(input.task) || input.task.goal !== input.goal)) {
|
|
39
40
|
throw new Error('Cloud task must preserve the original task goal');
|
|
40
41
|
}
|
|
42
|
+
if (input.images !== undefined && (!Array.isArray(input.images) || input.images.length > 4)) {
|
|
43
|
+
throw new Error('cloud-task images must be an array of at most four images');
|
|
44
|
+
}
|
|
41
45
|
return { goal: input.goal, ...(input.ideTaskId === undefined ? {} : { ideTaskId: input.ideTaskId }),
|
|
46
|
+
...(input.images === undefined ? {} : { images: input.images }),
|
|
42
47
|
...(input.task === undefined ? {} : { task: input.task }) };
|
|
43
48
|
}
|
|
44
49
|
|
|
@@ -88,7 +93,7 @@ export async function runCloudTask(input, dependencies) {
|
|
|
88
93
|
const ideTaskId = ideTaskIdentity(task.ideTaskId);
|
|
89
94
|
if (ideTaskId) task.ideTaskId = ideTaskId;
|
|
90
95
|
validateDependencies(dependencies);
|
|
91
|
-
|
|
96
|
+
let id = requestId(task);
|
|
92
97
|
const choice = input.sandboxChoice ?? input.answer;
|
|
93
98
|
await dependencies.install();
|
|
94
99
|
if (!await dependencies.agreementAccepted(dependencies.home)) {
|
|
@@ -99,9 +104,12 @@ export async function runCloudTask(input, dependencies) {
|
|
|
99
104
|
});
|
|
100
105
|
if (choice === 'local') return envelope(task, 'skipped', {
|
|
101
106
|
reason: 'task', executionIsolation: 'local', message: '已退出云端沙箱,采用本地方案继续当前任务。',
|
|
102
|
-
continuation: { goal: task.goal,...(task.ideTaskId ? { ideTaskId: task.ideTaskId } : {})
|
|
107
|
+
continuation: { goal: task.goal,...(task.ideTaskId ? { ideTaskId: task.ideTaskId } : {}),
|
|
108
|
+
...(task.images === undefined ? {} : { images: task.images }) },
|
|
103
109
|
});
|
|
104
110
|
|
|
111
|
+
if (task.images !== undefined) task.images = await prepareCloudImages(task.images);
|
|
112
|
+
id = requestId(task);
|
|
105
113
|
const token = await dependencies.readCredential(dependencies.home);
|
|
106
114
|
const origin = trustedOrigin(dependencies.apiOrigin());
|
|
107
115
|
const metadata = { kind: 'remote', operation: 'cloud-task', requestId: id };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { constants } from 'node:fs';
|
|
2
|
+
import { open } from 'node:fs/promises';
|
|
3
|
+
import { isAbsolute } from 'node:path';
|
|
4
|
+
import { inspectModelImage, MODEL_IMAGE_LIMITS, normalizeModelImages } from './model-images.mjs';
|
|
5
|
+
|
|
6
|
+
async function readImage(path) {
|
|
7
|
+
if (typeof path !== 'string' || !isAbsolute(path)) throw new Error('Image path must be an explicit absolute file path');
|
|
8
|
+
let file;
|
|
9
|
+
try {
|
|
10
|
+
file = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
11
|
+
const before = await file.stat();
|
|
12
|
+
if (!before.isFile() || before.size < 26 || before.size > MODEL_IMAGE_LIMITS.perImageBytes) throw new Error();
|
|
13
|
+
const bytes = Buffer.alloc(before.size);
|
|
14
|
+
let offset = 0;
|
|
15
|
+
while (offset < bytes.length) {
|
|
16
|
+
const result = await file.read(bytes, offset, bytes.length - offset, offset);
|
|
17
|
+
if (result.bytesRead === 0) throw new Error();
|
|
18
|
+
offset += result.bytesRead;
|
|
19
|
+
}
|
|
20
|
+
const after = await file.stat();
|
|
21
|
+
if (before.size !== after.size || before.mtimeMs !== after.mtimeMs || before.ctimeMs !== after.ctimeMs) throw new Error();
|
|
22
|
+
return bytes;
|
|
23
|
+
} catch { throw new Error('Input image must be an unchanged readable regular file within the image byte limit'); }
|
|
24
|
+
finally { await file?.close(); }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Only explicitly attached paths are read, after the task's cloud choice. */
|
|
28
|
+
export async function prepareCloudImages(value = []) {
|
|
29
|
+
if (!Array.isArray(value) || value.length > MODEL_IMAGE_LIMITS.count) throw new Error('At most four input images are supported');
|
|
30
|
+
const inputs = [];
|
|
31
|
+
for (const image of value) {
|
|
32
|
+
if (image && typeof image === 'object' && !Array.isArray(image) && 'path' in image) {
|
|
33
|
+
if (Object.keys(image).some(key => !['path','detail'].includes(key))) throw new Error('Image path input has unsupported fields');
|
|
34
|
+
const bytes = await readImage(image.path), { mimeType } = inspectModelImage(bytes);
|
|
35
|
+
inputs.push({ url: `data:${mimeType};base64,${bytes.toString('base64')}`, ...(image.detail === undefined ? {} : { detail: image.detail }) });
|
|
36
|
+
} else inputs.push(image);
|
|
37
|
+
}
|
|
38
|
+
return normalizeModelImages(inputs).map(({ url, detail }) => ({ url, detail }));
|
|
39
|
+
}
|
package/ide-exit.mjs
CHANGED
|
@@ -1,26 +1,66 @@
|
|
|
1
1
|
import os from 'node:os';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import {
|
|
3
|
+
import { promises as fs } from 'node:fs';
|
|
4
|
+
import { withStateLock } from './local-state-lock.mjs';
|
|
5
|
+
import { ideDirectory, ideDigest, ideError, ideGuardBinding, readIdeJson, writeIdeJson, verifiedIdeGuards } from './ide-session.mjs';
|
|
4
6
|
|
|
5
7
|
const exactExit = /^(?:(?:请)?(?:停止使用|退出)\s*\$?entrorove|(?:stop using|exit)\s+\$?entrorove)[.!。!]?$/iu;
|
|
6
8
|
const naturalExit = /(?:^|[,,。;;!!\n])\s*(?:从现在开始|接下来|今后)?\s*(?:停止使用|不再使用|不使用)\s*(?:entrorove(?:\s*技能)?|(?:这个|该)?技能)\s*(?:(?:来)?(?:执行|处理|完成|继续)[^,,。;;!!\n]{0,40}(?:任务|工作))?[。!!]?\s*$/iu;
|
|
9
|
+
const englishExit = /^(?:please[,:]?\s+)?(?:from\s+now\s+on[,:]?\s+)?(?:stop\s+using|do\s+not\s+use|no\s+longer\s+use|I\s+will\s+no\s+longer\s+use)\s+(?:\$?entrorove(?:\s+skill)?|(?:this|the)\s+skill)(?:\s+(?:for|to\s+(?:execute|perform|handle|complete|continue))\s+(?:the\s+)?(?:remaining\s+(?:tasks|work)|rest\s+of\s+the\s+(?:tasks|work)))?(?:\s+(?:from\s+now\s+on|anymore|any\s+longer))?[.!]?$/iu;
|
|
10
|
+
export const IDE_EXIT_DECISIONS = Object.freeze({
|
|
11
|
+
zh: '用户明确决定:错误问题发送给技能修改人员,从现在开始不使用技能来执行剩余任务。仅结束本轮Entrorove治理;保留源码、原清单顺序、审批和全部失败记录,不代表剩余业务任务取消或完成。',
|
|
12
|
+
en: 'The user has explicitly decided: report the errors to the skill maintainers and stop using the skill for the remaining tasks from now on. End only the current Entrorove governance session; preserve the source code, the original task-list order, approvals, and all failure records. This does not mean that the remaining business tasks are cancelled or completed.',
|
|
13
|
+
});
|
|
14
|
+
const decisionKey = text => text.normalize('NFC').replace(/\s+/gu, '').toLowerCase();
|
|
15
|
+
const decisionKeys = new Set(Object.values(IDE_EXIT_DECISIONS).map(decisionKey));
|
|
16
|
+
const quotedOrConditional = /[`"'“”‘’「」『』<>??]|如果|假如|假设|示例|截图|引用|\b(?:if|unless|whether|suppose|assuming|example|screenshot|quoted?|translate|translation)\b/iu;
|
|
7
17
|
export function explicitIdeExit(prompt) {
|
|
8
|
-
if (
|
|
9
|
-
|
|
18
|
+
if (typeof prompt !== 'string') return false;
|
|
19
|
+
const text = prompt.trim().replace(/\bdon['’]t\b/giu, 'do not');
|
|
20
|
+
if (quotedOrConditional.test(text)) return false;
|
|
21
|
+
return exactExit.test(text) || naturalExit.test(text) || englishExit.test(text) || decisionKeys.has(decisionKey(text));
|
|
22
|
+
}
|
|
23
|
+
export function ideExitNotice(prompt) {
|
|
24
|
+
return /\p{Script=Han}/u.test(prompt)
|
|
25
|
+
? '用户明确退出本轮 Entrorove 管理;源码、原清单顺序、审批和全部失败记录均保留。剩余业务未取消或完成,未声明验收通过或评分为成功。'
|
|
26
|
+
: 'The user explicitly ended this Entrorove governance session. The source code, original task-list order, approvals, and all failure records are preserved. Remaining business work is neither cancelled nor completed, and no acceptance or success score is claimed.';
|
|
10
27
|
}
|
|
11
28
|
export function markIdeExited(state, source) {
|
|
12
29
|
if (state.phase !== 'user-exited') state.exitPhase = state.phase;
|
|
13
30
|
state.active = false; state.phase = 'user-exited';
|
|
14
31
|
state.exitDecision = { source, observedAt: new Date().toISOString() };
|
|
15
32
|
}
|
|
16
|
-
|
|
33
|
+
async function exitState(file, sessionId, cwd) {
|
|
34
|
+
let state;
|
|
35
|
+
try { state = await readIdeJson(file); }
|
|
36
|
+
catch { throw ideError('ENTROROVE_IDE_STORE', 'Current IDE exit state is not a readable private record'); }
|
|
37
|
+
if (state) {
|
|
38
|
+
if (state.schemaVersion !== 'entrorove.ide-session/1.0' || state.sessionId !== sessionId
|
|
39
|
+
|| !path.isAbsolute(state.repositoryRoot ?? '')) throw ideError('ENTROROVE_IDE_IDENTITY', 'Exit cannot replace an invalid existing IDE session');
|
|
40
|
+
return state;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
await fs.lstat(file);
|
|
44
|
+
throw ideError('ENTROROVE_IDE_IDENTITY', 'Exit cannot replace an invalid existing IDE session');
|
|
45
|
+
} catch (cause) { if (cause.code !== 'ENOENT') throw cause; }
|
|
46
|
+
if (!path.isAbsolute(cwd ?? '')) throw ideError('ENTROROVE_IDE_IDENTITY', 'Exit requires the current physical repository directory');
|
|
47
|
+
let repositoryRoot;
|
|
48
|
+
try { repositoryRoot = await fs.realpath(cwd); if (!(await fs.stat(repositoryRoot)).isDirectory()) throw new Error(); }
|
|
49
|
+
catch { throw ideError('ENTROROVE_IDE_IDENTITY', 'Exit requires the current physical repository directory'); }
|
|
50
|
+
return { schemaVersion: 'entrorove.ide-session/1.0', sessionId, repositoryRoot, active: false,
|
|
51
|
+
phase: 'entry', seen: [], blockStreak: 0 };
|
|
52
|
+
}
|
|
53
|
+
export async function exitIdeSession({ home = os.homedir(), environment = process.env, cwd = process.cwd() } = {}) {
|
|
17
54
|
const { host, sessionId } = ideGuardBinding(environment);
|
|
18
|
-
if (!Object.hasOwn(verifiedIdeGuards, host) || !sessionId
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
55
|
+
if (!Object.hasOwn(verifiedIdeGuards, host) || !sessionId || !/^[A-Za-z0-9_-]{1,200}$/u.test(sessionId)) {
|
|
56
|
+
throw ideError('ENTROROVE_IDE_IDENTITY', 'Exit requires the current verified IDE session identity');
|
|
57
|
+
}
|
|
58
|
+
const directory = await ideDirectory(home, true), file = path.join(directory, `session-${ideDigest(sessionId)}.json`);
|
|
59
|
+
return withStateLock(file, async () => {
|
|
60
|
+
const current = await exitState(file, sessionId, cwd);
|
|
22
61
|
markIdeExited(current, 'cli');
|
|
62
|
+
await writeIdeJson(file, { ...current, observedAt: new Date().toISOString() });
|
|
23
63
|
return { schemaVersion: 'entrorove.ide-exit/1.0', status: 'user-exited', sessionId,
|
|
24
64
|
offline: true, network: false, hook: 'not-required', originalFlowPreserved: true, claimsCompletion: false };
|
|
25
|
-
}
|
|
65
|
+
});
|
|
26
66
|
}
|
package/ide-hook.mjs
CHANGED
|
@@ -13,7 +13,7 @@ import { parseIdeResults, consumeIdeResults } from './ide-results.mjs';
|
|
|
13
13
|
import { verifyIdeLocalAcceptance } from './ide-local-acceptance.mjs';
|
|
14
14
|
import { registerIdeRequirement, ideQueueContext, unclassifiedIdeRequirements,
|
|
15
15
|
advanceIdeQueue, checkQueuedStart, unfinishedIdeQueue } from './ide-queue.mjs';
|
|
16
|
-
import { explicitIdeExit, markIdeExited } from './ide-exit.mjs';
|
|
16
|
+
import { explicitIdeExit, ideExitNotice, markIdeExited } from './ide-exit.mjs';
|
|
17
17
|
import { appliesIdeHook, hasIdeHookSelection, selectsIdeHook, ideUserPrompt as userPrompt } from './ide-selection.mjs';
|
|
18
18
|
|
|
19
19
|
const output = (event, extra = {}) => ({ hookSpecificOutput: { hookEventName: event, ...extra } });
|
|
@@ -251,7 +251,7 @@ export async function runIdeHook(event, { home } = {}) {
|
|
|
251
251
|
if (name === 'PreToolUse') state.enforcement = 'enforced';
|
|
252
252
|
if (name === 'UserPromptSubmit' && explicitIdeExit(userPrompt(event))) {
|
|
253
253
|
markIdeExited(state, 'user-prompt');
|
|
254
|
-
return output(name, { additionalContext:
|
|
254
|
+
return output(name, { additionalContext: ideExitNotice(userPrompt(event)) });
|
|
255
255
|
}
|
|
256
256
|
if (selected) {
|
|
257
257
|
state.active = true;
|
package/local-contracts.mjs
CHANGED
|
@@ -38,7 +38,10 @@ const write = { ...step, taskId: identifier, generation: integer(1), executionAt
|
|
|
38
38
|
add('gate-verify', write, ['repositoryRoot', 'flowId', 'stepId', 'gatePassPath', 'targetPath']);
|
|
39
39
|
add('discipline-review|discipline-check', write, ['repositoryRoot', 'flowId', 'stepId', 'gatePassPath', 'targetPath']);
|
|
40
40
|
add('guarded-delete|guarded-move', { ...write, declaration: DISCIPLINE_DECLARATION_SCHEMA }, ['repositoryRoot', 'flowId', 'stepId', 'gatePassPath', 'targetPath', 'declaration']);
|
|
41
|
-
add('guarded-write', { ...write, content: {},
|
|
41
|
+
add('guarded-write', { ...write, content: {}, contentBase64: { type: 'string', maxLength: 16_000_000,
|
|
42
|
+
pattern: '^[A-Za-z0-9+/]*={0,2}$' }, expectedSha256: nullable(sha256), declaration: DISCIPLINE_DECLARATION_SCHEMA }, ['repositoryRoot', 'flowId', 'stepId', 'gatePassPath', 'targetPath']);
|
|
43
|
+
schemas['guarded-write'].oneOf = [{ required: ['content'], not: { required: ['contentBase64'] } },
|
|
44
|
+
{ required: ['contentBase64'], not: { required: ['content'] } }];
|
|
42
45
|
schemas['guarded-write'] = { oneOf: [schemas['guarded-write'], object({ ...step, targetPath: relativePath, content: {},
|
|
43
46
|
expectedSha256: nullable(sha256), handoffId: ledgerIdentifier, epoch: integer(1, 2147483647),
|
|
44
47
|
handoffPass: HANDOFF_WRITE_PASS_SCHEMA, provisionalLease: HANDOFF_PROVISIONAL_LEASE_SCHEMA },
|
|
@@ -17,7 +17,8 @@ function declaredWrite(input, pass, baseline) {
|
|
|
17
17
|
if (!planned || canonical(planned) !== canonical(supplied)) needs('Write declaration must match the original cloud-signed execution plan');
|
|
18
18
|
}
|
|
19
19
|
if (planned && !['modify', 'create', 'format'].includes(planned.kind)) needs('The declared physical action must use its matching guarded entry point');
|
|
20
|
-
|
|
20
|
+
const development = pass.writeDelegation?.purpose === 'planned-file-modify' && baseline.exists && planned?.kind === 'create';
|
|
21
|
+
if (planned && planned.kind !== 'format' && planned.kind !== expectedKind && !development) needs('Declared write kind differs from the actual snapshot baseline');
|
|
21
22
|
return planned;
|
|
22
23
|
}
|
|
23
24
|
function requireException(intent, kind, message) {
|
|
@@ -70,12 +71,22 @@ function checkReuse(review, candidate, target, intent, parallelPaths) {
|
|
|
70
71
|
empty: duplicates.length === 0, complete: review.scanComplete };
|
|
71
72
|
}
|
|
72
73
|
export async function disciplineWritePreflight(input, value, content) {
|
|
74
|
+
const candidateDigest = sha256(content);
|
|
75
|
+
// Analyze decoded source bytes through the same original source checks.
|
|
76
|
+
// Binary assets remain in the exact-byte repository and M/S integrity chain.
|
|
77
|
+
if (Buffer.isBuffer(content)) {
|
|
78
|
+
try { content = new TextDecoder('utf-8', { ignoreBOM: true,
|
|
79
|
+
fatal: /\.(?:[cm]?[jt]sx?|php|py|java|rs|vue|svelte|css)$/i.test(value.target) }).decode(content); }
|
|
80
|
+
catch { mutationFail14('ENTROROVE_CONTENT_INVALID', 'Source content must contain valid UTF-8 bytes'); }
|
|
81
|
+
}
|
|
73
82
|
const { pass, target, binding, snapshot, ledger } = value;
|
|
74
83
|
const review = await readDisciplineSnapshot(snapshot), baseline = snapshot.manifest.files.find(file => file.path === target);
|
|
75
84
|
await assertDisciplineRepository(input, binding.root, review, ledger);
|
|
76
85
|
const intent = declaredWrite(input, pass, baseline);
|
|
77
86
|
const original = baseline.exists ? (await readSnapshotCopy14(input, snapshot.snapshotRoot, snapshot.manifest, target)).content.toString('utf8') : '';
|
|
78
|
-
if (pass.writeDelegation)
|
|
87
|
+
if (pass.writeDelegation?.purpose === 'planned-file-create') {
|
|
88
|
+
if (baseline.exists || intent?.kind !== 'create' || !pass.newFilePaths.includes(target)) needs('Planned creation cannot overwrite an existing source or replace its create role');
|
|
89
|
+
} else if (pass.writeDelegation?.purpose === 'preserved-existing-modify') assertDelegatedSourceModification({ path: target, before: original, after: content });
|
|
79
90
|
const candidate = await disciplineRepositoryObservation(input, binding.root, { analyze: true, overrides: new Map([[target, content]]) });
|
|
80
91
|
const removed = checkRemoved(review, candidate.analysis, target, intent, snapshot.manifest.disciplineReviewDigest, input.declaration);
|
|
81
92
|
checkContracts(review, candidate.analysis, target, intent);
|
|
@@ -83,5 +94,5 @@ export async function disciplineWritePreflight(input, value, content) {
|
|
|
83
94
|
const formatting = baseline.exists ? disciplineFormattingLines(original, content, target) : [];
|
|
84
95
|
if (formatting.length) mutationFail14('ENTROROVE_DISCIPLINE_FORMAT_SCOPE', `Formatting touches lines without functional changes: ${target}:${formatting.join(',')}`);
|
|
85
96
|
return { reviewDigest: snapshot.manifest.disciplineReviewDigest, declaredKind: baseline.exists ? 'modify' : 'create', removed, reuse,
|
|
86
|
-
candidateDigest
|
|
97
|
+
candidateDigest, comparisonScope: review.comparison.scope };
|
|
87
98
|
}
|