@skrr-ai/cli 0.1.15 → 0.1.18
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/README.md +10 -4
- package/dist/commands/commitments/action-proposals/decide.d.ts +13 -0
- package/dist/commands/commitments/action-proposals/decide.js +41 -0
- package/dist/commands/commitments/action-proposals/execute.d.ts +12 -0
- package/dist/commands/commitments/action-proposals/execute.js +29 -0
- package/dist/commands/commitments/action-proposals.d.ts +13 -0
- package/dist/commands/commitments/action-proposals.js +56 -0
- package/dist/commands/commitments/autonomy.d.ts +7 -11
- package/dist/commands/commitments/autonomy.js +16 -17
- package/dist/commands/commitments/bundle.d.ts +14 -0
- package/dist/commands/commitments/bundle.js +49 -0
- package/dist/commands/commitments/create.d.ts +33 -5
- package/dist/commands/commitments/create.js +234 -27
- package/dist/commands/commitments/delivery.d.ts +16 -0
- package/dist/commands/commitments/delivery.js +71 -0
- package/dist/commands/commitments/doctor.d.ts +20 -0
- package/dist/commands/commitments/doctor.js +88 -0
- package/dist/commands/commitments/effective-policy.d.ts +2 -0
- package/dist/commands/commitments/effective-policy.js +50 -5
- package/dist/commands/commitments/endpoints/connect.d.ts +3 -10
- package/dist/commands/commitments/endpoints/connect.js +6 -37
- package/dist/commands/commitments/endpoints/create.d.ts +2 -9
- package/dist/commands/commitments/endpoints/create.js +10 -23
- package/dist/commands/commitments/mode.d.ts +15 -0
- package/dist/commands/commitments/mode.js +57 -0
- package/dist/commands/commitments/pack/promote.d.ts +1 -1
- package/dist/commands/commitments/pack/promote.js +7 -3
- package/dist/commands/commitments/pack/shadow.d.ts +3 -2
- package/dist/commands/commitments/pack/shadow.js +6 -4
- package/dist/commands/commitments/patch.d.ts +14 -0
- package/dist/commands/commitments/patch.js +51 -0
- package/dist/commands/commitments/preflight.d.ts +5 -2
- package/dist/commands/commitments/preflight.js +47 -6
- package/dist/commands/commitments/receipt.d.ts +15 -0
- package/dist/commands/commitments/receipt.js +34 -0
- package/dist/commands/commitments/remediate.d.ts +0 -1
- package/dist/commands/commitments/remediate.js +5 -3
- package/dist/commands/commitments/reports.js +27 -5
- package/dist/commands/commitments/show.d.ts +1 -0
- package/dist/commands/commitments/show.js +22 -1
- package/dist/commands/commitments/update.d.ts +9 -2
- package/dist/commands/commitments/update.js +40 -4
- package/dist/commands/commitments/wake-capabilities.d.ts +5 -0
- package/dist/commands/commitments/wake-capabilities.js +23 -7
- package/dist/commands/spaces/bootstrap.js +1 -1
- package/dist/commands/tasks/complete.js +57 -0
- package/dist/commands/tasks/create.js +2 -1
- package/dist/commands/tasks/deliverable/confirm.d.ts +35 -0
- package/dist/commands/tasks/deliverable/confirm.js +77 -0
- package/dist/commands/tasks/expectations.js +20 -1
- package/dist/commands/tasks/report.js +24 -0
- package/dist/commands/tasks/trust.d.ts +35 -0
- package/dist/commands/tasks/trust.js +75 -0
- package/dist/commands/tasks/waive.js +1 -1
- package/dist/lib/commitment-endpoints.d.ts +3 -2
- package/dist/lib/commitment-endpoints.js +12 -9
- package/dist/lib/commitment-product.d.ts +14 -0
- package/dist/lib/commitment-product.js +78 -0
- package/dist/lib/commitments.d.ts +67 -20
- package/dist/lib/commitments.js +89 -39
- package/dist/lib/tasks.js +27 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +3733 -3570
- package/oclif.manifest.json +9291 -8306
- package/package.json +1 -1
|
@@ -1,18 +1,142 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.hasEventCapableWatchedSource = hasEventCapableWatchedSource;
|
|
4
|
+
exports.assertEventDrivenWatchedSource = assertEventDrivenWatchedSource;
|
|
5
|
+
exports.createCadenceFromFlags = createCadenceFromFlags;
|
|
3
6
|
exports.autonomyStagingNotice = autonomyStagingNotice;
|
|
4
7
|
const core_1 = require("@oclif/core");
|
|
5
8
|
const base_command_1 = require("../../base-command");
|
|
6
9
|
const web_url_1 = require("../../lib/web-url");
|
|
7
10
|
const prompt_1 = require("../../lib/prompt");
|
|
8
11
|
const triggers_1 = require("../../lib/triggers");
|
|
12
|
+
const commitment_product_1 = require("../../lib/commitment-product");
|
|
9
13
|
const commitments_1 = require("../../lib/commitments");
|
|
14
|
+
const DEFAULT_EVENT_DRIVEN_BACKSTOP_MS = 24 * 60 * 60 * 1000;
|
|
15
|
+
const DEFAULT_EVENT_SOURCES = new Set(['github', 'slack', 'email', 'calendar']);
|
|
16
|
+
function record(value) {
|
|
17
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
18
|
+
? value
|
|
19
|
+
: undefined;
|
|
20
|
+
}
|
|
21
|
+
function configuredId(config, ...keys) {
|
|
22
|
+
return keys.some((key) => {
|
|
23
|
+
const value = config?.[key];
|
|
24
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
function hasStructurallyValidExplicitEvent(config) {
|
|
28
|
+
const events = config?.events;
|
|
29
|
+
if (!Array.isArray(events))
|
|
30
|
+
return false;
|
|
31
|
+
return events.some((value) => {
|
|
32
|
+
const event = record(value);
|
|
33
|
+
if (!event)
|
|
34
|
+
return false;
|
|
35
|
+
if (typeof event.entityType !== 'string' || !event.entityType.trim())
|
|
36
|
+
return false;
|
|
37
|
+
if (typeof event.eventType !== 'string' || !event.eventType.trim())
|
|
38
|
+
return false;
|
|
39
|
+
if (event.filterField === undefined)
|
|
40
|
+
return true;
|
|
41
|
+
if (typeof event.filterField !== 'string')
|
|
42
|
+
return false;
|
|
43
|
+
return !event.filterField
|
|
44
|
+
.split('.')
|
|
45
|
+
.some((part) => ['__proto__', 'constructor', 'prototype'].includes(part));
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* True only when the authored create body can materialize at least one event
|
|
50
|
+
* binding. A polling-only source must not make `--event-driven` look successful
|
|
51
|
+
* while the resulting Commitment still wakes exclusively on its backstop.
|
|
52
|
+
*/
|
|
53
|
+
function hasEventCapableWatchedSource(payload) {
|
|
54
|
+
const goalId = typeof payload.goalId === 'string' && payload.goalId.trim().length > 0;
|
|
55
|
+
const watchedSources = Array.isArray(payload.watchedSources) ? payload.watchedSources : [];
|
|
56
|
+
return watchedSources.some((value) => {
|
|
57
|
+
const sourceEntry = record(value);
|
|
58
|
+
if (!sourceEntry || sourceEntry.enabled === false || typeof sourceEntry.source !== 'string') {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
const source = sourceEntry.source;
|
|
62
|
+
const config = record(sourceEntry.config);
|
|
63
|
+
// For these four sources, TriggerSync owns a default event family. An
|
|
64
|
+
// explicit empty or malformed event list intentionally suppresses it.
|
|
65
|
+
if (DEFAULT_EVENT_SOURCES.has(source)) {
|
|
66
|
+
return Array.isArray(config?.events) ? hasStructurallyValidExplicitEvent(config) : true;
|
|
67
|
+
}
|
|
68
|
+
if (source === 'tasks') {
|
|
69
|
+
return (goalId || configuredId(config, 'taskId', 'goalId', 'keyResultId'));
|
|
70
|
+
}
|
|
71
|
+
if (source === 'goal') {
|
|
72
|
+
return goalId || configuredId(config, 'goalId', 'keyResultId');
|
|
73
|
+
}
|
|
74
|
+
if (source === 'space')
|
|
75
|
+
return configuredId(config, 'spaceId');
|
|
76
|
+
if (source === 'conversations')
|
|
77
|
+
return configuredId(config, 'conversationId');
|
|
78
|
+
if (source === 'messages')
|
|
79
|
+
return configuredId(config, 'groupChatId');
|
|
80
|
+
if (source === 'agent_initiatives') {
|
|
81
|
+
return configuredId(config, 'initiativeId', 'cloudCodingTaskId');
|
|
82
|
+
}
|
|
83
|
+
if (source === 'documents')
|
|
84
|
+
return configuredId(config, 'artifactIdentifier');
|
|
85
|
+
if (source === 'monitor')
|
|
86
|
+
return configuredId(config, 'monitorId');
|
|
87
|
+
// Custom bindings are still server-validated against the source-specific
|
|
88
|
+
// wake allowlist. At this boundary, require at least a structurally valid
|
|
89
|
+
// event row instead of treating any non-empty array as event capability.
|
|
90
|
+
return hasStructurallyValidExplicitEvent(config);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
function assertEventDrivenWatchedSource(payload) {
|
|
94
|
+
if (record(payload.cadence)?.mode !== 'event_plus_interval' ||
|
|
95
|
+
hasEventCapableWatchedSource(payload)) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
throw new Error('Event-driven cadence (--event-driven or --cadence-mode event_plus_interval) requires at least one event-capable watched source. ' +
|
|
99
|
+
'Add --watch github (or another event-capable source), or choose an interval-only cadence.');
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Resolve the create-time cadence flags into the API payload.
|
|
103
|
+
*
|
|
104
|
+
* `--event-driven` is deliberately a complete shortcut rather than an alias
|
|
105
|
+
* for the mode string: event delivery is an accelerator, so the contract must
|
|
106
|
+
* always retain a timed safety net even when the author omits `--cadence`.
|
|
107
|
+
*/
|
|
108
|
+
function createCadenceFromFlags({ eventDriven, cadence: cadenceInput, cadenceMode, timezone, }) {
|
|
109
|
+
if (eventDriven) {
|
|
110
|
+
if (cadenceMode && cadenceMode !== 'event_plus_interval') {
|
|
111
|
+
throw new Error(`--event-driven cannot be combined with --cadence-mode ${cadenceMode}. ` +
|
|
112
|
+
'Use --event-driven alone or --cadence-mode event_plus_interval.');
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
mode: 'event_plus_interval',
|
|
116
|
+
intervalMs: cadenceInput
|
|
117
|
+
? (0, commitments_1.parseIntervalToMs)(cadenceInput)
|
|
118
|
+
: DEFAULT_EVENT_DRIVEN_BACKSTOP_MS,
|
|
119
|
+
...(timezone ? { timezone } : {}),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
const cadence = {};
|
|
123
|
+
if (cadenceInput) {
|
|
124
|
+
cadence.intervalMs = (0, commitments_1.parseIntervalToMs)(cadenceInput);
|
|
125
|
+
cadence.mode = cadenceMode || 'interval';
|
|
126
|
+
}
|
|
127
|
+
else if (cadenceMode) {
|
|
128
|
+
cadence.mode = cadenceMode;
|
|
129
|
+
}
|
|
130
|
+
if (timezone)
|
|
131
|
+
cadence.timezone = timezone;
|
|
132
|
+
return Object.keys(cadence).length ? cadence : undefined;
|
|
133
|
+
}
|
|
10
134
|
/**
|
|
11
|
-
*
|
|
12
|
-
* mode
|
|
13
|
-
*
|
|
14
|
-
* boundary; otherwise a successful create response
|
|
15
|
-
* autonomy is live when
|
|
135
|
+
* Creation preserves the policy its author wrote, so the mode that comes back
|
|
136
|
+
* is normally the mode that was asked for. When it is not — normalization
|
|
137
|
+
* rejected the value, or a governance layer sits lower — the difference has to
|
|
138
|
+
* be said out loud at the CLI boundary; otherwise a successful create response
|
|
139
|
+
* reads like the requested autonomy is live when something else is running.
|
|
16
140
|
*/
|
|
17
141
|
function autonomyStagingNotice({ payload, commitment, bin, }) {
|
|
18
142
|
const requestedPolicy = payload.policy && typeof payload.policy === 'object' && !Array.isArray(payload.policy)
|
|
@@ -21,14 +145,21 @@ function autonomyStagingNotice({ payload, commitment, bin, }) {
|
|
|
21
145
|
const returnedPolicy = commitment.policy && typeof commitment.policy === 'object' && !Array.isArray(commitment.policy)
|
|
22
146
|
? commitment.policy
|
|
23
147
|
: {};
|
|
24
|
-
const requested = typeof
|
|
148
|
+
const requested = typeof payload.mode === 'string'
|
|
149
|
+
? payload.mode
|
|
150
|
+
: typeof requestedPolicy.autonomyMode === 'string'
|
|
151
|
+
? requestedPolicy.autonomyMode
|
|
152
|
+
: '';
|
|
25
153
|
const admitted = typeof returnedPolicy.autonomyMode === 'string' ? returnedPolicy.autonomyMode : '';
|
|
26
|
-
if (!requested ||
|
|
154
|
+
if (!requested ||
|
|
155
|
+
(0, commitment_product_1.productMode)(requested) === (0, commitment_product_1.productMode)(admitted || 'suggestions_only') ||
|
|
156
|
+
requested === 'off') {
|
|
27
157
|
return undefined;
|
|
158
|
+
}
|
|
28
159
|
const id = typeof commitment.id === 'string' ? commitment.id : '<commitment-id>';
|
|
29
160
|
return [
|
|
30
|
-
`Requested
|
|
31
|
-
`To
|
|
161
|
+
`Requested mode ${(0, commitment_product_1.productModeLabel)(requested)} was not applied; this commitment is running as ${(0, commitment_product_1.productModeLabel)(admitted || 'suggestions_only')}.`,
|
|
162
|
+
`To set it explicitly: ${bin} commitments mode ${id} ${(0, commitment_product_1.productMode)(requested)}`,
|
|
32
163
|
].join('\n');
|
|
33
164
|
}
|
|
34
165
|
/**
|
|
@@ -45,9 +176,11 @@ class CommitmentsCreate extends base_command_1.BaseCommand {
|
|
|
45
176
|
static description = 'Create a commitment (a durable agent contract)';
|
|
46
177
|
static examples = [
|
|
47
178
|
'<%= config.bin %> commitments create --agent <agent-id> --title "Ship v2" --outcome "v2 is live for all users" --cadence 6h',
|
|
48
|
-
'<%= config.bin %> commitments create --agent <agent-id> --title "Hiring loop" --outcome "Every candidate hears back in 48h" --watch tasks --watch email --cadence 12h
|
|
179
|
+
'<%= config.bin %> commitments create --agent <agent-id> --title "Hiring loop" --outcome "Every candidate hears back in 48h" --watch tasks --watch email --cadence 12h',
|
|
49
180
|
'<%= config.bin %> commitments create --agent <agent-id> --title "SLA" --outcome "p95 under 200ms" --criterion "p95 latency confirmed" --evaluator metric --measure <measure-id>',
|
|
181
|
+
'<%= config.bin %> commitments create --agent <agent-id> --title "AWS cost watch" --outcome "Cost regressions are investigated" --agent-directive-file ./aws-cost-policy.md',
|
|
50
182
|
'<%= config.bin %> commitments create --from-json commitment.json --json',
|
|
183
|
+
'<%= config.bin %> commitments create --agent <agent-id> --title "Keep CI healthy" --outcome "Failed checks are repaired" --watch github --event-driven --mode execute --delivery draft-pr --repo owner/repo --base main --status draft',
|
|
51
184
|
];
|
|
52
185
|
static flags = {
|
|
53
186
|
json: core_1.Flags.boolean({ description: 'Output the created commitment as JSON' }),
|
|
@@ -64,10 +197,16 @@ class CommitmentsCreate extends base_command_1.BaseCommand {
|
|
|
64
197
|
description: 'The outcome to hold true — required when setting any target field',
|
|
65
198
|
}),
|
|
66
199
|
description: core_1.Flags.string({ description: 'Longer description' }),
|
|
200
|
+
'agent-directive': core_1.Flags.string({
|
|
201
|
+
description: 'Per-Commitment domain policy (max 8,000 characters). Mutually exclusive with --agent-directive-file.',
|
|
202
|
+
}),
|
|
203
|
+
'agent-directive-file': core_1.Flags.string({
|
|
204
|
+
description: 'Read the per-Commitment domain policy from a text file, or - for stdin. Mutually exclusive with --agent-directive.',
|
|
205
|
+
}),
|
|
67
206
|
goal: core_1.Flags.string({ description: 'Link to a goal ID' }),
|
|
68
207
|
status: core_1.Flags.string({
|
|
69
|
-
description: 'Create as draft (default)
|
|
70
|
-
options: ['draft'
|
|
208
|
+
description: 'Create as a draft (default); activate later with commitments resume',
|
|
209
|
+
options: ['draft'],
|
|
71
210
|
}),
|
|
72
211
|
deadline: core_1.Flags.string({ description: 'ISO-8601 deadline for an `achieve` commitment' }),
|
|
73
212
|
criterion: core_1.Flags.string({
|
|
@@ -108,16 +247,45 @@ class CommitmentsCreate extends base_command_1.BaseCommand {
|
|
|
108
247
|
multiple: true,
|
|
109
248
|
}),
|
|
110
249
|
cadence: core_1.Flags.string({
|
|
111
|
-
description: 'Check interval, e.g. "15m", "6h", "
|
|
250
|
+
description: 'Check interval, e.g. "15m", "6h", "30d" (1m..365d). Defaults to interval mode; with --event-driven, this is the timed backstop.',
|
|
251
|
+
}),
|
|
252
|
+
'event-driven': core_1.Flags.boolean({
|
|
253
|
+
description: 'Wake on matching watched-source events, with a timed backstop from --cadence (default 24h). Requires an event-capable watched source and cannot be combined with a different --cadence-mode.',
|
|
112
254
|
}),
|
|
113
255
|
'cadence-mode': core_1.Flags.string({
|
|
114
|
-
description: '
|
|
115
|
-
options: [...commitments_1.
|
|
256
|
+
description: 'Advanced cadence mode (default: interval when --cadence is set, else manual). Prefer --event-driven for event_plus_interval with a 24h default backstop.',
|
|
257
|
+
options: [...commitments_1.COMMITMENT_AUTHORABLE_CADENCE_MODES],
|
|
116
258
|
}),
|
|
117
259
|
timezone: core_1.Flags.string({ description: 'IANA timezone for the cadence' }),
|
|
260
|
+
mode: core_1.Flags.string({
|
|
261
|
+
description: 'How the commitment progresses (default Monitor)',
|
|
262
|
+
options: [...commitment_product_1.COMMITMENT_PRODUCT_MODES],
|
|
263
|
+
}),
|
|
264
|
+
delivery: core_1.Flags.string({
|
|
265
|
+
description: 'Where Prepare or Execute coding work is delivered; Monitor is report-only',
|
|
266
|
+
options: [...commitment_product_1.COMMITMENT_DELIVERY_CHOICES],
|
|
267
|
+
}),
|
|
268
|
+
repo: core_1.Flags.string({ description: 'Delivery repository, owner/repo' }),
|
|
269
|
+
base: core_1.Flags.string({ description: 'Delivery target branch' }),
|
|
118
270
|
autonomy: core_1.Flags.string({
|
|
119
|
-
|
|
271
|
+
// `--help` is where someone decides what to type, so the ceiling that can
|
|
272
|
+
// still bind has to be stated HERE and not only in the notice that
|
|
273
|
+
// corrects them afterwards. `commitments autonomy --mode` learned the
|
|
274
|
+
// same lesson: what it must not keep is the silence.
|
|
275
|
+
description: 'Compatibility flag. Prefer --mode monitor|prepare|execute. Workspace administrator rules remain binding.',
|
|
120
276
|
options: [...commitments_1.COMMITMENT_AUTONOMY_MODES],
|
|
277
|
+
hidden: true,
|
|
278
|
+
}),
|
|
279
|
+
isolation: core_1.Flags.string({
|
|
280
|
+
// Omitting this is not the same as passing `shared`: an omitted value
|
|
281
|
+
// leaves the daemon's own isolation heuristic in force, while `shared` is
|
|
282
|
+
// an explicit contract that suppresses it. Say so here, because `--help`
|
|
283
|
+
// is where someone decides which of the two they mean.
|
|
284
|
+
description: 'Where autonomous runs do their work. "ephemeral" cuts a one-shot git worktree per run ' +
|
|
285
|
+
'and destroys it after; "shared" lands them on the daemon working tree, in place. ' +
|
|
286
|
+
'Omit to leave the runtime default. Cannot be combined with a remediation capability.',
|
|
287
|
+
options: [...commitments_1.COMMITMENT_EXECUTION_ISOLATION_MODES],
|
|
288
|
+
hidden: true,
|
|
121
289
|
}),
|
|
122
290
|
'max-nudges': core_1.Flags.integer({ description: 'Max user nudges per day (0..20)' }),
|
|
123
291
|
};
|
|
@@ -125,6 +293,7 @@ class CommitmentsCreate extends base_command_1.BaseCommand {
|
|
|
125
293
|
this.requireAuth();
|
|
126
294
|
const { flags } = await this.parse(CommitmentsCreate);
|
|
127
295
|
let payload;
|
|
296
|
+
let deliveryNormalizedForMonitor = false;
|
|
128
297
|
try {
|
|
129
298
|
// `createBodySchema` is a z.strictObject, so unwrap a `plan --json`
|
|
130
299
|
// envelope and drop any key it does not declare (a contract dumped from
|
|
@@ -138,6 +307,12 @@ class CommitmentsCreate extends base_command_1.BaseCommand {
|
|
|
138
307
|
payload.title = flags.title;
|
|
139
308
|
if (flags.description !== undefined)
|
|
140
309
|
payload.description = flags.description;
|
|
310
|
+
const agentDirective = await (0, commitments_1.readCommitmentAgentDirective)({
|
|
311
|
+
directive: flags['agent-directive'],
|
|
312
|
+
file: flags['agent-directive-file'],
|
|
313
|
+
});
|
|
314
|
+
if (agentDirective !== undefined)
|
|
315
|
+
payload.agentDirective = agentDirective;
|
|
141
316
|
if (flags.goal)
|
|
142
317
|
payload.goalId = flags.goal;
|
|
143
318
|
if (flags.status)
|
|
@@ -182,25 +357,47 @@ class CommitmentsCreate extends base_command_1.BaseCommand {
|
|
|
182
357
|
});
|
|
183
358
|
if (built)
|
|
184
359
|
payload.watchedSources = built;
|
|
185
|
-
const cadence = {
|
|
186
|
-
|
|
187
|
-
cadence
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
}
|
|
193
|
-
if (flags.timezone)
|
|
194
|
-
cadence.timezone = flags.timezone;
|
|
195
|
-
if (Object.keys(cadence).length)
|
|
360
|
+
const cadence = createCadenceFromFlags({
|
|
361
|
+
eventDriven: flags['event-driven'],
|
|
362
|
+
cadence: flags.cadence,
|
|
363
|
+
cadenceMode: flags['cadence-mode'],
|
|
364
|
+
timezone: flags.timezone,
|
|
365
|
+
});
|
|
366
|
+
if (cadence)
|
|
196
367
|
payload.cadence = cadence;
|
|
368
|
+
assertEventDrivenWatchedSource(payload);
|
|
197
369
|
const policy = {};
|
|
198
370
|
if (flags.autonomy)
|
|
199
371
|
policy.autonomyMode = flags.autonomy;
|
|
372
|
+
if (flags.mode && flags.autonomy)
|
|
373
|
+
throw new Error('Use --mode or the compatibility --autonomy flag, not both.');
|
|
374
|
+
if (flags.mode)
|
|
375
|
+
payload.mode = (0, commitment_product_1.productMode)(flags.mode);
|
|
376
|
+
if (flags.delivery)
|
|
377
|
+
payload.delivery = (0, commitment_product_1.deliveryInput)(flags.delivery, flags);
|
|
378
|
+
else if (flags.repo || flags.base)
|
|
379
|
+
throw new Error('--repo and --base require --delivery.');
|
|
380
|
+
if (flags.isolation)
|
|
381
|
+
policy.executionIsolation = flags.isolation;
|
|
200
382
|
if (flags['max-nudges'] !== undefined)
|
|
201
383
|
policy.maxUserNudgesPerDay = flags['max-nudges'];
|
|
202
384
|
if (Object.keys(policy).length)
|
|
203
385
|
payload.policy = policy;
|
|
386
|
+
const payloadPolicy = payload.policy && typeof payload.policy === 'object' && !Array.isArray(payload.policy)
|
|
387
|
+
? payload.policy
|
|
388
|
+
: {};
|
|
389
|
+
const selectedMode = typeof payload.mode === 'string'
|
|
390
|
+
? payload.mode
|
|
391
|
+
: typeof payloadPolicy.autonomyMode === 'string'
|
|
392
|
+
? payloadPolicy.autonomyMode
|
|
393
|
+
: 'monitor';
|
|
394
|
+
if (payload.delivery && typeof payload.delivery === 'object' && !Array.isArray(payload.delivery)) {
|
|
395
|
+
const declared = payload.delivery;
|
|
396
|
+
const coherent = (0, commitment_product_1.deliveryForProductMode)(selectedMode, declared);
|
|
397
|
+
deliveryNormalizedForMonitor =
|
|
398
|
+
(0, commitment_product_1.productMode)(selectedMode) === 'monitor' && declared.mode !== 'report_only';
|
|
399
|
+
payload.delivery = coherent;
|
|
400
|
+
}
|
|
204
401
|
}
|
|
205
402
|
catch (err) {
|
|
206
403
|
this.error(err.message, { exit: 1 });
|
|
@@ -215,6 +412,9 @@ class CommitmentsCreate extends base_command_1.BaseCommand {
|
|
|
215
412
|
exit: 1,
|
|
216
413
|
});
|
|
217
414
|
}
|
|
415
|
+
// Creation is authoring only. Activation is a separate resume boundary
|
|
416
|
+
// that runs the current preflight; JSON imports cannot bypass it.
|
|
417
|
+
payload.status = 'draft';
|
|
218
418
|
let commitment;
|
|
219
419
|
try {
|
|
220
420
|
commitment = await commitments_1.commitmentApi.create(payload);
|
|
@@ -231,6 +431,9 @@ class CommitmentsCreate extends base_command_1.BaseCommand {
|
|
|
231
431
|
});
|
|
232
432
|
if (notice)
|
|
233
433
|
this.warn(notice);
|
|
434
|
+
if (deliveryNormalizedForMonitor) {
|
|
435
|
+
this.warn('Monitor is report-only, so the requested code-delivery destination was not stored. Use --mode prepare or --mode execute to deliver code.');
|
|
436
|
+
}
|
|
234
437
|
return;
|
|
235
438
|
}
|
|
236
439
|
this.log('Created commitment:');
|
|
@@ -244,6 +447,10 @@ class CommitmentsCreate extends base_command_1.BaseCommand {
|
|
|
244
447
|
this.log('');
|
|
245
448
|
this.log(notice);
|
|
246
449
|
}
|
|
450
|
+
if (deliveryNormalizedForMonitor) {
|
|
451
|
+
this.log('');
|
|
452
|
+
this.log('Monitor is report-only, so delivery was set to Report only. Choose Prepare or Execute to deliver code.');
|
|
453
|
+
}
|
|
247
454
|
if (commitment.status === 'draft') {
|
|
248
455
|
this.log('');
|
|
249
456
|
this.log(`Activate it with: ${this.config.bin} commitments resume ${commitment.id}`);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base-command';
|
|
2
|
+
export default class CommitmentsDelivery extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
mode: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
8
|
+
};
|
|
9
|
+
static flags: {
|
|
10
|
+
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
repo: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
12
|
+
base: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
13
|
+
reason: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
14
|
+
};
|
|
15
|
+
run(): Promise<void>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const base_command_1 = require("../../base-command");
|
|
5
|
+
const commitments_1 = require("../../lib/commitments");
|
|
6
|
+
const commitment_product_1 = require("../../lib/commitment-product");
|
|
7
|
+
const web_url_1 = require("../../lib/web-url");
|
|
8
|
+
class CommitmentsDelivery extends base_command_1.BaseCommand {
|
|
9
|
+
static description = 'Declare where Prepare or Execute delivers work; Monitor is always report-only';
|
|
10
|
+
static examples = [
|
|
11
|
+
'<%= config.bin %> commitments delivery <id> draft-pr --repo owner/repo --base main',
|
|
12
|
+
];
|
|
13
|
+
static args = {
|
|
14
|
+
id: core_1.Args.string({ required: true, ignoreStdin: true }),
|
|
15
|
+
mode: core_1.Args.string({
|
|
16
|
+
required: true,
|
|
17
|
+
description: 'Delivery destination. Code destinations require Prepare or Execute (or a Pack Shadow promotion target).',
|
|
18
|
+
options: [
|
|
19
|
+
...commitment_product_1.COMMITMENT_DELIVERY_CHOICES,
|
|
20
|
+
...commitment_product_1.COMMITMENT_DELIVERY_CHOICES.map((mode) => mode.replace(/-/g, '_')),
|
|
21
|
+
],
|
|
22
|
+
}),
|
|
23
|
+
};
|
|
24
|
+
static flags = {
|
|
25
|
+
json: core_1.Flags.boolean({ description: 'Output the complete contract as JSON' }),
|
|
26
|
+
repo: core_1.Flags.string({ description: 'GitHub repository, owner/repo' }),
|
|
27
|
+
base: core_1.Flags.string({ description: 'Delivery target branch, such as main' }),
|
|
28
|
+
reason: core_1.Flags.string({ description: 'Reason recorded with this contract change' }),
|
|
29
|
+
};
|
|
30
|
+
async run() {
|
|
31
|
+
this.requireAuth();
|
|
32
|
+
const { args, flags } = await this.parse(CommitmentsDelivery);
|
|
33
|
+
try {
|
|
34
|
+
const current = (await commitments_1.commitmentApi.get(args.id)).commitment;
|
|
35
|
+
const requested = (0, commitment_product_1.deliveryInput)(args.mode, flags);
|
|
36
|
+
const stagingForPromotion = current.pack?.phase === 'shadow';
|
|
37
|
+
const targetPolicy = stagingForPromotion
|
|
38
|
+
? current.pack?.policyBeforeShadow || current.policy
|
|
39
|
+
: current.policy;
|
|
40
|
+
const productContract = targetPolicy?.workPromotionModel === 'monitor_prepare_execute_v1';
|
|
41
|
+
if (productContract &&
|
|
42
|
+
(0, commitment_product_1.productMode)(targetPolicy?.autonomyMode) === 'monitor' &&
|
|
43
|
+
requested.mode !== 'report_only') {
|
|
44
|
+
throw new Error(stagingForPromotion
|
|
45
|
+
? 'This Pack promotion target is Monitor, so its delivery must be report-only. Set the commitment mode to Prepare or Execute before staging code delivery.'
|
|
46
|
+
: 'Monitor is report-only. Set the commitment mode to Prepare or Execute before selecting code delivery.');
|
|
47
|
+
}
|
|
48
|
+
const delivery = { ...current.delivery, ...requested };
|
|
49
|
+
const result = await commitments_1.commitmentApi.delivery(args.id, {
|
|
50
|
+
delivery,
|
|
51
|
+
expectedVersion: current.version,
|
|
52
|
+
...(flags.reason ? { reason: flags.reason } : {}),
|
|
53
|
+
});
|
|
54
|
+
const linked = (0, web_url_1.withCommitmentUrl)(this.cliConfig.baseURL, result);
|
|
55
|
+
if (flags.json)
|
|
56
|
+
return this.log(JSON.stringify(linked, null, 2));
|
|
57
|
+
const renderedDelivery = stagingForPromotion
|
|
58
|
+
? result.pack?.deliveryBeforeShadow || delivery
|
|
59
|
+
: result.delivery;
|
|
60
|
+
this.log(`${stagingForPromotion ? 'Promotion delivery staged' : 'Delivery'}: ${(0, commitment_product_1.deliveryLabel)(renderedDelivery?.mode)}${renderedDelivery?.repo ? ` · ${renderedDelivery.repo}` : ''}${renderedDelivery?.baseBranch ? ` → ${renderedDelivery.baseBranch}` : ''}`);
|
|
61
|
+
if (stagingForPromotion) {
|
|
62
|
+
this.log('Live Shadow delivery remains Report only until explicit promotion.');
|
|
63
|
+
}
|
|
64
|
+
this.log(`URL: ${linked.url}`);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
this.handleApiError(error);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
exports.default = CommitmentsDelivery;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base-command';
|
|
2
|
+
export declare function doctorVerdict(input: {
|
|
3
|
+
commitment?: Record<string, unknown>;
|
|
4
|
+
preflight?: Record<string, unknown>;
|
|
5
|
+
operatingState?: Record<string, unknown>;
|
|
6
|
+
}): {
|
|
7
|
+
verdict: string;
|
|
8
|
+
next: string;
|
|
9
|
+
};
|
|
10
|
+
export default class CommitmentsDoctor extends BaseCommand {
|
|
11
|
+
static description: string;
|
|
12
|
+
static args: {
|
|
13
|
+
id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
14
|
+
};
|
|
15
|
+
static flags: {
|
|
16
|
+
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
17
|
+
strict: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
18
|
+
};
|
|
19
|
+
run(): Promise<void>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.doctorVerdict = doctorVerdict;
|
|
4
|
+
const core_1 = require("@oclif/core");
|
|
5
|
+
const base_command_1 = require("../../base-command");
|
|
6
|
+
const commitments_1 = require("../../lib/commitments");
|
|
7
|
+
function doctorVerdict(input) {
|
|
8
|
+
const commitment = input.commitment || {};
|
|
9
|
+
const preflight = input.preflight || {};
|
|
10
|
+
const operatingState = input.operatingState || {};
|
|
11
|
+
const blocker = operatingState.firstActionableBlocker;
|
|
12
|
+
if (blocker?.code) {
|
|
13
|
+
return { verdict: 'blocked', next: String(blocker.remediation || 'Inspect the Commitment') };
|
|
14
|
+
}
|
|
15
|
+
if (operatingState.status === 'waiting_approval') {
|
|
16
|
+
return { verdict: 'waiting_approval', next: 'Open the pending prepared action for review' };
|
|
17
|
+
}
|
|
18
|
+
if (operatingState.coverage && operatingState.coverage.complete === false) {
|
|
19
|
+
return { verdict: 'degraded', next: 'Retry doctor; one or more operating-state projections are unavailable' };
|
|
20
|
+
}
|
|
21
|
+
if (operatingState.status && commitment.status === 'active') {
|
|
22
|
+
const status = String(operatingState.status);
|
|
23
|
+
return {
|
|
24
|
+
verdict: ['on_track', 'healthy'].includes(status) ? 'active_healthy' : status,
|
|
25
|
+
next: 'skrr commitments trace <id> --limit 50',
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
if (commitment.status !== 'active')
|
|
29
|
+
return { verdict: 'blocked', next: 'skrr commitments resume <id>' };
|
|
30
|
+
if (preflight.verdict === 'blocked')
|
|
31
|
+
return { verdict: 'blocked', next: 'skrr commitments preflight <id> --json' };
|
|
32
|
+
const attention = preflight.attention;
|
|
33
|
+
if (attention?.allowed === false)
|
|
34
|
+
return { verdict: 'attention_suppressed', next: 'skrr commitments effective-policy <id>' };
|
|
35
|
+
return { verdict: 'active_healthy', next: 'skrr commitments trace <id> --limit 50' };
|
|
36
|
+
}
|
|
37
|
+
class CommitmentsDoctor extends base_command_1.BaseCommand {
|
|
38
|
+
static description = 'Summarize commitment readiness and the next operator action';
|
|
39
|
+
static args = {
|
|
40
|
+
id: core_1.Args.string({ description: 'Commitment ID', required: true, ignoreStdin: true }),
|
|
41
|
+
};
|
|
42
|
+
static flags = {
|
|
43
|
+
json: core_1.Flags.boolean({ description: 'Output the composed health report as JSON' }),
|
|
44
|
+
strict: core_1.Flags.boolean({
|
|
45
|
+
description: 'Exit non-zero unless the commitment is active and healthy',
|
|
46
|
+
}),
|
|
47
|
+
};
|
|
48
|
+
async run() {
|
|
49
|
+
this.requireAuth();
|
|
50
|
+
const { args, flags } = await this.parse(CommitmentsDoctor);
|
|
51
|
+
let commitment;
|
|
52
|
+
let shown;
|
|
53
|
+
try {
|
|
54
|
+
shown = (await commitments_1.commitmentApi.get(args.id));
|
|
55
|
+
commitment = (shown.commitment || {});
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
this.handleApiError(error);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const operatingState = (shown.operatingState || {});
|
|
62
|
+
const diagnosis = doctorVerdict({ commitment, operatingState });
|
|
63
|
+
const report = {
|
|
64
|
+
...diagnosis,
|
|
65
|
+
commitment,
|
|
66
|
+
abilities: shown.abilities,
|
|
67
|
+
operatingState,
|
|
68
|
+
observedAt: new Date().toISOString(),
|
|
69
|
+
};
|
|
70
|
+
if (flags.json)
|
|
71
|
+
this.log(JSON.stringify(report, null, 2));
|
|
72
|
+
else {
|
|
73
|
+
this.log(`Verdict: ${diagnosis.verdict}`);
|
|
74
|
+
this.log(`Next: ${diagnosis.next.replace('<id>', args.id)}`);
|
|
75
|
+
const autonomy = operatingState.autonomy;
|
|
76
|
+
if (autonomy) {
|
|
77
|
+
this.log(`Mode: ${(0, commitments_1.commitmentAutonomyLabel)(String(autonomy.declaredMode || ''))} → ${(0, commitments_1.commitmentAutonomyLabel)(String(autonomy.effectiveMode || ''))}`);
|
|
78
|
+
}
|
|
79
|
+
const approvals = operatingState.approvals;
|
|
80
|
+
if (approvals?.pendingCount)
|
|
81
|
+
this.log(`Waiting approval: ${String(approvals.pendingCount)}`);
|
|
82
|
+
}
|
|
83
|
+
if (flags.strict && !['healthy', 'active_healthy'].includes(diagnosis.verdict)) {
|
|
84
|
+
process.exitCode = 1;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
exports.default = CommitmentsDoctor;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BaseCommand } from '../../base-command';
|
|
2
|
+
export declare function effectivePolicyLines(policy: Record<string, any>): string[];
|
|
2
3
|
/** Show the policy actually governing a Commitment without changing it. */
|
|
3
4
|
export default class CommitmentsEffectivePolicy extends BaseCommand {
|
|
4
5
|
static description: string;
|
|
@@ -8,6 +9,7 @@ export default class CommitmentsEffectivePolicy extends BaseCommand {
|
|
|
8
9
|
};
|
|
9
10
|
static flags: {
|
|
10
11
|
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
verbose: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
11
13
|
};
|
|
12
14
|
run(): Promise<void>;
|
|
13
15
|
}
|
|
@@ -1,8 +1,46 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.effectivePolicyLines = effectivePolicyLines;
|
|
3
4
|
const core_1 = require("@oclif/core");
|
|
4
5
|
const base_command_1 = require("../../base-command");
|
|
5
6
|
const commitments_1 = require("../../lib/commitments");
|
|
7
|
+
const commitment_product_1 = require("../../lib/commitment-product");
|
|
8
|
+
function effectivePolicyLines(policy) {
|
|
9
|
+
const governance = policy.governance || {};
|
|
10
|
+
const owner = policy.owner || {};
|
|
11
|
+
const lines = [`Mode in force: ${(0, commitment_product_1.productModeLabel)(governance.effectiveAutonomyMode)}`];
|
|
12
|
+
if (governance.blocked) {
|
|
13
|
+
lines.push(`Workspace status: blocked${governance.blockedReason ? ` — ${governance.blockedReason}` : ''}`);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
lines.push('Workspace status: allowed');
|
|
17
|
+
}
|
|
18
|
+
if (governance.workspaceMaxMode) {
|
|
19
|
+
lines.push(`Workspace Mode ceiling: ${(0, commitment_product_1.productModeLabel)(governance.workspaceMaxMode)}`);
|
|
20
|
+
}
|
|
21
|
+
if (Array.isArray(governance.workspaceModeOverrides)) {
|
|
22
|
+
for (const override of governance.workspaceModeOverrides) {
|
|
23
|
+
if (override?.effectiveMode) {
|
|
24
|
+
lines.push(`Workspace override: ${(0, commitment_product_1.productModeLabel)(override.effectiveMode)} — ${override.policy || 'configured administrator rule'}${override.configuredValue ? ` (${override.configuredValue})` : ''}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (Array.isArray(governance.allowedCommitmentDeliveryModes)) {
|
|
29
|
+
lines.push(`Workspace delivery allowlist: ${governance.allowedCommitmentDeliveryModes.length ? governance.allowedCommitmentDeliveryModes.map(commitment_product_1.deliveryLabel).join(', ') : 'none'}`);
|
|
30
|
+
}
|
|
31
|
+
if (Array.isArray(governance.requireAttestedAccessForDeliveryModes) && governance.requireAttestedAccessForDeliveryModes.length) {
|
|
32
|
+
lines.push(`Runtime attestation required for: ${governance.requireAttestedAccessForDeliveryModes.map(commitment_product_1.deliveryLabel).join(', ')}`);
|
|
33
|
+
}
|
|
34
|
+
const runLimit = owner.actionRunsPerDay;
|
|
35
|
+
if (runLimit) {
|
|
36
|
+
lines.push(`Owner action-run limit: ${runLimit.mode === 'unbounded' ? 'Unlimited' : `${runLimit.value}/day`}`);
|
|
37
|
+
}
|
|
38
|
+
const cost = policy.billing;
|
|
39
|
+
if (cost?.effectiveCostLimitUsd != null) {
|
|
40
|
+
lines.push(`Effective daily cost limit: $${Number(cost.effectiveCostLimitUsd).toFixed(2)}`);
|
|
41
|
+
}
|
|
42
|
+
return lines;
|
|
43
|
+
}
|
|
6
44
|
/** Show the policy actually governing a Commitment without changing it. */
|
|
7
45
|
class CommitmentsEffectivePolicy extends base_command_1.BaseCommand {
|
|
8
46
|
static description = 'Show the owner, workspace, and infrastructure policy currently governing a commitment';
|
|
@@ -15,6 +53,7 @@ class CommitmentsEffectivePolicy extends base_command_1.BaseCommand {
|
|
|
15
53
|
};
|
|
16
54
|
static flags = {
|
|
17
55
|
json: core_1.Flags.boolean({ description: 'Output the complete effective policy as JSON' }),
|
|
56
|
+
verbose: core_1.Flags.boolean({ description: 'Append raw compatibility and diagnostic policy JSON' }),
|
|
18
57
|
};
|
|
19
58
|
async run() {
|
|
20
59
|
this.requireAuth();
|
|
@@ -27,11 +66,17 @@ class CommitmentsEffectivePolicy extends base_command_1.BaseCommand {
|
|
|
27
66
|
this.handleApiError(err);
|
|
28
67
|
return;
|
|
29
68
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
this.log(
|
|
69
|
+
if (flags.json) {
|
|
70
|
+
this.log(JSON.stringify(policy, null, 2));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
this.log('Effective policy:');
|
|
74
|
+
for (const line of effectivePolicyLines(policy))
|
|
75
|
+
this.log(` ${line}`);
|
|
76
|
+
if (flags.verbose) {
|
|
77
|
+
this.log('\nRaw compatibility diagnostics:');
|
|
78
|
+
this.log(JSON.stringify(policy, null, 2));
|
|
79
|
+
}
|
|
35
80
|
}
|
|
36
81
|
}
|
|
37
82
|
exports.default = CommitmentsEffectivePolicy;
|