@skrr-ai/cli 0.1.127 → 0.1.128
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/dist/base-command.d.ts +23 -0
- package/dist/base-command.js +72 -0
- package/dist/commands/agents/chat.js +7 -0
- package/dist/commands/commitments/action-history.js +1 -1
- package/dist/commands/commitments/cases.js +3 -3
- package/dist/commands/commitments/check-now.d.ts +10 -2
- package/dist/commands/commitments/check-now.js +53 -3
- package/dist/commands/commitments/create.d.ts +6 -14
- package/dist/commands/commitments/create.js +61 -6
- package/dist/commands/commitments/doctor.js +3 -1
- package/dist/commands/commitments/effective-policy.d.ts +11 -1
- package/dist/commands/commitments/effective-policy.js +84 -4
- package/dist/commands/commitments/hypotheses.js +12 -12
- package/dist/commands/commitments/next-tick.js +5 -0
- package/dist/commands/commitments/observations.js +18 -17
- package/dist/commands/commitments/pack/list.js +2 -1
- package/dist/commands/commitments/pack/plan.d.ts +6 -0
- package/dist/commands/commitments/pack/plan.js +19 -0
- package/dist/commands/commitments/preflight.d.ts +19 -0
- package/dist/commands/commitments/preflight.js +108 -10
- package/dist/commands/commitments/trigger-status.js +8 -0
- package/dist/commands/commitments/update.d.ts +1 -0
- package/dist/commands/commitments/update.js +45 -4
- package/dist/commands/commitments/wake-capabilities.js +13 -0
- package/dist/commands/compass/archive.js +4 -0
- package/dist/commands/compass/commitments.js +15 -1
- package/dist/commands/compass/create.js +11 -3
- package/dist/commands/compass/effective-policy.d.ts +1 -0
- package/dist/commands/compass/effective-policy.js +8 -7
- package/dist/commands/compass/revert.d.ts +8 -0
- package/dist/commands/compass/revert.js +25 -1
- package/dist/commands/compass/revisions.d.ts +2 -0
- package/dist/commands/compass/revisions.js +18 -2
- package/dist/commands/compass/tactic.d.ts +5 -3
- package/dist/commands/compass/tactic.js +8 -7
- package/dist/commands/compass/update.js +19 -3
- package/dist/commands/followups/act.js +1 -1
- package/dist/commands/followups/cancel.js +13 -0
- package/dist/commands/followups/observability.js +20 -1
- package/dist/commands/followups/plans/create.js +12 -1
- package/dist/commands/followups/show.js +13 -0
- package/dist/commands/followups/watch.js +1 -1
- package/dist/commands/goals/planner-config.d.ts +11 -0
- package/dist/commands/goals/planner-config.js +16 -0
- package/dist/commands/machines/update.d.ts +5 -3
- package/dist/commands/machines/update.js +70 -9
- package/dist/commands/northstars/create.js +3 -1
- package/dist/commands/northstars/governance/configure.d.ts +4 -0
- package/dist/commands/northstars/governance/configure.js +75 -9
- package/dist/commands/northstars/intents/decide.d.ts +6 -0
- package/dist/commands/northstars/intents/decide.js +69 -15
- package/dist/commands/northstars/intents/list.d.ts +2 -1
- package/dist/commands/northstars/intents/list.js +15 -5
- package/dist/commands/northstars/intents.d.ts +26 -0
- package/dist/commands/northstars/intents.js +49 -0
- package/dist/commands/northstars/list.d.ts +1 -0
- package/dist/commands/northstars/list.js +24 -3
- package/dist/commands/northstars/recommendations/list.d.ts +1 -1
- package/dist/commands/northstars/recommendations/list.js +3 -3
- package/dist/commands/northstars/recommendations.d.ts +19 -0
- package/dist/commands/northstars/recommendations.js +42 -0
- package/dist/commands/northstars/review-now.d.ts +1 -0
- package/dist/commands/northstars/review-now.js +10 -4
- package/dist/commands/northstars/reviews.d.ts +1 -0
- package/dist/commands/northstars/reviews.js +19 -3
- package/dist/commands/northstars/update.js +8 -1
- package/dist/commands/proposals/list.js +11 -2
- package/dist/commands/proposals/show.js +9 -1
- package/dist/commands/spaces/auto-executor/preview.d.ts +14 -1
- package/dist/commands/spaces/auto-executor/preview.js +35 -1
- package/dist/commands/spaces/auto-executor/run.d.ts +17 -0
- package/dist/commands/spaces/auto-executor/run.js +60 -2
- package/dist/commands/spaces/files/upload.js +1 -2
- package/dist/commands/spaces/index.js +23 -0
- package/dist/generated/agent-guides.json +69 -69
- package/dist/lib/agentic-stream.d.ts +9 -0
- package/dist/lib/agentic-stream.js +14 -0
- package/dist/lib/command-miss.js +11 -2
- package/dist/lib/commitment-attention-copy.d.ts +96 -0
- package/dist/lib/commitment-attention-copy.js +209 -0
- package/dist/lib/commitment-loop-status.d.ts +6 -0
- package/dist/lib/commitment-loop-status.js +16 -6
- package/dist/lib/commitments.d.ts +88 -0
- package/dist/lib/commitments.js +188 -0
- package/dist/lib/compass-body-help.d.ts +20 -0
- package/dist/lib/compass-body-help.js +61 -0
- package/dist/lib/compass-refusals.d.ts +35 -0
- package/dist/lib/compass-refusals.js +50 -0
- package/dist/lib/compasses.d.ts +80 -4
- package/dist/lib/compasses.js +232 -12
- package/dist/lib/followups.d.ts +22 -1
- package/dist/lib/followups.js +21 -1
- package/dist/lib/format.d.ts +11 -0
- package/dist/lib/format.js +29 -0
- package/dist/lib/initiatives.d.ts +9 -2
- package/dist/lib/initiatives.js +13 -2
- package/dist/lib/machines.d.ts +14 -1
- package/dist/lib/machines.js +24 -0
- package/dist/lib/northstars.d.ts +158 -24
- package/dist/lib/northstars.js +538 -26
- package/dist/lib/proposal-decision-command.js +21 -1
- package/dist/lib/proposals.d.ts +26 -1
- package/dist/lib/proposals.js +163 -0
- package/dist/lib/rate-limit-retry.d.ts +5 -0
- package/dist/lib/rate-limit-retry.js +19 -0
- package/dist/lib/roles.d.ts +1 -1
- package/dist/lib/space-charter.js +9 -0
- package/dist/lib/task-asset-upload.js +1 -2
- package/dist/lib/triggers.d.ts +1 -1
- package/dist/lib/triggers.js +11 -3
- package/dist/node_modules/@skrr-ai/data-provider/index.js +6504 -6493
- package/oclif.manifest.json +350 -34
- package/package.json +4 -4
package/dist/lib/northstars.js
CHANGED
|
@@ -1,14 +1,27 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.northstarApi = exports.NORTHSTAR_RECOMMENDATION_STATUSES = exports.NORTHSTAR_PRIORITY_TIERS = exports.NORTHSTAR_STATUSES = exports.NORTHSTAR_REVIEW_STATUSES = exports.NORTHSTAR_MANAGEMENT_INTENT_KINDS = exports.NORTHSTAR_INTENT_VERDICTS = exports.NORTHSTAR_EXECUTION_MODES = exports.NORTHSTAR_APPROVAL_TRIGGERS = exports.NORTHSTAR_ANALYSIS_MODES = void 0;
|
|
3
|
+
exports.northstarWideFlag = exports.ALLOCATION_INPUT_FLAGS = exports.NORTHSTAR_LIST_DEFAULT_LIMIT = exports.NORTHSTAR_REASON_WORDS = exports.northstarApi = exports.NORTHSTAR_RECOMMENDATION_STATUSES = exports.NORTHSTAR_PRIORITY_TIERS = exports.NORTHSTAR_STATUSES = exports.NORTHSTAR_REVIEW_STATUSES = exports.NORTHSTAR_MANAGEMENT_INTENT_KINDS = exports.NORTHSTAR_INTENT_VERDICTS = exports.NORTHSTAR_EXECUTION_MODES = exports.NORTHSTAR_APPROVAL_TRIGGERS = exports.NORTHSTAR_ANALYSIS_MODES = void 0;
|
|
4
4
|
exports.humanMs = humanMs;
|
|
5
5
|
exports.shortDate = shortDate;
|
|
6
|
+
exports.describeNorthstarReason = describeNorthstarReason;
|
|
7
|
+
exports.describeDecisionRefusal = describeDecisionRefusal;
|
|
8
|
+
exports.describeDecisionOutcome = describeDecisionOutcome;
|
|
9
|
+
exports.listFooter = listFooter;
|
|
6
10
|
exports.renderNorthstarList = renderNorthstarList;
|
|
11
|
+
exports.describeMaxActive = describeMaxActive;
|
|
7
12
|
exports.renderNorthstar = renderNorthstar;
|
|
8
13
|
exports.renderRecommendationList = renderRecommendationList;
|
|
14
|
+
exports.reviewerReadiness = reviewerReadiness;
|
|
15
|
+
exports.readAllocationRefresh = readAllocationRefresh;
|
|
16
|
+
exports.describeAllocationRefresh = describeAllocationRefresh;
|
|
17
|
+
exports.describeReviewAllocations = describeReviewAllocations;
|
|
9
18
|
exports.renderGovernance = renderGovernance;
|
|
19
|
+
exports.describeAutoBudget = describeAutoBudget;
|
|
20
|
+
exports.reviewNote = reviewNote;
|
|
21
|
+
exports.describeReviewReason = describeReviewReason;
|
|
10
22
|
exports.renderReviewList = renderReviewList;
|
|
11
23
|
exports.renderIntentList = renderIntentList;
|
|
24
|
+
exports.intentBudgetNote = intentBudgetNote;
|
|
12
25
|
/**
|
|
13
26
|
* northstars.ts — request-based client for the Northstar portfolio layer
|
|
14
27
|
* (`/api/northstars/*`) and its Portfolio Governor.
|
|
@@ -31,6 +44,7 @@ exports.renderIntentList = renderIntentList;
|
|
|
31
44
|
* re-declared. The API validates against those exact strings; a second copy here
|
|
32
45
|
* is how the CLI silently drifts from the server that rejects it.
|
|
33
46
|
*/
|
|
47
|
+
const core_1 = require("@oclif/core");
|
|
34
48
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
35
49
|
Object.defineProperty(exports, "NORTHSTAR_ANALYSIS_MODES", { enumerable: true, get: function () { return data_provider_1.NORTHSTAR_ANALYSIS_MODES; } });
|
|
36
50
|
Object.defineProperty(exports, "NORTHSTAR_APPROVAL_TRIGGERS", { enumerable: true, get: function () { return data_provider_1.NORTHSTAR_APPROVAL_TRIGGERS; } });
|
|
@@ -96,6 +110,258 @@ function shortDate(value) {
|
|
|
96
110
|
const date = new Date(value);
|
|
97
111
|
return Number.isNaN(date.getTime()) ? '—' : date.toISOString().replace('T', ' ').slice(0, 16);
|
|
98
112
|
}
|
|
113
|
+
/* --------------------------------------------------------------------------
|
|
114
|
+
* Reason codes in words
|
|
115
|
+
*
|
|
116
|
+
* The server refuses, skips and decides with machine codes
|
|
117
|
+
* (`reviewer_agent_not_always_on`, `northstar_version_drift`, ...). A code is
|
|
118
|
+
* for a script; a person reading a table or an error needs the sentence. This
|
|
119
|
+
* is the ONE place those sentences live, so a command never invents its own
|
|
120
|
+
* wording (the old governance line guessed "offline" for a reviewer that was
|
|
121
|
+
* simply not Always-On) and `--json` can carry the same text as `reasonText`.
|
|
122
|
+
*
|
|
123
|
+
* Entries are CLAUSES, lower-case with no trailing full stop, so a caller can
|
|
124
|
+
* put them after "agent turn not started:" or inside a longer sentence. A code
|
|
125
|
+
* with no entry is returned AS IT IS: an unknown code printed raw is honest,
|
|
126
|
+
* a guessed explanation is not.
|
|
127
|
+
* ------------------------------------------------------------------------ */
|
|
128
|
+
exports.NORTHSTAR_REASON_WORDS = {
|
|
129
|
+
// An agent turn that was not reserved for an agent_assisted review.
|
|
130
|
+
no_reviewer_agent_configured: 'no reviewer agent is set on this northstar',
|
|
131
|
+
northstar_paused: 'the northstar is paused',
|
|
132
|
+
northstar_achieved: 'the northstar is marked achieved',
|
|
133
|
+
northstar_archived: 'the northstar is archived',
|
|
134
|
+
analysis_mode_not_agent_assisted: 'analysis mode is deterministic, so no agent is asked',
|
|
135
|
+
managed_agent_missing: 'the reviewer agent does not exist or is not visible to you',
|
|
136
|
+
always_on_control_unavailable: "the reviewer agent's Always-On state could not be read just now",
|
|
137
|
+
always_on_disabled: 'Always-On is turned off for the reviewer agent',
|
|
138
|
+
always_on_suspended: 'Always-On is suspended for the reviewer agent',
|
|
139
|
+
always_on_stale_generation: "the reviewer agent's Always-On state changed while this ran",
|
|
140
|
+
reviewer_agent_foreign_owner: "the reviewer agent belongs to someone other than this northstar's owner",
|
|
141
|
+
reviewer_agent_not_always_on: 'the reviewer agent is not Always-On (it is not offline; turn Always-On on for it)',
|
|
142
|
+
run_already_reserved: 'an agent run was already reserved for this review',
|
|
143
|
+
envelope_build_failed: 'the review could not be packaged for the agent',
|
|
144
|
+
governance_run_reservation_failed: 'reserving the agent turn failed unexpectedly',
|
|
145
|
+
run_not_reserved: 'the run queue declined to reserve the agent turn',
|
|
146
|
+
enqueue_failed: 'the agent turn could not be queued',
|
|
147
|
+
agent_run_reserved: 'an agent turn is reserved; its results land asynchronously',
|
|
148
|
+
// Why an intent was recorded, held or refused by policy.
|
|
149
|
+
execution_mode_advisory: 'execution mode is advisory: recorded for you to read, nothing is changed',
|
|
150
|
+
execution_mode_approval: 'execution mode is approval: it waits for a person',
|
|
151
|
+
proposal_only: 'a proposal: the governor never applies it, a person does',
|
|
152
|
+
commitment_protected: "the commitment is on this northstar's protected list",
|
|
153
|
+
intent_kind_not_allowed: "this kind of intent is not in the northstar's allowed list",
|
|
154
|
+
critical_commitment_lifecycle: 'it changes a critical commitment, which always needs a person',
|
|
155
|
+
resume_requires_approval: 'resuming always needs a person',
|
|
156
|
+
budget_telemetry_unavailable: 'the automatic-change budget could not be read, so it fell back to approval',
|
|
157
|
+
critical_demotion: 'it would demote a critical commitment',
|
|
158
|
+
tier_change_too_large: 'the tier change is larger than one step',
|
|
159
|
+
weight_change_too_large: 'the weight change exceeds the allowed relative change',
|
|
160
|
+
bounds_increase_compute: 'it would buy more compute',
|
|
161
|
+
bounds_change_exceeds_max_relative_change: 'the bounds change exceeds the allowed relative change',
|
|
162
|
+
auto_pause_evidence_incomplete: 'the evidence for an automatic pause is incomplete',
|
|
163
|
+
no_auto_rule_for_kind: 'no automatic rule covers this kind of change',
|
|
164
|
+
auto_mutation_review_cap_reached: 'this review already made its maximum automatic changes',
|
|
165
|
+
auto_mutation_daily_cap_reached: "today's automatic-change cap is used up",
|
|
166
|
+
mutation_cooldown_active: 'the commitment was changed automatically too recently (cooldown)',
|
|
167
|
+
commitment_not_linked_to_northstar: 'the commitment is no longer linked to this northstar',
|
|
168
|
+
commitment_not_found: 'the commitment no longer exists',
|
|
169
|
+
approved_by_user: 'approved by a person',
|
|
170
|
+
rejected_by_user: 'rejected by a person',
|
|
171
|
+
// Why a decision (approve / reject) was refused or did not take effect.
|
|
172
|
+
commitment_version_drift: 'the commitment changed after this was proposed',
|
|
173
|
+
northstar_version_drift: 'the northstar changed after this was proposed',
|
|
174
|
+
superseded: 'a newer review proposed the same thing',
|
|
175
|
+
recommendation_dismissed: 'its recommendation was dismissed',
|
|
176
|
+
intent_not_found: "no such intent in this northstar's 50 most recent reviews",
|
|
177
|
+
review_not_found: 'no such review on this northstar',
|
|
178
|
+
northstar_not_found: 'no such northstar',
|
|
179
|
+
owner_approval_required: "only the northstar's owner can approve a proposal",
|
|
180
|
+
transition_lost: 'another decision landed first',
|
|
181
|
+
claim_lost: 'another process is already applying it',
|
|
182
|
+
mutation_error: 'the change failed part-way and was left for the repair pass',
|
|
183
|
+
proposal_kind_requires_manual_action: 'this kind of proposal is completed by hand',
|
|
184
|
+
};
|
|
185
|
+
/** A code in words; an unknown code comes back unchanged, an absent one says so. */
|
|
186
|
+
function describeNorthstarReason(code) {
|
|
187
|
+
if (!code)
|
|
188
|
+
return 'no reason was recorded';
|
|
189
|
+
return exports.NORTHSTAR_REASON_WORDS[code] ?? code;
|
|
190
|
+
}
|
|
191
|
+
function utcStamp(value) {
|
|
192
|
+
const stamp = shortDate(typeof value === 'string' ? value : undefined);
|
|
193
|
+
return stamp === '—' ? undefined : `${stamp} UTC`;
|
|
194
|
+
}
|
|
195
|
+
function stringField(source, key) {
|
|
196
|
+
const value = source?.[key];
|
|
197
|
+
return typeof value === 'string' && value.trim() ? value : undefined;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* A refused decision, as one sentence with its next step.
|
|
201
|
+
*
|
|
202
|
+
* `body` is the server's refusal (`reason`, plus the context it adds: the two
|
|
203
|
+
* northstar versions, or when and by whom the intent was already decided).
|
|
204
|
+
*/
|
|
205
|
+
function describeDecisionRefusal(body, ctx) {
|
|
206
|
+
const code = stringField(body, 'reason') ?? stringField(body, 'error') ?? 'unknown';
|
|
207
|
+
const bin = ctx.bin ?? 'skrr';
|
|
208
|
+
const reviewNow = `Run \`${bin} northstars review-now ${ctx.northstarId}\` for a fresh proposal.`;
|
|
209
|
+
const intents = `\`${bin} northstars intents list ${ctx.northstarId}\``;
|
|
210
|
+
const notApplied = ctx.decision === 'approve' ? 'the approval was not applied' : 'the rejection was not recorded';
|
|
211
|
+
if (code === 'northstar_version_drift') {
|
|
212
|
+
const proposed = body.proposedAtNorthstarVersion;
|
|
213
|
+
const current = body.currentNorthstarVersion;
|
|
214
|
+
const versions = typeof proposed === 'number' && typeof current === 'number'
|
|
215
|
+
? ` (it was at v${proposed}, now v${current})`
|
|
216
|
+
: '';
|
|
217
|
+
return {
|
|
218
|
+
code,
|
|
219
|
+
text: `The northstar changed after this was proposed${versions}, so ${notApplied} and the intent was invalidated. ${reviewNow}`,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
if (code === 'intent_not_pending') {
|
|
223
|
+
const verdict = stringField(body, 'verdict') ?? 'decided';
|
|
224
|
+
if (verdict === 'advisory') {
|
|
225
|
+
return {
|
|
226
|
+
code,
|
|
227
|
+
text: "This intent is advisory: this northstar's execution mode records it for you to read and does not ask for a decision. " +
|
|
228
|
+
`To get proposals you can approve, run \`${bin} northstars governance configure ${ctx.northstarId} --execution approval\` and review again.`,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
if (verdict === 'applying') {
|
|
232
|
+
return {
|
|
233
|
+
code,
|
|
234
|
+
text: `This intent is being applied right now — nothing to do. Check its verdict with ${intents}.`,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
// `invalidated` is the platform ending an intent (the northstar moved, a newer review replaced
|
|
238
|
+
// it, its recommendation was dismissed), never a person's decision: attributing it to "you"
|
|
239
|
+
// because your click was the one that found it out is false (OSK-21247). Say why instead.
|
|
240
|
+
if (verdict === 'invalidated') {
|
|
241
|
+
const why = stringField(body, 'verdictReason');
|
|
242
|
+
const when = utcStamp(body.decidedAt);
|
|
243
|
+
return {
|
|
244
|
+
code,
|
|
245
|
+
text: `This intent was already invalidated${why ? ` (${describeNorthstarReason(why)})` : ''}${when ? ` at ${when}` : ''} — nothing changed.`,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
let by = '';
|
|
249
|
+
if (body.decidedByYou === true)
|
|
250
|
+
by = ' by you';
|
|
251
|
+
else if (body.decidedByYou === false)
|
|
252
|
+
by = ' by someone else';
|
|
253
|
+
const at = utcStamp(body.decidedAt);
|
|
254
|
+
return {
|
|
255
|
+
code,
|
|
256
|
+
text: `This intent was already ${verdict}${by}${at ? ` at ${at}` : ''} — nothing changed.`,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
if (code === 'transition_lost') {
|
|
260
|
+
return {
|
|
261
|
+
code,
|
|
262
|
+
text: `Another decision on this intent landed at the same moment, so yours was not recorded. Run ${intents} to see how it ended.`,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
if (code === 'intent_not_found') {
|
|
266
|
+
return {
|
|
267
|
+
code,
|
|
268
|
+
text: `No intent ${ctx.intentId} in this northstar's 50 most recent reviews. If it is older, pass --review <review-id>.`,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
const words = describeNorthstarReason(code);
|
|
272
|
+
return { code, text: `${words.charAt(0).toUpperCase()}${words.slice(1)}.` };
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* What a decision that the server ACCEPTED (HTTP 200) actually did.
|
|
276
|
+
*
|
|
277
|
+
* One exit rule: an approval exits 0 only when the intent is `applied`, and a
|
|
278
|
+
* rejection only when it is `rejected`. An approval the version fence
|
|
279
|
+
* invalidated, or one whose change has not landed, used to print a line shaped
|
|
280
|
+
* like success and exit 0, so a script could not tell it from one that worked.
|
|
281
|
+
*/
|
|
282
|
+
function describeDecisionOutcome(result, ctx) {
|
|
283
|
+
const bin = ctx.bin ?? 'skrr';
|
|
284
|
+
const verdict = stringField(result, 'verdict') ?? 'unknown';
|
|
285
|
+
const headline = `${ctx.intentId} → ${verdict}`;
|
|
286
|
+
const replayed = result.replayed === true;
|
|
287
|
+
const reviewNow = `Run \`${bin} northstars review-now ${ctx.northstarId}\` for a fresh proposal.`;
|
|
288
|
+
if (ctx.decision === 'reject') {
|
|
289
|
+
if (verdict === 'rejected') {
|
|
290
|
+
return {
|
|
291
|
+
headline,
|
|
292
|
+
exitCode: 0,
|
|
293
|
+
...(replayed ? { text: 'Already rejected — nothing changed.' } : {}),
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
headline,
|
|
298
|
+
exitCode: 1,
|
|
299
|
+
code: verdict,
|
|
300
|
+
text: `The intent was not rejected; its verdict is ${verdict}.`,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
const continuation = result.continuation;
|
|
304
|
+
const applyResult = result.applyResult;
|
|
305
|
+
if (verdict === 'applied') {
|
|
306
|
+
const commitmentId = stringField(continuation, 'commitmentId');
|
|
307
|
+
const status = stringField(continuation, 'status');
|
|
308
|
+
let text;
|
|
309
|
+
if (status === 'draft_created') {
|
|
310
|
+
text = `Created draft commitment ${commitmentId ?? ''}`.trim();
|
|
311
|
+
text += '. Configure and activate it when you are ready.';
|
|
312
|
+
}
|
|
313
|
+
else if (status === 'executor_reassigned') {
|
|
314
|
+
text = `Reassigned the executor of ${commitmentId ?? 'the commitment'}.`;
|
|
315
|
+
}
|
|
316
|
+
if (replayed)
|
|
317
|
+
text = `Already applied — nothing changed.${text ? ` ${text}` : ''}`;
|
|
318
|
+
return { headline, exitCode: 0, ...(text ? { text } : {}) };
|
|
319
|
+
}
|
|
320
|
+
// Not applied. The reason is the apply step's, or a continuation's.
|
|
321
|
+
const code = stringField(applyResult, 'reason') ??
|
|
322
|
+
stringField(continuation, 'reason') ??
|
|
323
|
+
stringField(continuation, 'status');
|
|
324
|
+
let text;
|
|
325
|
+
if (code === 'commitment_version_drift' || code === 'commitment_not_found') {
|
|
326
|
+
const what = code === 'commitment_not_found'
|
|
327
|
+
? 'The commitment no longer exists'
|
|
328
|
+
: 'The commitment changed after this was proposed';
|
|
329
|
+
text = `${what}, so the approval was not applied and the intent was invalidated. ${reviewNow}`;
|
|
330
|
+
}
|
|
331
|
+
else if (code) {
|
|
332
|
+
text = `Your approval is recorded, but nothing was applied: ${describeNorthstarReason(code)}.`;
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
text = `Your approval is recorded, but the change has not landed yet (verdict: ${verdict}). It is retried automatically; check \`${bin} northstars intents list ${ctx.northstarId}\`.`;
|
|
336
|
+
}
|
|
337
|
+
return { headline, exitCode: 1, code: code ?? verdict, text };
|
|
338
|
+
}
|
|
339
|
+
/** What the server returned before `--limit` existed; sent explicitly so a page is never silently smaller. */
|
|
340
|
+
exports.NORTHSTAR_LIST_DEFAULT_LIMIT = 50;
|
|
341
|
+
/**
|
|
342
|
+
* The footer under a list that was cut short. `has_more: false` used to be
|
|
343
|
+
* unconditional on these lists, so a page of `--limit` rows read as the whole
|
|
344
|
+
* answer; the server now says when there is more and this says what to do.
|
|
345
|
+
* Empty when the page is complete.
|
|
346
|
+
*
|
|
347
|
+
* `cursor` is true only for a list that can be paged (`northstars list`); the
|
|
348
|
+
* reviews and intents lists have no cursor, so their way on is a larger
|
|
349
|
+
* `--limit`, or a narrower `--status` once `--limit` is at its ceiling.
|
|
350
|
+
*/
|
|
351
|
+
function listFooter(input) {
|
|
352
|
+
if (!input.hasMore)
|
|
353
|
+
return '';
|
|
354
|
+
const head = `Showing ${input.shown} — more available;`;
|
|
355
|
+
const atMax = input.limit >= input.max;
|
|
356
|
+
if (input.cursor && input.nextCursor) {
|
|
357
|
+
return atMax
|
|
358
|
+
? `${head} --limit is already at its maximum (${input.max}), so continue with --cursor ${input.nextCursor}`
|
|
359
|
+
: `${head} raise --limit (max ${input.max}) or page with --cursor ${input.nextCursor}`;
|
|
360
|
+
}
|
|
361
|
+
return atMax
|
|
362
|
+
? `${head} --limit is already at its maximum (${input.max}), so narrow the list with --status`
|
|
363
|
+
: `${head} raise --limit (max ${input.max})`;
|
|
364
|
+
}
|
|
99
365
|
function renderNorthstarList(rows, log) {
|
|
100
366
|
if (rows.length === 0) {
|
|
101
367
|
log('No northstars.');
|
|
@@ -121,6 +387,18 @@ function renderNorthstarList(rows, log) {
|
|
|
121
387
|
{ key: 'reviewed', header: 'REVIEWED' },
|
|
122
388
|
], log);
|
|
123
389
|
}
|
|
390
|
+
/** A budget that was never set says so; `—` is kept for a timestamp that does not exist yet. */
|
|
391
|
+
const notSet = (value) => value === undefined || value === null ? 'not set' : String(value);
|
|
392
|
+
/**
|
|
393
|
+
* The active-commitment cap, in words. `0` is accepted and stored, and a review
|
|
394
|
+
* treats it as "no cap" (it does not flag every commitment over a cap of zero),
|
|
395
|
+
* so the screen says that rather than letting `0` read as a limit.
|
|
396
|
+
*/
|
|
397
|
+
function describeMaxActive(value) {
|
|
398
|
+
if (value === undefined || value === null)
|
|
399
|
+
return 'not set';
|
|
400
|
+
return value === 0 ? '0 (no cap)' : String(value);
|
|
401
|
+
}
|
|
124
402
|
function renderNorthstar(status, log) {
|
|
125
403
|
const ns = status.northstar;
|
|
126
404
|
log(`${ns.title} (${ns.id})`);
|
|
@@ -128,7 +406,7 @@ function renderNorthstar(status, log) {
|
|
|
128
406
|
if (ns.description)
|
|
129
407
|
log(`About ${ns.description}`);
|
|
130
408
|
log(`Priority ${ns.priority?.tier ?? '—'} / weight ${ns.priority?.weight ?? '—'}`);
|
|
131
|
-
log(`Attention total ${ns.attentionBudget?.totalWeight
|
|
409
|
+
log(`Attention total ${notSet(ns.attentionBudget?.totalWeight)} · max active ${describeMaxActive(ns.attentionBudget?.maxActiveCommitments)}`);
|
|
132
410
|
const cadence = ns.reviewCadence ?? {};
|
|
133
411
|
log(`Review ${cadence.mode ?? 'manual'}${cadence.intervalMs ? ` every ${humanMs(cadence.intervalMs)}` : ''} next ${shortDate(cadence.nextReviewAt)} last ${shortDate(ns.state?.lastReviewedAt)}`);
|
|
134
412
|
if (ns.goalIds?.length)
|
|
@@ -195,9 +473,87 @@ function renderRecommendationList(rows, log) {
|
|
|
195
473
|
function reviewerReadiness(reviewer) {
|
|
196
474
|
if (!reviewer)
|
|
197
475
|
return '';
|
|
476
|
+
// The server's own admission gate is the authority on whether the NEXT review
|
|
477
|
+
// gets an agent turn: it also knows a paused northstar or a deterministic
|
|
478
|
+
// analysis mode, which `ready` (is the agent Always-On) does not.
|
|
479
|
+
const admission = reviewer.admission;
|
|
480
|
+
if (admission) {
|
|
481
|
+
if (admission.admitted)
|
|
482
|
+
return ' [ready]';
|
|
483
|
+
if (admission.reason === 'analysis_mode_not_agent_assisted') {
|
|
484
|
+
return ` [not in use: ${describeNorthstarReason(admission.reason)}]`;
|
|
485
|
+
}
|
|
486
|
+
return ` [not ready: ${describeNorthstarReason(admission.reason)}]`;
|
|
487
|
+
}
|
|
198
488
|
if (reviewer.ready)
|
|
199
489
|
return ' [ready]';
|
|
200
|
-
|
|
490
|
+
// No reason from an older server is "unknown", never a guess: the old fallback
|
|
491
|
+
// said "offline" about an agent that was reachable and merely not Always-On.
|
|
492
|
+
return ` [not ready: ${reviewer.reason ? describeNorthstarReason(reviewer.reason) : 'reason unknown'}]`;
|
|
493
|
+
}
|
|
494
|
+
/** The `governance configure` flags that move what a commitment's allocation is computed from. */
|
|
495
|
+
exports.ALLOCATION_INPUT_FLAGS = [
|
|
496
|
+
'max-runs-per-day',
|
|
497
|
+
'clear-max-runs-per-day',
|
|
498
|
+
'critical-reserve',
|
|
499
|
+
'min-interval-hours',
|
|
500
|
+
'max-interval-hours',
|
|
501
|
+
'max-relative-change',
|
|
502
|
+
];
|
|
503
|
+
/** The refresh summary a configure response carries, or undefined from a server that sends none. */
|
|
504
|
+
function readAllocationRefresh(response) {
|
|
505
|
+
if (typeof response?.allocationsRefreshed !== 'number')
|
|
506
|
+
return undefined;
|
|
507
|
+
return {
|
|
508
|
+
refreshed: response.allocationsRefreshed,
|
|
509
|
+
pending: response.allocationsPending ?? 0,
|
|
510
|
+
failed: response.allocationsRefreshFailed === true,
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* What `governance configure` says about the linked commitments. The server re-applies a changed
|
|
515
|
+
* budget or cadence band to every linked active commitment at once and answers how many it wrote,
|
|
516
|
+
* how many it could not, and whether the refresh itself failed; the command discarded all of it and
|
|
517
|
+
* re-read the status, so "nothing happened" and "applied to three" looked the same (OSK-21245).
|
|
518
|
+
*
|
|
519
|
+
* `inputsSent` is whether this call touched an allocation input: only then is "already up to date" a
|
|
520
|
+
* true statement, since a zero is also what an unrelated change (the analysis mode) answers.
|
|
521
|
+
*/
|
|
522
|
+
function describeAllocationRefresh(refresh, options) {
|
|
523
|
+
const lines = [];
|
|
524
|
+
const warnings = [];
|
|
525
|
+
if (!refresh)
|
|
526
|
+
return { lines, warnings };
|
|
527
|
+
const bin = options.bin ?? 'skrr';
|
|
528
|
+
if (refresh.failed) {
|
|
529
|
+
warnings.push(`The policy is saved, but re-applying it to the linked commitments failed. The next review applies it, or run \`${bin} northstars review-now ${options.northstarId}\`.`);
|
|
530
|
+
}
|
|
531
|
+
if (refresh.pending > 0) {
|
|
532
|
+
warnings.push(`${refresh.pending} linked commitment(s) were out of date but could not be updated yet; the next review applies them.`);
|
|
533
|
+
}
|
|
534
|
+
if (refresh.refreshed > 0) {
|
|
535
|
+
lines.push(`Applied to ${refresh.refreshed} linked commitment(s) now.`);
|
|
536
|
+
}
|
|
537
|
+
else if (options.inputsSent && !refresh.failed && refresh.pending === 0) {
|
|
538
|
+
lines.push('Allocations already up to date: no linked commitment needed a write.');
|
|
539
|
+
}
|
|
540
|
+
return { lines, warnings };
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* The `Allocations` line of `review-now`. The count is of commitments WRITTEN, so `0` does not say
|
|
544
|
+
* "nothing happened": it is what a review prints when a configure already applied the budget, or
|
|
545
|
+
* when no active commitment is linked, or when no review row could be opened to write under.
|
|
546
|
+
*/
|
|
547
|
+
function describeReviewAllocations(result) {
|
|
548
|
+
const written = Array.isArray(result.allocations) ? result.allocations.length : 0;
|
|
549
|
+
if (written > 0)
|
|
550
|
+
return `Allocations ${written} commitment(s) re-budgeted`;
|
|
551
|
+
if (!result.review)
|
|
552
|
+
return 'Allocations not written: the review could not be recorded';
|
|
553
|
+
if (Array.isArray(result.attention) && result.attention.length === 0) {
|
|
554
|
+
return 'Allocations nothing to allocate: no active commitment is linked';
|
|
555
|
+
}
|
|
556
|
+
return 'Allocations already up to date: no commitment needed a write';
|
|
201
557
|
}
|
|
202
558
|
function renderGovernance(status, log) {
|
|
203
559
|
const g = status.governance;
|
|
@@ -205,35 +561,122 @@ function renderGovernance(status, log) {
|
|
|
205
561
|
log(`Analysis ${g.analysisMode}`);
|
|
206
562
|
log(`Execution ${g.executionMode}`);
|
|
207
563
|
const readiness = reviewerReadiness(status.reviewer);
|
|
208
|
-
log(`Reviewer ${g.reviewerAgentId || '
|
|
564
|
+
log(`Reviewer ${g.reviewerAgentId || 'none'}${readiness}`);
|
|
209
565
|
log('');
|
|
210
566
|
log(`Cadence band ${humanMs(g.cadencePolicy.minIntervalMs)} … ${humanMs(g.cadencePolicy.maxIntervalMs)} (max step ${Math.round((g.cadencePolicy.maxRelativeChange ?? 0) * 100)}%)`);
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
567
|
+
// 0 is a setting, not an absence: a review keeps NO intents at all.
|
|
568
|
+
const intentBudget = g.maxIntentsPerReview === 0
|
|
569
|
+
? '0/review (no intents are ever recorded)'
|
|
570
|
+
: `${g.maxIntentsPerReview}/review`;
|
|
571
|
+
log(`Intent budget ${intentBudget} · auto ${g.maxAutoMutationsPerReview}/review, ${g.maxAutoMutationsPerDay}/day · cooldown ${humanMs(g.mutationCooldownMs)}`);
|
|
572
|
+
// An empty list is a decision with consequences, not an absence of one: say what it does.
|
|
573
|
+
log(`Allowed ${g.allowedIntentKinds.join(', ') || 'none — every intent is refused'}`);
|
|
574
|
+
if (g.allowedIntentKinds.length === 0) {
|
|
575
|
+
log(` ! Every intent a review proposes is refused (intent_kind_not_allowed). Allow a kind with \`governance configure ${status.northstarId} --allow <kind>\`.`);
|
|
216
576
|
}
|
|
577
|
+
log(`Approval for ${g.requireApprovalFor.join(', ') ||
|
|
578
|
+
'none — no change class is always held for a person (the execution mode still decides)'}`);
|
|
579
|
+
if (g.requireApprovalFor.length === 0) {
|
|
580
|
+
log(' ! No change class is on the always-ask list, so the execution mode alone decides what waits for you.');
|
|
581
|
+
}
|
|
582
|
+
log(`Protected ${g.protectedCommitmentIds.join(', ') || 'none'}`);
|
|
217
583
|
const eb = status.executionBudget ?? {};
|
|
218
|
-
log(`Exec budget action runs/day ${eb.maxRunsPerDay
|
|
219
|
-
log(`Observation runs/day ${eb.maxObservationRunsPerDay
|
|
584
|
+
log(`Exec budget action runs/day ${notSet(eb.maxRunsPerDay)} · concurrent ${eb.maxConcurrentRuns == null ? 'not set' : `${eb.maxConcurrentRuns} (recorded, not enforced)`} · critical reserve ${notSet(eb.reserveWeightForCritical)}`);
|
|
585
|
+
log(`Observation runs/day ${notSet(eb.maxObservationRunsPerDay)} · daily budgets reset at 00:00 UTC`);
|
|
220
586
|
log('');
|
|
221
|
-
|
|
587
|
+
const trigger = status.reviewTrigger;
|
|
588
|
+
log(trigger.scheduled
|
|
589
|
+
? `Review trigger scheduled every ${humanMs(trigger.intervalMs)} next ${shortDate(trigger.nextFireAt)}`
|
|
590
|
+
: 'Review trigger manual (no schedule: a review runs only when you run `review-now`)');
|
|
222
591
|
if (status.latestReview) {
|
|
223
592
|
log(`Last review ${status.latestReview.id} ${status.latestReview.status} ${shortDate(status.latestReview.startedAt)} (${status.latestReview.intentCount} intent(s))`);
|
|
224
593
|
}
|
|
225
594
|
else {
|
|
226
|
-
log('Last review
|
|
595
|
+
log('Last review none yet');
|
|
227
596
|
}
|
|
228
597
|
if (status.autonomousRun) {
|
|
229
598
|
log(`Agent run ${status.autonomousRun.id} ${status.autonomousRun.status}`);
|
|
230
599
|
}
|
|
231
600
|
const counts = status.intentCounts ?? {};
|
|
232
601
|
log(`Intents open ${counts.open ?? 0} · awaiting approval ${counts.awaiting_approval ?? 0} · applied ${counts.applied ?? 0} · invalidated ${counts.invalidated ?? 0}`);
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
602
|
+
log(describeAutoBudget(status.mutationBudget));
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* The automatic-change budget line.
|
|
606
|
+
*
|
|
607
|
+
* `0/day` is a MODE (automatic changes off, everything waits for approval), and `used` can
|
|
608
|
+
* legitimately exceed any cap, because the changes were made before the owner lowered it. A bare
|
|
609
|
+
* `2/0 used today · 0 left` collapses both into a ratio over 100% that says neither (OSK-21233).
|
|
610
|
+
*/
|
|
611
|
+
function describeAutoBudget(budget) {
|
|
612
|
+
if ('unavailable' in budget) {
|
|
613
|
+
return 'Auto budget unavailable (governor fails closed to approval)';
|
|
614
|
+
}
|
|
615
|
+
const cap = budget.maxAutoMutationsPerDay;
|
|
616
|
+
const used = budget.appliedToday;
|
|
617
|
+
const earlier = `${used} made earlier today, before the cap was lowered`;
|
|
618
|
+
if (cap === 0) {
|
|
619
|
+
const note = used > 0 ? ` (${earlier})` : '';
|
|
620
|
+
return `Auto budget automatic changes off (0/day) — everything waits for approval${note}`;
|
|
621
|
+
}
|
|
622
|
+
if (used > cap)
|
|
623
|
+
return `Auto budget ${cap}/day · 0 left (${earlier})`;
|
|
624
|
+
return `Auto budget ${used}/${cap} used today · ${budget.remainingToday} left`;
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Reasons that mean an `agent_assisted` review did NOT get an agent turn. A
|
|
628
|
+
* `succeeded` status says the review finished, not that an agent reviewed
|
|
629
|
+
* anything, so the table must say it in words (the reason also lives on the
|
|
630
|
+
* record, and used to be visible only through `review-now` and `--review`).
|
|
631
|
+
*/
|
|
632
|
+
const AGENT_TURN_NOT_STARTED = new Set([
|
|
633
|
+
'no_reviewer_agent_configured',
|
|
634
|
+
'northstar_paused',
|
|
635
|
+
'northstar_achieved',
|
|
636
|
+
'northstar_archived',
|
|
637
|
+
'analysis_mode_not_agent_assisted',
|
|
638
|
+
'managed_agent_missing',
|
|
639
|
+
'always_on_control_unavailable',
|
|
640
|
+
'always_on_disabled',
|
|
641
|
+
'always_on_suspended',
|
|
642
|
+
'always_on_stale_generation',
|
|
643
|
+
'reviewer_agent_foreign_owner',
|
|
644
|
+
'reviewer_agent_not_always_on',
|
|
645
|
+
'run_already_reserved',
|
|
646
|
+
'envelope_build_failed',
|
|
647
|
+
'governance_run_reservation_failed',
|
|
648
|
+
'run_not_reserved',
|
|
649
|
+
'enqueue_failed',
|
|
650
|
+
]);
|
|
651
|
+
/** One cell of words for what a review did beyond "it ran"; empty when there is nothing to add. */
|
|
652
|
+
function reviewNote(review) {
|
|
653
|
+
// A review the intent budget cut short is not a review with nothing to say. The count lived in the
|
|
654
|
+
// snapshot, and the list printed `—` for it, so scanning the list hid it (OSK-21249).
|
|
655
|
+
const omitted = Number(review.deterministicSnapshot?.truncatedIntents);
|
|
656
|
+
const budget = Number.isFinite(omitted) && omitted > 0
|
|
657
|
+
? `${omitted} intent(s) omitted (--max-intents-per-review)`
|
|
658
|
+
: '';
|
|
659
|
+
return [agentTurnNote(review), budget].filter(Boolean).join('; ');
|
|
660
|
+
}
|
|
661
|
+
function agentTurnNote(review) {
|
|
662
|
+
if (review.status === 'failed') {
|
|
663
|
+
return `failed: ${review.errorMessage ?? describeNorthstarReason(review.reason)}`;
|
|
664
|
+
}
|
|
665
|
+
if (review.status === 'awaiting_agent')
|
|
666
|
+
return 'waiting for the agent turn';
|
|
667
|
+
if (review.analysisMode === 'agent_assisted' && review.status === 'succeeded') {
|
|
668
|
+
if (review.reason && AGENT_TURN_NOT_STARTED.has(review.reason)) {
|
|
669
|
+
return `agent turn not started: ${describeNorthstarReason(review.reason)}`;
|
|
670
|
+
}
|
|
671
|
+
if (!review.autonomousRunId)
|
|
672
|
+
return 'agent turn not started: no reason was recorded';
|
|
673
|
+
}
|
|
674
|
+
return '';
|
|
675
|
+
}
|
|
676
|
+
/** `Reason` line of one review: the words, with the raw code kept beside them for grep. */
|
|
677
|
+
function describeReviewReason(reason) {
|
|
678
|
+
const words = describeNorthstarReason(reason);
|
|
679
|
+
return words === reason ? reason : `${words} [${reason}]`;
|
|
237
680
|
}
|
|
238
681
|
function renderReviewList(reviews, log) {
|
|
239
682
|
if (reviews.length === 0) {
|
|
@@ -247,6 +690,7 @@ function renderReviewList(reviews, log) {
|
|
|
247
690
|
mode: `${review.analysisMode ?? '—'}/${review.executionMode ?? '—'}`,
|
|
248
691
|
intents: String((review.intents ?? []).length),
|
|
249
692
|
run: review.autonomousRunId ?? '—',
|
|
693
|
+
note: reviewNote(review) || '—',
|
|
250
694
|
})), [
|
|
251
695
|
{ key: 'id', header: 'REVIEW' },
|
|
252
696
|
{ key: 'started', header: 'STARTED' },
|
|
@@ -254,26 +698,94 @@ function renderReviewList(reviews, log) {
|
|
|
254
698
|
{ key: 'mode', header: 'MODE' },
|
|
255
699
|
{ key: 'intents', header: 'INTENTS' },
|
|
256
700
|
{ key: 'run', header: 'AGENT RUN' },
|
|
701
|
+
{ key: 'note', header: 'NOTE' },
|
|
257
702
|
], log);
|
|
258
703
|
}
|
|
259
|
-
|
|
704
|
+
/** `--wide` on every command that prints the intents table. */
|
|
705
|
+
exports.northstarWideFlag = core_1.Flags.boolean({
|
|
706
|
+
description: 'Do not clip the WHY column, and keep the machine code beside the reason as [code]',
|
|
707
|
+
});
|
|
708
|
+
/**
|
|
709
|
+
* The WHY column never shrinks below this, however narrow the terminal. The reviewer's sentences run
|
|
710
|
+
* to about 105 characters ("This northstar's attention budget allows 12 active commitments; this
|
|
711
|
+
* is the lowest priority over the cap."), and the part that matters is often at the END of one
|
|
712
|
+
* ("Commitment tier (high) drifted from northstar tier (low)."): at the old floor of 40 the default
|
|
713
|
+
* clip cut it before the fact, and the owner needed `--wide` to read the reason (OSK-21248).
|
|
714
|
+
*/
|
|
715
|
+
const INTENT_WHY_MIN_WIDTH = 110;
|
|
716
|
+
/** No terminal to overflow (a pipe, a log): the table is as wide as it needs to be. */
|
|
717
|
+
const INTENT_TABLE_DEFAULT_COLUMNS = 250;
|
|
718
|
+
function whyCell(row, wide, budget) {
|
|
719
|
+
if (!wide)
|
|
720
|
+
return (0, format_1.truncateAtWord)(row.why, budget);
|
|
721
|
+
return row.code && row.code !== row.why ? `${row.why} [${row.code}]` : row.why;
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* The intents table.
|
|
725
|
+
*
|
|
726
|
+
* WHY is the HUMAN reason the governor wrote (`reason`), not the machine code in
|
|
727
|
+
* `verdictReason`, which says why an intent needed approval (`execution_mode_approval`)
|
|
728
|
+
* and is no reason for the change itself. The code stays in `--json`, and beside
|
|
729
|
+
* the sentence as `[code]` under `--wide`. By default the sentence is cut at a
|
|
730
|
+
* word boundary to what the terminal leaves after the other columns.
|
|
731
|
+
*/
|
|
732
|
+
function renderIntentList(intents, log, options = {}) {
|
|
260
733
|
if (intents.length === 0) {
|
|
261
734
|
log('No management intents.');
|
|
262
735
|
return;
|
|
263
736
|
}
|
|
264
|
-
|
|
737
|
+
const rows = intents.map((intent) => ({
|
|
265
738
|
id: intent.id,
|
|
266
739
|
kind: intent.kind,
|
|
267
740
|
target: intent.targetCommitmentId ?? '—',
|
|
268
741
|
verdict: intent.verdict,
|
|
269
|
-
|
|
742
|
+
// No sentence from the governor: the policy code in words (an unknown code stays as it is).
|
|
743
|
+
why: intent.reason ?? (intent.verdictReason ? describeNorthstarReason(intent.verdictReason) : '—'),
|
|
744
|
+
code: intent.verdictReason ?? '',
|
|
270
745
|
source: intent.source ?? 'deterministic',
|
|
746
|
+
}));
|
|
747
|
+
const headers = {
|
|
748
|
+
id: 'INTENT',
|
|
749
|
+
kind: 'KIND',
|
|
750
|
+
target: 'COMMITMENT',
|
|
751
|
+
verdict: 'VERDICT',
|
|
752
|
+
source: 'SOURCE',
|
|
753
|
+
};
|
|
754
|
+
const fixedKeys = ['id', 'kind', 'target', 'verdict', 'source'];
|
|
755
|
+
const fixedWidth = fixedKeys.reduce((sum, key) => sum + Math.max((0, format_1.displayWidth)(headers[key]), ...rows.map((row) => (0, format_1.displayWidth)(row[key]))), 0);
|
|
756
|
+
const terminal = options.columns ?? process.stdout.columns ?? INTENT_TABLE_DEFAULT_COLUMNS;
|
|
757
|
+
// Five gaps of two spaces separate the six columns.
|
|
758
|
+
const whyBudget = Math.max(INTENT_WHY_MIN_WIDTH, terminal - fixedWidth - 10);
|
|
759
|
+
(0, format_1.renderTable)(rows.map((row) => ({
|
|
760
|
+
id: row.id,
|
|
761
|
+
kind: row.kind,
|
|
762
|
+
target: row.target,
|
|
763
|
+
verdict: row.verdict,
|
|
764
|
+
source: row.source,
|
|
765
|
+
why: whyCell(row, options.wide === true, whyBudget),
|
|
271
766
|
})), [
|
|
272
|
-
{ key: 'id', header:
|
|
273
|
-
{ key: 'kind', header:
|
|
274
|
-
{ key: 'target', header:
|
|
275
|
-
{ key: 'verdict', header:
|
|
276
|
-
{ key: 'source', header:
|
|
277
|
-
{ key: 'why', header: 'WHY'
|
|
767
|
+
{ key: 'id', header: headers.id },
|
|
768
|
+
{ key: 'kind', header: headers.kind },
|
|
769
|
+
{ key: 'target', header: headers.target },
|
|
770
|
+
{ key: 'verdict', header: headers.verdict },
|
|
771
|
+
{ key: 'source', header: headers.source },
|
|
772
|
+
{ key: 'why', header: 'WHY' },
|
|
278
773
|
], log);
|
|
279
774
|
}
|
|
775
|
+
/**
|
|
776
|
+
* The line that says a review's intent budget cut something off. A cap of 0
|
|
777
|
+
* records nothing at all, which a screen reading "No management intents." made
|
|
778
|
+
* indistinguishable from a review with nothing to say; the count was only in
|
|
779
|
+
* `--json` (`deterministicSnapshot.truncatedIntents`).
|
|
780
|
+
*/
|
|
781
|
+
function intentBudgetNote(truncated, cap) {
|
|
782
|
+
const count = Number(truncated);
|
|
783
|
+
if (!Number.isFinite(count) || count <= 0)
|
|
784
|
+
return '';
|
|
785
|
+
const limit = Number.isFinite(Number(cap)) && cap !== undefined && cap !== null ? Number(cap) : undefined;
|
|
786
|
+
const budget = limit === undefined ? '' : ` (${limit})`;
|
|
787
|
+
const hint = limit === 0
|
|
788
|
+
? 'a cap of 0 records no intents at all'
|
|
789
|
+
: 'raise it with `governance configure --max-intents-per-review`';
|
|
790
|
+
return `${count} intent(s) omitted by --max-intents-per-review${budget}: ${hint}.`;
|
|
791
|
+
}
|