@agent-delivery-harness/cli 0.2.0 → 0.5.0
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/LICENSE +104 -201
- package/NOTICE +1 -1
- package/package.json +4 -4
- package/src/boundary.ts +77 -61
- package/src/command-activity.ts +75 -0
- package/src/commands/admit.ts +106 -0
- package/src/commands/check.ts +13 -2
- package/src/commands/emit-review-evidence.ts +38 -0
- package/src/commands/emit.ts +80 -12
- package/src/commands/gate.ts +33 -8
- package/src/commands/maintain.ts +1 -0
- package/src/commands/managed.ts +1 -0
- package/src/commands/prepare.ts +123 -22
- package/src/commands/record.ts +98 -3
- package/src/commands/resume.ts +53 -0
- package/src/commands/review-context.ts +19 -0
- package/src/commands/runs.ts +415 -30
- package/src/commands/save-context.ts +51 -0
- package/src/commands/submit-evidence.ts +46 -7
- package/src/commands/verify.ts +29 -8
- package/src/declared-checks.ts +50 -0
- package/src/index.ts +14 -1
- package/src/main.ts +20 -47
- package/src/ordinary-context.ts +99 -0
- package/src/provider-rails.ts +23 -704
- package/src/record-retention.ts +325 -0
- package/src/review-evidence.ts +284 -0
- package/src/run-archive-commands.ts +164 -0
- package/src/run-archive.ts +127 -0
- package/src/run-artifact-commands.ts +163 -0
- package/src/run-attachments.ts +112 -0
- package/src/run-export.ts +164 -0
- package/src/run-live.ts +102 -0
- package/src/run-projection.ts +129 -11
- package/src/run-server.ts +394 -149
- package/src/run-surface.ts +81 -8
- package/src/run-view-command.ts +77 -0
- package/src/run-view-html.ts +138 -0
- package/src/run-view-record.ts +119 -0
- package/src/run-view.ts +343 -0
package/LICENSE
CHANGED
|
@@ -1,202 +1,105 @@
|
|
|
1
|
+
# Functional Source License, Version 1.1, ALv2 Future License
|
|
1
2
|
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
the Derivative Works; and
|
|
106
|
-
|
|
107
|
-
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
-
distribution, then any Derivative Works that You distribute must
|
|
109
|
-
include a readable copy of the attribution notices contained
|
|
110
|
-
within such NOTICE file, excluding those notices that do not
|
|
111
|
-
pertain to any part of the Derivative Works, in at least one
|
|
112
|
-
of the following places: within a NOTICE text file distributed
|
|
113
|
-
as part of the Derivative Works; within the Source form or
|
|
114
|
-
documentation, if provided along with the Derivative Works; or,
|
|
115
|
-
within a display generated by the Derivative Works, if and
|
|
116
|
-
wherever such third-party notices normally appear. The contents
|
|
117
|
-
of the NOTICE file are for informational purposes only and
|
|
118
|
-
do not modify the License. You may add Your own attribution
|
|
119
|
-
notices within Derivative Works that You distribute, alongside
|
|
120
|
-
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
-
that such additional attribution notices cannot be construed
|
|
122
|
-
as modifying the License.
|
|
123
|
-
|
|
124
|
-
You may add Your own copyright statement to Your modifications and
|
|
125
|
-
may provide additional or different license terms and conditions
|
|
126
|
-
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
-
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
-
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
-
the conditions stated in this License.
|
|
130
|
-
|
|
131
|
-
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
-
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
-
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
-
this License, without any additional terms or conditions.
|
|
135
|
-
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
-
the terms of any separate license agreement you may have executed
|
|
137
|
-
with Licensor regarding such Contributions.
|
|
138
|
-
|
|
139
|
-
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
-
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
-
except as required for reasonable and customary use in describing the
|
|
142
|
-
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
-
|
|
144
|
-
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
-
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
-
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
-
implied, including, without limitation, any warranties or conditions
|
|
149
|
-
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
-
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
-
appropriateness of using or redistributing the Work and assume any
|
|
152
|
-
risks associated with Your exercise of permissions under this License.
|
|
153
|
-
|
|
154
|
-
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
-
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
-
unless required by applicable law (such as deliberate and grossly
|
|
157
|
-
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
-
liable to You for damages, including any direct, indirect, special,
|
|
159
|
-
incidental, or consequential damages of any character arising as a
|
|
160
|
-
result of this License or out of the use or inability to use the
|
|
161
|
-
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
-
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
-
other commercial damages or losses), even if such Contributor
|
|
164
|
-
has been advised of the possibility of such damages.
|
|
165
|
-
|
|
166
|
-
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
-
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
-
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
-
or other liability obligations and/or rights consistent with this
|
|
170
|
-
License. However, in accepting such obligations, You may act only
|
|
171
|
-
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
-
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
-
defend, and hold each Contributor harmless for any liability
|
|
174
|
-
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
-
of your accepting any such warranty or additional liability.
|
|
176
|
-
|
|
177
|
-
END OF TERMS AND CONDITIONS
|
|
178
|
-
|
|
179
|
-
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
-
|
|
181
|
-
To apply the Apache License to your work, attach the following
|
|
182
|
-
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
-
replaced with your own identifying information. (Don't include
|
|
184
|
-
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
-
comment syntax for the file format. We also recommend that a
|
|
186
|
-
file or class name and description of purpose be included on the
|
|
187
|
-
same "printed page" as the copyright notice for easier
|
|
188
|
-
identification within third-party archives.
|
|
189
|
-
|
|
190
|
-
Copyright [yyyy] [name of copyright owner]
|
|
191
|
-
|
|
192
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
-
you may not use this file except in compliance with the License.
|
|
194
|
-
You may obtain a copy of the License at
|
|
195
|
-
|
|
196
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
-
|
|
198
|
-
Unless required by applicable law or agreed to in writing, software
|
|
199
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
-
See the License for the specific language governing permissions and
|
|
202
|
-
limitations under the License.
|
|
3
|
+
## Abbreviation
|
|
4
|
+
|
|
5
|
+
FSL-1.1-ALv2
|
|
6
|
+
|
|
7
|
+
## Notice
|
|
8
|
+
|
|
9
|
+
Copyright 2026 Kwamina Essuah Mensah
|
|
10
|
+
|
|
11
|
+
## Terms and Conditions
|
|
12
|
+
|
|
13
|
+
### Licensor ("We")
|
|
14
|
+
|
|
15
|
+
The party offering the Software under these Terms and Conditions.
|
|
16
|
+
|
|
17
|
+
### The Software
|
|
18
|
+
|
|
19
|
+
The "Software" is each version of the software that we make available under
|
|
20
|
+
these Terms and Conditions, as indicated by our inclusion of these Terms and
|
|
21
|
+
Conditions with the Software.
|
|
22
|
+
|
|
23
|
+
### License Grant
|
|
24
|
+
|
|
25
|
+
Subject to your compliance with this License Grant and the Patents,
|
|
26
|
+
Redistribution and Trademark clauses below, we hereby grant you the right to
|
|
27
|
+
use, copy, modify, create derivative works, publicly perform, publicly display
|
|
28
|
+
and redistribute the Software for any Permitted Purpose identified below.
|
|
29
|
+
|
|
30
|
+
### Permitted Purpose
|
|
31
|
+
|
|
32
|
+
A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
|
|
33
|
+
means making the Software available to others in a commercial product or
|
|
34
|
+
service that:
|
|
35
|
+
|
|
36
|
+
1. substitutes for the Software;
|
|
37
|
+
|
|
38
|
+
2. substitutes for any other product or service we offer using the Software
|
|
39
|
+
that exists as of the date we make the Software available; or
|
|
40
|
+
|
|
41
|
+
3. offers the same or substantially similar functionality as the Software.
|
|
42
|
+
|
|
43
|
+
Permitted Purposes specifically include using the Software:
|
|
44
|
+
|
|
45
|
+
1. for your internal use and access;
|
|
46
|
+
|
|
47
|
+
2. for non-commercial education;
|
|
48
|
+
|
|
49
|
+
3. for non-commercial research; and
|
|
50
|
+
|
|
51
|
+
4. in connection with professional services that you provide to a licensee
|
|
52
|
+
using the Software in accordance with these Terms and Conditions.
|
|
53
|
+
|
|
54
|
+
### Patents
|
|
55
|
+
|
|
56
|
+
To the extent your use for a Permitted Purpose would necessarily infringe our
|
|
57
|
+
patents, the license grant above includes a license under our patents. If you
|
|
58
|
+
make a claim against any party that the Software infringes or contributes to
|
|
59
|
+
the infringement of any patent, then your patent license to the Software ends
|
|
60
|
+
immediately.
|
|
61
|
+
|
|
62
|
+
### Redistribution
|
|
63
|
+
|
|
64
|
+
The Terms and Conditions apply to all copies, modifications and derivatives of
|
|
65
|
+
the Software.
|
|
66
|
+
|
|
67
|
+
If you redistribute any copies, modifications or derivatives of the Software,
|
|
68
|
+
you must include a copy of or a link to these Terms and Conditions and not
|
|
69
|
+
remove any copyright notices provided in or with the Software.
|
|
70
|
+
|
|
71
|
+
### Disclaimer
|
|
72
|
+
|
|
73
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
|
|
74
|
+
IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
|
|
75
|
+
PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
|
|
76
|
+
|
|
77
|
+
IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
|
|
78
|
+
SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
|
|
79
|
+
EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
|
|
80
|
+
|
|
81
|
+
### Trademarks
|
|
82
|
+
|
|
83
|
+
Except for displaying the License Details and identifying us as the origin of
|
|
84
|
+
the Software, you have no right under these Terms and Conditions to use our
|
|
85
|
+
trademarks, trade names, service marks or product names.
|
|
86
|
+
|
|
87
|
+
## Grant of Future License
|
|
88
|
+
|
|
89
|
+
We hereby irrevocably grant you an additional license to use the Software under
|
|
90
|
+
the Apache License, Version 2.0 that is effective on the second anniversary of
|
|
91
|
+
the date we make the Software available. On or after that date, you may use the
|
|
92
|
+
Software under the Apache License, Version 2.0, in which case the following
|
|
93
|
+
will apply:
|
|
94
|
+
|
|
95
|
+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
|
96
|
+
this file except in compliance with the License.
|
|
97
|
+
|
|
98
|
+
You may obtain a copy of the License at
|
|
99
|
+
|
|
100
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
101
|
+
|
|
102
|
+
Unless required by applicable law or agreed to in writing, software distributed
|
|
103
|
+
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
104
|
+
CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
105
|
+
specific language governing permissions and limitations under the License.
|
package/NOTICE
CHANGED
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-delivery-harness/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "Delivery harness CLI: prepare, review-context, submit-evidence, gate, record, verify, check",
|
|
6
|
-
"license": "
|
|
5
|
+
"description": "Delivery harness CLI: admit, prepare, review-context, submit-evidence, gate, record, verify, check",
|
|
6
|
+
"license": "FSL-1.1-ALv2",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "git+https://github.com/kwam1na/agent-delivery-harness.git",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"node": ">=22.6.0"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@agent-delivery-harness/kernel": "0.
|
|
16
|
+
"@agent-delivery-harness/kernel": "0.5.0"
|
|
17
17
|
},
|
|
18
18
|
"exports": {
|
|
19
19
|
".": "./src/index.ts"
|
package/src/boundary.ts
CHANGED
|
@@ -52,6 +52,7 @@ import {
|
|
|
52
52
|
type LiveProviderResult,
|
|
53
53
|
type NonEmptyTuple,
|
|
54
54
|
type ReviewActivationProjection,
|
|
55
|
+
type RunPreparationObservation,
|
|
55
56
|
type WaiverPrompt,
|
|
56
57
|
} from "@agent-delivery-harness/kernel";
|
|
57
58
|
import {
|
|
@@ -60,7 +61,7 @@ import {
|
|
|
60
61
|
type ProviderRailInvocationResult,
|
|
61
62
|
type ProviderRailSession,
|
|
62
63
|
} from "./provider-rails.ts";
|
|
63
|
-
import {
|
|
64
|
+
import { beginCommandObservation, type CommandObservation } from "./command-activity.ts";
|
|
64
65
|
|
|
65
66
|
// ── Exit codes ───────────────────────────────────────────────────────────────
|
|
66
67
|
|
|
@@ -88,7 +89,7 @@ export class CliInterruption extends Error {
|
|
|
88
89
|
* the typed blockers to render; `usage` is an argument or invocation error.
|
|
89
90
|
*/
|
|
90
91
|
export type CommandResult =
|
|
91
|
-
| { readonly kind: "ok"; readonly summary?: string }
|
|
92
|
+
| { readonly kind: "ok"; readonly summary?: string; readonly digest?: string; readonly preparation?: RunPreparationObservation }
|
|
92
93
|
| { readonly kind: "blocked"; readonly blockers: readonly Blocker[] }
|
|
93
94
|
| { readonly kind: "usage"; readonly message: string };
|
|
94
95
|
|
|
@@ -113,6 +114,8 @@ export interface CommandContext {
|
|
|
113
114
|
readonly stdoutIsTTY: boolean;
|
|
114
115
|
/** Positional and flag arguments after the command name. */
|
|
115
116
|
readonly args: readonly string[];
|
|
117
|
+
readonly readStdin?: () => Promise<string>;
|
|
118
|
+
readonly signal?: AbortSignal;
|
|
116
119
|
/**
|
|
117
120
|
* Wires capture and the store from this repo, memoized. Lazy so `--help`
|
|
118
121
|
* wires nothing and so a command owns how it renders a store that will not
|
|
@@ -140,6 +143,17 @@ export interface CommandDescriptor {
|
|
|
140
143
|
/** The blocker `source.id` this command stamps on failures it raises itself. */
|
|
141
144
|
readonly sourceId: string;
|
|
142
145
|
readonly summary: string;
|
|
146
|
+
/**
|
|
147
|
+
* What a lone `--help`/`-h` on this command is answered with, printed by the
|
|
148
|
+
* boundary before anything is loaded. It is the command's own text — its
|
|
149
|
+
* invocation form and, where the command has one, the sentence explaining
|
|
150
|
+
* what its flags decide. Most commands build their own usage errors from it
|
|
151
|
+
* too, but nothing enforces that: `emit-review-evidence`, `maintain`,
|
|
152
|
+
* `managed` and `submit-evidence`'s missing-manifest arm phrase theirs from
|
|
153
|
+
* separate literals, so this member is the help answer and not a
|
|
154
|
+
* single-authority claim over every usage message.
|
|
155
|
+
*/
|
|
156
|
+
readonly usage: string;
|
|
143
157
|
run(context: CommandContext): Promise<CommandResult>;
|
|
144
158
|
}
|
|
145
159
|
|
|
@@ -159,6 +173,8 @@ export interface CommandDescriptor {
|
|
|
159
173
|
export interface ConfigFreeCommandContext {
|
|
160
174
|
readonly rootDir: string;
|
|
161
175
|
readonly env: EnvSnapshot;
|
|
176
|
+
/** Whether omitted payload input would read from an interactive terminal. */
|
|
177
|
+
readonly stdinIsTTY?: boolean;
|
|
162
178
|
/** Positional and flag arguments after the command name. */
|
|
163
179
|
readonly args: readonly string[];
|
|
164
180
|
/** The payload channel: everything on stdin, when a command reads one. */
|
|
@@ -178,6 +194,8 @@ export interface ConfigFreeCommandDescriptor {
|
|
|
178
194
|
readonly name: string;
|
|
179
195
|
readonly sourceId: string;
|
|
180
196
|
readonly summary: string;
|
|
197
|
+
/** As {@link CommandDescriptor.usage}: one help contract across both classes. */
|
|
198
|
+
readonly usage: string;
|
|
181
199
|
/** The discriminator the boundary dispatches on, before any config load. */
|
|
182
200
|
readonly configFree: true;
|
|
183
201
|
run(context: ConfigFreeCommandContext): Promise<CommandResult>;
|
|
@@ -302,13 +320,32 @@ export async function wireRepo(rootDir: string, config: HarnessConfig): Promise<
|
|
|
302
320
|
|
|
303
321
|
// ── The boundary ─────────────────────────────────────────────────────────────
|
|
304
322
|
|
|
305
|
-
|
|
306
|
-
|
|
323
|
+
/**
|
|
324
|
+
* The listing column is computed from the longest registered name, never a
|
|
325
|
+
* constant: a fixed pad width silently fused `emit-review-evidence` (twenty
|
|
326
|
+
* characters) into its own summary, and every later command name long enough
|
|
327
|
+
* to reach the width would have done the same. Two spaces is the separator, so
|
|
328
|
+
* the longest name is still visibly separated from its summary.
|
|
329
|
+
*/
|
|
330
|
+
const NAME_GAP = 2;
|
|
331
|
+
|
|
332
|
+
const USAGE = (commands: readonly AnyCommandDescriptor[]): string => {
|
|
333
|
+
const column = Math.max(0, ...commands.map((command) => command.name.length)) + NAME_GAP;
|
|
334
|
+
return [
|
|
307
335
|
"Usage: delivery-harness <command> [options]",
|
|
308
336
|
"",
|
|
309
337
|
"Commands:",
|
|
310
|
-
...commands.map((command) => ` ${command.name.padEnd(
|
|
338
|
+
...commands.map((command) => ` ${command.name.padEnd(column)}${command.summary}`),
|
|
311
339
|
].join("\n");
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* A lone `--help`/`-h`, and nothing else. Deliberately exact: `--help` among
|
|
344
|
+
* other arguments is an invocation the command itself has to judge, and
|
|
345
|
+
* answering it as help would let a real invocation be silently swallowed.
|
|
346
|
+
*/
|
|
347
|
+
const isHelpRequest = (args: readonly string[]): boolean =>
|
|
348
|
+
args.length === 1 && (args[0] === "--help" || args[0] === "-h");
|
|
312
349
|
|
|
313
350
|
/**
|
|
314
351
|
* THE WRAPPED COMMANDS, NAMED ONE BY ONE.
|
|
@@ -325,61 +362,17 @@ const USAGE = (commands: readonly AnyCommandDescriptor[]): string =>
|
|
|
325
362
|
* fails safe for a store nothing authoritative may read.
|
|
326
363
|
*/
|
|
327
364
|
export const COMPLETION_WRAPPED_COMMANDS: readonly string[] = [
|
|
365
|
+
"admit",
|
|
328
366
|
"check",
|
|
329
367
|
"prepare",
|
|
330
368
|
"review-context",
|
|
369
|
+
"emit-review-evidence",
|
|
331
370
|
"submit-evidence",
|
|
332
371
|
"gate",
|
|
333
372
|
"record",
|
|
334
373
|
"verify",
|
|
335
374
|
];
|
|
336
375
|
|
|
337
|
-
/** The four exit codes, as the closed outcome enum `command.completed` carries. */
|
|
338
|
-
function outcomeOfExit(code: number): "ok" | "policy" | "usage" | "interrupted" {
|
|
339
|
-
if (code === EXIT_OK) return "ok";
|
|
340
|
-
if (code === EXIT_USAGE) return "usage";
|
|
341
|
-
if (code === EXIT_INTERRUPTED) return "interrupted";
|
|
342
|
-
return "policy";
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
/**
|
|
346
|
-
* Appends this invocation's `command.completed`, when — and only when — a run
|
|
347
|
-
* is current for the invoking worktree.
|
|
348
|
-
*
|
|
349
|
-
* BEST-EFFORT, TOTAL, AND SILENT. Every failure is swallowed: an unresolvable
|
|
350
|
-
* repository, a refused pointer, a store that will not accept the append. The
|
|
351
|
-
* caller has already decided the exit code, and a run journal that could
|
|
352
|
-
* change a gate's verdict would be evidence. It is not evidence, so it is not
|
|
353
|
-
* allowed to matter. A refused append still lands one bounded line in the
|
|
354
|
-
* run's note, which the store writes.
|
|
355
|
-
*/
|
|
356
|
-
async function recordCommandCompletion(input: {
|
|
357
|
-
readonly cwd: string;
|
|
358
|
-
readonly command: string;
|
|
359
|
-
readonly exitCode: number;
|
|
360
|
-
readonly durationMs: number;
|
|
361
|
-
}): Promise<void> {
|
|
362
|
-
try {
|
|
363
|
-
const resolved = await resolveRunSurface(input.cwd);
|
|
364
|
-
if (!resolved.ok) return;
|
|
365
|
-
const { store, commonDir, worktreeKey } = resolved.surface;
|
|
366
|
-
const current = await store.current(worktreeKey);
|
|
367
|
-
if (!current.ok || current.runId === undefined) return;
|
|
368
|
-
await store.append(
|
|
369
|
-
current.runId,
|
|
370
|
-
buildRunEvent({
|
|
371
|
-
runId: current.runId,
|
|
372
|
-
commonDir,
|
|
373
|
-
kind: "command.completed",
|
|
374
|
-
role: "cli",
|
|
375
|
-
payload: { command: input.command, outcome: outcomeOfExit(input.exitCode), durationMs: input.durationMs },
|
|
376
|
-
}),
|
|
377
|
-
);
|
|
378
|
-
} catch {
|
|
379
|
-
// Deliberately silent. See the paragraph above.
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
376
|
/**
|
|
384
377
|
* Runs one CLI invocation to an exit code. Total: it maps every command result
|
|
385
378
|
* and every throw to one of the four codes, and renders every failure through
|
|
@@ -403,6 +396,20 @@ export async function runCliBoundary(
|
|
|
403
396
|
return EXIT_USAGE;
|
|
404
397
|
}
|
|
405
398
|
|
|
399
|
+
// HELP IS ANSWERED HERE, AND ONLY HERE. After the descriptor is resolved —
|
|
400
|
+
// so the answer is that command's own usage — and before config loading,
|
|
401
|
+
// wiring, the command observation, or the action. Answering it inside each
|
|
402
|
+
// command would put the answer after the config load the command class
|
|
403
|
+
// performs for it, which is how `gate --help`, `record --help` and
|
|
404
|
+
// `check --help` came to run the gate, the record and the preflight: those
|
|
405
|
+
// commands parse no arguments at all, so a help request reached their action
|
|
406
|
+
// unexamined. A read-only question is answered read-only, for every command
|
|
407
|
+
// in both classes, from one predicate.
|
|
408
|
+
if (isHelpRequest(args)) {
|
|
409
|
+
runtime.stdout(`${descriptor.usage}\n`);
|
|
410
|
+
return EXIT_OK;
|
|
411
|
+
}
|
|
412
|
+
|
|
406
413
|
// CONFIG-FREE COMMANDS ARE DISPATCHED FIRST, before any config load. That
|
|
407
414
|
// ordering is the whole point of the class: `emit` runs in a repository with
|
|
408
415
|
// no `harness.config.ts`, and no other command's `config_unloadable` timing
|
|
@@ -412,15 +419,11 @@ export async function runCliBoundary(
|
|
|
412
419
|
}
|
|
413
420
|
|
|
414
421
|
const startedAt = Date.now();
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
exitCode: code,
|
|
421
|
-
durationMs: Date.now() - startedAt,
|
|
422
|
-
});
|
|
423
|
-
}
|
|
422
|
+
const observation = COMPLETION_WRAPPED_COMMANDS.includes(descriptor.name)
|
|
423
|
+
? await beginCommandObservation(runtime.cwd, descriptor.name) : undefined;
|
|
424
|
+
let completed: Extract<CommandResult, { kind: "ok" }> | undefined;
|
|
425
|
+
const code = await runConfiguredCommand(descriptor, args, runtime, value => { completed = value; }, observation);
|
|
426
|
+
await observation?.finish(code, Date.now() - startedAt, completed?.digest, completed?.preparation);
|
|
424
427
|
return code;
|
|
425
428
|
}
|
|
426
429
|
|
|
@@ -434,6 +437,7 @@ async function runConfigFreeCommand(
|
|
|
434
437
|
const result = await descriptor.run({
|
|
435
438
|
rootDir: runtime.cwd,
|
|
436
439
|
env: runtime.env,
|
|
440
|
+
stdinIsTTY: runtime.stdinIsTTY,
|
|
437
441
|
args,
|
|
438
442
|
readStdin: runtime.readStdin ?? (async () => ""),
|
|
439
443
|
write: (text) => runtime.stdout(`${text}\n`),
|
|
@@ -472,6 +476,8 @@ async function runConfiguredCommand(
|
|
|
472
476
|
descriptor: CommandDescriptor,
|
|
473
477
|
args: readonly string[],
|
|
474
478
|
runtime: CliRuntime,
|
|
479
|
+
observeSuccess: (result: Extract<CommandResult, { kind: "ok" }>) => void,
|
|
480
|
+
observation?: CommandObservation,
|
|
475
481
|
): Promise<number> {
|
|
476
482
|
const loadConfig = runtime.loadConfig ?? importHarnessConfig;
|
|
477
483
|
const artifacts = runtime.artifacts ?? createArtifactsPort();
|
|
@@ -485,6 +491,12 @@ async function runConfiguredCommand(
|
|
|
485
491
|
return wiringPromise;
|
|
486
492
|
};
|
|
487
493
|
|
|
494
|
+
if (observation?.needsCandidate) {
|
|
495
|
+
try {
|
|
496
|
+
const capture = await (await wire()).captureCandidate();
|
|
497
|
+
if (capture.ok) await observation.start(capture.candidate.treeSha);
|
|
498
|
+
} catch { /* Missing candidate observations never block execution. */ }
|
|
499
|
+
}
|
|
488
500
|
const context: CommandContext = {
|
|
489
501
|
rootDir: runtime.cwd,
|
|
490
502
|
config,
|
|
@@ -494,11 +506,13 @@ async function runConfiguredCommand(
|
|
|
494
506
|
stdoutIsTTY: runtime.stdoutIsTTY,
|
|
495
507
|
args,
|
|
496
508
|
wire,
|
|
509
|
+
readStdin: runtime.readStdin ?? (async () => ""),
|
|
497
510
|
artifacts,
|
|
511
|
+
...(runtime.signal === undefined ? {} : { signal: runtime.signal }),
|
|
498
512
|
// The waiver prompt is offered only under a real TTY. A non-interactive
|
|
499
513
|
// invocation never prompts — it blocks — no matter what the run wired.
|
|
500
514
|
...(runtime.stdinIsTTY && runtime.stdoutIsTTY && runtime.promptForWaiver !== undefined
|
|
501
|
-
? { promptForWaiver: runtime.promptForWaiver }
|
|
515
|
+
? { promptForWaiver: observation?.prompt(runtime.promptForWaiver) ?? runtime.promptForWaiver }
|
|
502
516
|
: {}),
|
|
503
517
|
...(runtime.liveResults === undefined ? {} : { liveResults: runtime.liveResults }),
|
|
504
518
|
invokeProvider: async ({ providerId, payload, requiresEvidence }) => {
|
|
@@ -571,7 +585,9 @@ async function runConfiguredCommand(
|
|
|
571
585
|
};
|
|
572
586
|
|
|
573
587
|
const result = await descriptor.run(context);
|
|
588
|
+
if (runtime.signal?.aborted) throw new CliInterruption();
|
|
574
589
|
if (result.kind === "ok") {
|
|
590
|
+
observeSuccess(result);
|
|
575
591
|
if (result.summary !== undefined && result.summary !== "") runtime.stdout(`${result.summary}\n`);
|
|
576
592
|
return EXIT_OK;
|
|
577
593
|
}
|