@skrr-ai/cli 0.1.15 → 0.1.17

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 CHANGED
@@ -753,6 +753,9 @@ sky commitments preflight <commitment-id> # explain gates bef
753
753
  sky commitments create --agent <agent-id> \
754
754
  --title "Hiring loop" --outcome "Every candidate hears back within 48h" \
755
755
  --watch tasks --watch email --cadence 12h
756
+ sky commitments create --agent <agent-id> --title "AWS cost watch" \
757
+ --outcome "Cost regressions are investigated" \
758
+ --agent-directive-file ./aws-cost-policy.md
756
759
  sky commitments resume <commitment-id> # activate
757
760
 
758
761
  # Or draft it from a sentence (read-only proposal)
@@ -29,6 +29,7 @@ class CommitmentsAutonomy extends base_command_1.BaseCommand {
29
29
  static examples = [
30
30
  '<%= config.bin %> commitments autonomy <commitment-id> --mode ask_first',
31
31
  '<%= config.bin %> commitments autonomy <commitment-id> --mode auto_run_limited --reason "trusted after 2 weeks"',
32
+ '<%= config.bin %> commitments autonomy <commitment-id> --mode auto_run --reason "owns the pipeline end to end"',
32
33
  ];
33
34
  static args = {
34
35
  id: core_1.Args.string({ description: 'Commitment ID', required: true, ignoreStdin: true }),
@@ -36,7 +37,14 @@ class CommitmentsAutonomy extends base_command_1.BaseCommand {
36
37
  static flags = {
37
38
  json: core_1.Flags.boolean({ description: 'Output as JSON' }),
38
39
  mode: core_1.Flags.string({
39
- description: 'Autonomy mode',
40
+ // The web surface groups these into three choices; the CLI keeps the
41
+ // canonical five because an operator and a coding agent both need to name
42
+ // the stored value. What it must NOT keep is the silence: the two acting
43
+ // rungs were indistinguishable in the decision core until the authority
44
+ // rework, and this help text never said what separated them.
45
+ description: 'Autonomy mode: off (stop) | suggestions_only (propose only) | ask_first (prepare, wait for approval) | ' +
46
+ 'auto_run_limited (act unattended on low/medium-risk work, bring high-risk to the owner) | ' +
47
+ 'auto_run (act unattended, high-risk work included)',
40
48
  options: [...commitments_1.COMMITMENT_AUTONOMY_MODES],
41
49
  required: true,
42
50
  }),
@@ -1,10 +1,10 @@
1
1
  import { BaseCommand } from '../../base-command';
2
2
  /**
3
- * Public commitment creation is intentionally trust-first: an action-capable
4
- * mode requested in the create payload is staged as suggestions_only until an
5
- * owner explicitly grants it. That safety boundary must be visible at the CLI
6
- * boundary; otherwise a successful create response reads like the requested
7
- * autonomy is live when it is not.
3
+ * Creation preserves the policy its author wrote, so the mode that comes back
4
+ * is normally the mode that was asked for. When it is not normalization
5
+ * rejected the value, or a governance layer sits lower the difference has to
6
+ * be said out loud at the CLI boundary; otherwise a successful create response
7
+ * reads like the requested autonomy is live when something else is running.
8
8
  */
9
9
  export declare function autonomyStagingNotice({ payload, commitment, bin, }: {
10
10
  payload: Record<string, unknown>;
@@ -32,6 +32,8 @@ export default class CommitmentsCreate extends BaseCommand {
32
32
  title: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
33
33
  outcome: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
34
34
  description: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
35
+ 'agent-directive': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
36
+ 'agent-directive-file': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
35
37
  goal: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
36
38
  status: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
37
39
  deadline: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
@@ -47,6 +49,7 @@ export default class CommitmentsCreate extends BaseCommand {
47
49
  'cadence-mode': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
48
50
  timezone: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
49
51
  autonomy: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
52
+ isolation: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
50
53
  'max-nudges': import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
51
54
  };
52
55
  run(): Promise<void>;
@@ -8,11 +8,11 @@ const prompt_1 = require("../../lib/prompt");
8
8
  const triggers_1 = require("../../lib/triggers");
9
9
  const commitments_1 = require("../../lib/commitments");
10
10
  /**
11
- * Public commitment creation is intentionally trust-first: an action-capable
12
- * mode requested in the create payload is staged as suggestions_only until an
13
- * owner explicitly grants it. That safety boundary must be visible at the CLI
14
- * boundary; otherwise a successful create response reads like the requested
15
- * autonomy is live when it is not.
11
+ * Creation preserves the policy its author wrote, so the mode that comes back
12
+ * is normally the mode that was asked for. When it is not normalization
13
+ * rejected the value, or a governance layer sits lower the difference has to
14
+ * be said out loud at the CLI boundary; otherwise a successful create response
15
+ * reads like the requested autonomy is live when something else is running.
16
16
  */
17
17
  function autonomyStagingNotice({ payload, commitment, bin, }) {
18
18
  const requestedPolicy = payload.policy && typeof payload.policy === 'object' && !Array.isArray(payload.policy)
@@ -27,8 +27,8 @@ function autonomyStagingNotice({ payload, commitment, bin, }) {
27
27
  return undefined;
28
28
  const id = typeof commitment.id === 'string' ? commitment.id : '<commitment-id>';
29
29
  return [
30
- `Requested autonomy ${requested} was staged as ${admitted || 'suggestions_only'} for review; it is not active yet.`,
31
- `To grant it explicitly: ${bin} commitments autonomy ${id} --mode ${requested} --reason "<why this scope is safe>"`,
30
+ `Requested autonomy ${requested} was not applied; this commitment is running as ${admitted || 'suggestions_only'}.`,
31
+ `To set it explicitly: ${bin} commitments autonomy ${id} --mode ${requested} --reason "<why this scope is safe>"`,
32
32
  ].join('\n');
33
33
  }
34
34
  /**
@@ -47,6 +47,7 @@ class CommitmentsCreate extends base_command_1.BaseCommand {
47
47
  '<%= config.bin %> commitments create --agent <agent-id> --title "Ship v2" --outcome "v2 is live for all users" --cadence 6h',
48
48
  '<%= config.bin %> commitments create --agent <agent-id> --title "Hiring loop" --outcome "Every candidate hears back in 48h" --watch tasks --watch email --cadence 12h --status active',
49
49
  '<%= config.bin %> commitments create --agent <agent-id> --title "SLA" --outcome "p95 under 200ms" --criterion "p95 latency confirmed" --evaluator metric --measure <measure-id>',
50
+ '<%= config.bin %> commitments create --agent <agent-id> --title "AWS cost watch" --outcome "Cost regressions are investigated" --agent-directive-file ./aws-cost-policy.md',
50
51
  '<%= config.bin %> commitments create --from-json commitment.json --json',
51
52
  ];
52
53
  static flags = {
@@ -64,6 +65,12 @@ class CommitmentsCreate extends base_command_1.BaseCommand {
64
65
  description: 'The outcome to hold true — required when setting any target field',
65
66
  }),
66
67
  description: core_1.Flags.string({ description: 'Longer description' }),
68
+ 'agent-directive': core_1.Flags.string({
69
+ description: 'Per-Commitment domain policy (max 8,000 characters). Mutually exclusive with --agent-directive-file.',
70
+ }),
71
+ 'agent-directive-file': core_1.Flags.string({
72
+ description: 'Read the per-Commitment domain policy from a text file, or - for stdin. Mutually exclusive with --agent-directive.',
73
+ }),
67
74
  goal: core_1.Flags.string({ description: 'Link to a goal ID' }),
68
75
  status: core_1.Flags.string({
69
76
  description: 'Create as draft (default) or active',
@@ -116,9 +123,24 @@ class CommitmentsCreate extends base_command_1.BaseCommand {
116
123
  }),
117
124
  timezone: core_1.Flags.string({ description: 'IANA timezone for the cadence' }),
118
125
  autonomy: core_1.Flags.string({
119
- description: 'How much the commitment may act without asking (default suggestions_only)',
126
+ // `--help` is where someone decides what to type, so the ceiling that can
127
+ // still bind has to be stated HERE and not only in the notice that
128
+ // corrects them afterwards. `commitments autonomy --mode` learned the
129
+ // same lesson: what it must not keep is the silence.
130
+ description: 'Autonomy to set at creation (default suggestions_only). Explicit values are preserved; ' +
131
+ 'Agent, Compass, and workspace governance can still impose a visible effective ceiling.',
120
132
  options: [...commitments_1.COMMITMENT_AUTONOMY_MODES],
121
133
  }),
134
+ isolation: core_1.Flags.string({
135
+ // Omitting this is not the same as passing `shared`: an omitted value
136
+ // leaves the daemon's own isolation heuristic in force, while `shared` is
137
+ // an explicit contract that suppresses it. Say so here, because `--help`
138
+ // is where someone decides which of the two they mean.
139
+ description: 'Where autonomous runs do their work. "ephemeral" cuts a one-shot git worktree per run ' +
140
+ 'and destroys it after; "shared" lands them on the daemon working tree, in place. ' +
141
+ 'Omit to leave the runtime default. Cannot be combined with a remediation capability.',
142
+ options: [...commitments_1.COMMITMENT_EXECUTION_ISOLATION_MODES],
143
+ }),
122
144
  'max-nudges': core_1.Flags.integer({ description: 'Max user nudges per day (0..20)' }),
123
145
  };
124
146
  async run() {
@@ -138,6 +160,12 @@ class CommitmentsCreate extends base_command_1.BaseCommand {
138
160
  payload.title = flags.title;
139
161
  if (flags.description !== undefined)
140
162
  payload.description = flags.description;
163
+ const agentDirective = await (0, commitments_1.readCommitmentAgentDirective)({
164
+ directive: flags['agent-directive'],
165
+ file: flags['agent-directive-file'],
166
+ });
167
+ if (agentDirective !== undefined)
168
+ payload.agentDirective = agentDirective;
141
169
  if (flags.goal)
142
170
  payload.goalId = flags.goal;
143
171
  if (flags.status)
@@ -197,6 +225,8 @@ class CommitmentsCreate extends base_command_1.BaseCommand {
197
225
  const policy = {};
198
226
  if (flags.autonomy)
199
227
  policy.autonomyMode = flags.autonomy;
228
+ if (flags.isolation)
229
+ policy.executionIsolation = flags.isolation;
200
230
  if (flags['max-nudges'] !== undefined)
201
231
  policy.maxUserNudgesPerDay = flags['max-nudges'];
202
232
  if (Object.keys(policy).length)
@@ -0,0 +1,19 @@
1
+ import { BaseCommand } from '../../base-command';
2
+ export declare function doctorVerdict(input: {
3
+ commitment?: Record<string, unknown>;
4
+ preflight?: Record<string, unknown>;
5
+ }): {
6
+ verdict: string;
7
+ next: string;
8
+ };
9
+ export default class CommitmentsDoctor extends BaseCommand {
10
+ static description: string;
11
+ static args: {
12
+ id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
13
+ };
14
+ static flags: {
15
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
16
+ strict: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
17
+ };
18
+ run(): Promise<void>;
19
+ }
@@ -0,0 +1,72 @@
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
+ if (commitment.status !== 'active')
11
+ return { verdict: 'blocked', next: 'skrr commitments resume <id>' };
12
+ if (preflight.verdict === 'blocked')
13
+ return { verdict: 'blocked', next: 'skrr commitments preflight <id> --json' };
14
+ const attention = preflight.attention;
15
+ if (attention?.allowed === false)
16
+ return { verdict: 'attention_suppressed', next: 'skrr commitments effective-policy <id>' };
17
+ return { verdict: 'active_healthy', next: 'skrr commitments trace <id> --limit 50' };
18
+ }
19
+ class CommitmentsDoctor extends base_command_1.BaseCommand {
20
+ static description = 'Summarize commitment readiness and the next operator action';
21
+ static args = {
22
+ id: core_1.Args.string({ description: 'Commitment ID', required: true, ignoreStdin: true }),
23
+ };
24
+ static flags = {
25
+ json: core_1.Flags.boolean({ description: 'Output the composed health report as JSON' }),
26
+ strict: core_1.Flags.boolean({
27
+ description: 'Exit non-zero unless the commitment is active and healthy',
28
+ }),
29
+ };
30
+ async run() {
31
+ this.requireAuth();
32
+ const { args, flags } = await this.parse(CommitmentsDoctor);
33
+ let commitment;
34
+ let preflight;
35
+ let triggerStatus;
36
+ let ledger;
37
+ try {
38
+ const [shown, pf, triggers, runs] = await Promise.all([
39
+ commitments_1.commitmentApi.get(args.id),
40
+ commitments_1.commitmentApi.preflight(args.id),
41
+ commitments_1.commitmentApi.triggerStatus(args.id, 5),
42
+ commitments_1.commitmentApi.ledger(args.id, { limit: 5 }),
43
+ ]);
44
+ commitment = (shown.commitment || {});
45
+ preflight = pf;
46
+ triggerStatus = triggers;
47
+ ledger = runs;
48
+ }
49
+ catch (error) {
50
+ this.handleApiError(error);
51
+ return;
52
+ }
53
+ const diagnosis = doctorVerdict({ commitment, preflight });
54
+ const report = {
55
+ ...diagnosis,
56
+ commitment,
57
+ preflight,
58
+ triggerStatus,
59
+ ledger,
60
+ observedAt: new Date().toISOString(),
61
+ };
62
+ if (flags.json)
63
+ this.log(JSON.stringify(report, null, 2));
64
+ else {
65
+ this.log(`Verdict: ${diagnosis.verdict}`);
66
+ this.log(`Next: ${diagnosis.next.replace('<id>', args.id)}`);
67
+ }
68
+ if (flags.strict && diagnosis.verdict !== 'active_healthy')
69
+ process.exitCode = 1;
70
+ }
71
+ }
72
+ exports.default = CommitmentsDoctor;
@@ -28,11 +28,13 @@ export default class CommitmentsPreflight extends BaseCommand {
28
28
  static description: string;
29
29
  static examples: string[];
30
30
  static args: {
31
- id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
31
+ id: import("@oclif/core/lib/interfaces").Arg<string | undefined, Record<string, unknown>>;
32
32
  };
33
33
  static flags: {
34
34
  json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
35
35
  next: import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
36
+ 'from-json': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
37
+ 'event-fixture': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
36
38
  };
37
39
  run(): Promise<void>;
38
40
  }
@@ -4,6 +4,7 @@ exports.preflightGateLines = preflightGateLines;
4
4
  const core_1 = require("@oclif/core");
5
5
  const base_command_1 = require("../../base-command");
6
6
  const commitments_1 = require("../../lib/commitments");
7
+ const prompt_1 = require("../../lib/prompt");
7
8
  /**
8
9
  * The lines one preflight gate contributes.
9
10
  *
@@ -46,21 +47,42 @@ class CommitmentsPreflight extends base_command_1.BaseCommand {
46
47
  '<%= config.bin %> commitments preflight <commitment-id> --json',
47
48
  ];
48
49
  static args = {
49
- id: core_1.Args.string({ description: 'Commitment ID', required: true, ignoreStdin: true }),
50
+ id: core_1.Args.string({
51
+ description: 'Commitment ID (omit with --from-json to preview before creation)',
52
+ required: false,
53
+ ignoreStdin: true,
54
+ }),
50
55
  };
51
56
  static flags = {
52
57
  json: core_1.Flags.boolean({ description: 'Output the complete preflight report as JSON' }),
53
58
  next: core_1.Flags.integer({ description: 'Number of upcoming runs to project (1–10)' }),
59
+ 'from-json': core_1.Flags.string({
60
+ description: 'Unsaved Commitment create body file, or - for stdin',
61
+ }),
62
+ 'event-fixture': core_1.Flags.string({
63
+ description: 'Optional event fixture JSON file, or - for stdin',
64
+ }),
54
65
  };
55
66
  async run() {
56
67
  this.requireAuth();
57
68
  const { args, flags } = await this.parse(CommitmentsPreflight);
69
+ if (Boolean(args.id) === Boolean(flags['from-json'])) {
70
+ this.error('Pass either a commitment ID or --from-json <file|->.', { exit: 1 });
71
+ }
58
72
  if (flags.next != null && (flags.next < 1 || flags.next > 10)) {
59
73
  this.error('--next must be between 1 and 10', { exit: 1 });
60
74
  }
61
75
  let report;
62
76
  try {
63
- report = await commitments_1.commitmentApi.preflight(args.id, flags.next);
77
+ report = args.id
78
+ ? await commitments_1.commitmentApi.preflight(args.id, flags.next)
79
+ : await commitments_1.commitmentApi.draftPreflight({
80
+ commitment: (0, commitments_1.normalizeCreateBody)(await (0, prompt_1.readJsonBody)(flags['from-json'])),
81
+ ...(flags['event-fixture']
82
+ ? { eventFixture: await (0, prompt_1.readJsonBody)(flags['event-fixture']) }
83
+ : {}),
84
+ ...(flags.next !== undefined ? { nextRunCount: flags.next } : {}),
85
+ });
64
86
  }
65
87
  catch (err) {
66
88
  this.handleApiError(err);
@@ -70,7 +92,7 @@ class CommitmentsPreflight extends base_command_1.BaseCommand {
70
92
  this.log(JSON.stringify(report, null, 2));
71
93
  return;
72
94
  }
73
- this.log(`Preflight: ${String(report.verdict).replace(/_/g, ' ')}`);
95
+ this.log(`${args.id ? 'Preflight' : 'Pre-create preview (nothing persisted)'}: ${String(report.verdict).replace(/_/g, ' ')}`);
74
96
  this.log('\nGates:');
75
97
  for (const gate of report.gates || []) {
76
98
  for (const line of preflightGateLines(gate))
@@ -13,7 +13,8 @@ const DESTINATIONS_NEEDING_TARGET = new Set(['slack_channel', 'email']);
13
13
  * completion, a ping on escalation), so `on` is NOT part of the key.
14
14
  */
15
15
  function reportKey(r) {
16
- return r.destinationTarget ? `${r.destination}:${r.destinationTarget}` : r.destination;
16
+ const target = r.destinationTarget ? `${r.destination}:${r.destinationTarget}` : r.destination;
17
+ return `${target}:${r.on ?? 'material_change'}`;
17
18
  }
18
19
  /**
19
20
  * `skrr commitments reports <id>` — where this commitment tells you things.
@@ -98,8 +99,20 @@ class CommitmentsReports extends base_command_1.BaseCommand {
98
99
  payload = { reports: (0, commitment_terms_1.appendUnique)(reports, next, reportKey, 'Report target') };
99
100
  }
100
101
  else {
102
+ const base = String(flags.remove);
103
+ const matches = reports.filter((report) => {
104
+ const target = report.destinationTarget
105
+ ? `${report.destination}:${report.destinationTarget}`
106
+ : report.destination;
107
+ return target === base && (!flags.on || (report.on ?? 'material_change') === flags.on);
108
+ });
109
+ if (matches.length !== 1) {
110
+ throw new Error(matches.length > 1
111
+ ? `Multiple report targets match ${base}. Pass --on to select one condition.`
112
+ : `Report target ${base} is not attached to this commitment.`);
113
+ }
101
114
  payload = {
102
- reports: (0, commitment_terms_1.removeByKey)(reports, String(flags.remove), reportKey, 'Report target'),
115
+ reports: (0, commitment_terms_1.removeByKey)(reports, reportKey(matches[0]), reportKey, 'Report target'),
103
116
  };
104
117
  }
105
118
  }
@@ -13,8 +13,11 @@ import { BaseCommand } from '../../base-command';
13
13
  * `freshnessExpectation` thresholds the loop uses to detect stale open loops.
14
14
  * `--criterion` still REPLACES the criteria list (that is its purpose).
15
15
  *
16
- * Autonomy is deliberately not settable here `policy` is likewise replaced
17
- * wholesale. Use `skrr commitments autonomy <id>`.
16
+ * Autonomy is deliberately not settable here: raising it wants a recorded
17
+ * reason, which `skrr commitments autonomy <id>` requires and this command has
18
+ * no place for. Other policy fields ARE patchable — the API deep-merges a
19
+ * `policy` patch (`mergePolicyPatch`), so a single-key patch leaves the rest of
20
+ * the contract untouched.
18
21
  */
19
22
  export default class CommitmentsUpdate extends BaseCommand {
20
23
  static description: string;
@@ -25,8 +28,11 @@ export default class CommitmentsUpdate extends BaseCommand {
25
28
  static flags: {
26
29
  json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
27
30
  'from-json': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
31
+ 'if-version': import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
28
32
  title: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
29
33
  description: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
34
+ 'agent-directive': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
35
+ 'agent-directive-file': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
30
36
  status: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
31
37
  outcome: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
32
38
  deadline: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
@@ -40,6 +46,7 @@ export default class CommitmentsUpdate extends BaseCommand {
40
46
  cadence: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
41
47
  'cadence-mode': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
42
48
  timezone: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
49
+ isolation: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
43
50
  };
44
51
  run(): Promise<void>;
45
52
  }
@@ -20,8 +20,11 @@ const commitments_1 = require("../../lib/commitments");
20
20
  * `freshnessExpectation` thresholds the loop uses to detect stale open loops.
21
21
  * `--criterion` still REPLACES the criteria list (that is its purpose).
22
22
  *
23
- * Autonomy is deliberately not settable here `policy` is likewise replaced
24
- * wholesale. Use `skrr commitments autonomy <id>`.
23
+ * Autonomy is deliberately not settable here: raising it wants a recorded
24
+ * reason, which `skrr commitments autonomy <id>` requires and this command has
25
+ * no place for. Other policy fields ARE patchable — the API deep-merges a
26
+ * `policy` patch (`mergePolicyPatch`), so a single-key patch leaves the rest of
27
+ * the contract untouched.
25
28
  */
26
29
  class CommitmentsUpdate extends base_command_1.BaseCommand {
27
30
  static description = "Update a commitment's contract (title, target, cadence, sources)";
@@ -30,6 +33,7 @@ class CommitmentsUpdate extends base_command_1.BaseCommand {
30
33
  '<%= config.bin %> commitments update <commitment-id> --title "Ship v2 (Q3)" --outcome "v2 is live for all users"',
31
34
  '<%= config.bin %> commitments update <commitment-id> --watch tasks --watch github',
32
35
  '<%= config.bin %> commitments update <commitment-id> --criterion "signups confirmed" --evaluator metric --measure <measure-id>',
36
+ '<%= config.bin %> commitments update <commitment-id> --agent-directive-file ./review-policy.md',
33
37
  ];
34
38
  static args = {
35
39
  id: core_1.Args.string({ description: 'Commitment ID', required: true, ignoreStdin: true }),
@@ -39,8 +43,17 @@ class CommitmentsUpdate extends base_command_1.BaseCommand {
39
43
  'from-json': core_1.Flags.string({
40
44
  description: 'Path to a JSON file (or "-" for stdin) for fields not exposed as flags',
41
45
  }),
46
+ 'if-version': core_1.Flags.integer({
47
+ description: 'Only update this observed contract version; returns a conflict if it changed',
48
+ }),
42
49
  title: core_1.Flags.string({ description: 'New title' }),
43
50
  description: core_1.Flags.string({ description: 'New description' }),
51
+ 'agent-directive': core_1.Flags.string({
52
+ description: 'Replace the per-Commitment domain policy (max 8,000 characters). Mutually exclusive with --agent-directive-file.',
53
+ }),
54
+ 'agent-directive-file': core_1.Flags.string({
55
+ description: 'Read replacement domain policy from a text file, or - for stdin. Mutually exclusive with --agent-directive.',
56
+ }),
44
57
  status: core_1.Flags.string({
45
58
  description: 'New status. Invalid lifecycle jumps (e.g. completed → active) are rejected by the API.',
46
59
  options: [...commitments_1.COMMITMENT_STATUSES],
@@ -82,6 +95,14 @@ class CommitmentsUpdate extends base_command_1.BaseCommand {
82
95
  options: [...commitments_1.COMMITMENT_CADENCE_MODES],
83
96
  }),
84
97
  timezone: core_1.Flags.string({ description: 'IANA timezone for the cadence' }),
98
+ isolation: core_1.Flags.string({
99
+ // Sent as a single-key `policy` patch. The API deep-merges it, so this
100
+ // cannot blank an autonomy mode or an attention budget set elsewhere.
101
+ description: 'Where autonomous runs do their work: "ephemeral" (one-shot git worktree per run) or ' +
102
+ '"shared" (the daemon working tree, in place). Cannot be combined with a remediation ' +
103
+ 'capability.',
104
+ options: [...commitments_1.COMMITMENT_EXECUTION_ISOLATION_MODES],
105
+ }),
85
106
  };
86
107
  async run() {
87
108
  this.requireAuth();
@@ -115,10 +136,18 @@ class CommitmentsUpdate extends base_command_1.BaseCommand {
115
136
  let payload;
116
137
  try {
117
138
  payload = await (0, prompt_1.readJsonBody)(flags['from-json']);
139
+ if (flags['if-version'] !== undefined)
140
+ payload.expectedVersion = flags['if-version'];
118
141
  if (flags.title)
119
142
  payload.title = flags.title;
120
143
  if (flags.description !== undefined)
121
144
  payload.description = flags.description;
145
+ const agentDirective = await (0, commitments_1.readCommitmentAgentDirective)({
146
+ directive: flags['agent-directive'],
147
+ file: flags['agent-directive-file'],
148
+ });
149
+ if (agentDirective !== undefined)
150
+ payload.agentDirective = agentDirective;
122
151
  if (flags.status)
123
152
  payload.status = flags.status;
124
153
  if (wantsTarget) {
@@ -189,6 +218,11 @@ class CommitmentsUpdate extends base_command_1.BaseCommand {
189
218
  cadence.timezone = flags.timezone;
190
219
  payload.cadence = cadence;
191
220
  }
221
+ // A single key, deliberately. `mergePolicyPatch` on the server merges it
222
+ // into the stored policy, so naming only this field is what keeps the
223
+ // rest of the contract intact.
224
+ if (flags.isolation)
225
+ payload.policy = { executionIsolation: flags.isolation };
192
226
  }
193
227
  catch (err) {
194
228
  this.error(err.message, { exit: 1 });
@@ -22,6 +22,38 @@ const task_transcript_1 = require("../../lib/task-transcript");
22
22
  const web_url_1 = require("../../lib/web-url");
23
23
  const tasks_1 = require("../../lib/tasks");
24
24
  const git_task_1 = require("../../lib/git-task");
25
+ /**
26
+ * Forward any `--deliverable` value that is really a ledger coordinate.
27
+ *
28
+ * FAIL-OPEN, and quietly. This is a convenience bridge on the completion path:
29
+ * the completion itself is the operation the caller asked for, and a ledger
30
+ * write that fails must not take it down. A caller who needed the row and did
31
+ * not get it meets the gate's refusal, which names the command that writes it.
32
+ *
33
+ * `local`, never anything stronger — this is the executor talking about its own
34
+ * work, which is exactly what the authority ladder is built to weigh.
35
+ */
36
+ async function recordCoordinateDeliverables({ taskId, values, warn, }) {
37
+ const seen = new Set();
38
+ for (const value of values) {
39
+ const raw = (value ?? '').trim();
40
+ if (!raw || raw === 'none' || seen.has(raw)) {
41
+ continue;
42
+ }
43
+ seen.add(raw);
44
+ const parsed = (0, tasks_1.parseDeliverableCoordinate)(raw);
45
+ if (!parsed.ok) {
46
+ continue;
47
+ }
48
+ try {
49
+ await data_provider_1.dataService.addTaskDeliverable(taskId, parsed.value);
50
+ }
51
+ catch {
52
+ warn(`Recorded "${raw}" in the report but not in the deliverables ledger. ` +
53
+ `If the task refuses to close, run \`tasks deliverable add\` for it.`);
54
+ }
55
+ }
56
+ }
25
57
  /**
26
58
  * Mutation endpoints require the task's canonical UUID even though the CLI
27
59
  * accepts human-readable refs and titles. Resolve the display reference before
@@ -483,6 +515,31 @@ class TasksComplete extends base_command_1.BaseCommand {
483
515
  });
484
516
  }
485
517
  }
518
+ // THE THIRD SPELLING, BRIDGED.
519
+ //
520
+ // `--deliverable` here is PROSE for the TASK_OUTPUT block. The ledger the
521
+ // completion gate reads is written by `tasks deliverable add`, and nothing
522
+ // connected the two — so an agent that ran
523
+ // `tasks complete --deliverable code:abc@origin/main` had, from its own
524
+ // point of view, recorded its deliverable, and was then refused by a gate
525
+ // whose message told it to record its deliverable.
526
+ //
527
+ // Three surfaces in one CLI answer to the word (`tasks deliverable add`,
528
+ // this flag, and `tasks report --deliverable`), and the project's own
529
+ // operating instructions teach the two that do NOT write the ledger. That
530
+ // is a documentation problem only if you believe people read the docs
531
+ // before the error; the cheaper fix is to make the wrong spelling do the
532
+ // right thing when what was typed is unambiguously a coordinate.
533
+ //
534
+ // Deliberately narrow: only values that PARSE as `<kind>:<identity>` are
535
+ // forwarded. A prose reference like `report.pdf` is left exactly as it was —
536
+ // guessing a coordinate out of prose is how the ledger fills with rows
537
+ // nobody can look up again.
538
+ await recordCoordinateDeliverables({
539
+ taskId: completionTaskId,
540
+ values: [...(flags.deliverable ?? []), ...(flags.deliverables ?? [])],
541
+ warn: (message) => this.warn(message),
542
+ });
486
543
  let result;
487
544
  try {
488
545
  result = await completeTaskDurably({
@@ -71,7 +71,8 @@ class TasksCreate extends base_command_1.BaseCommand {
71
71
  description: 'Validate and print the create payload without writing a task',
72
72
  }),
73
73
  'from-json': core_1.Flags.string({
74
- description: 'Path to a JSON file (or "-" for stdin) to use as the request body',
74
+ description: 'Path to a JSON file (or "-" for stdin) to use as the request body. ' +
75
+ 'Accepts stored names (projectId, goalId) and create-flag aliases (project, goal, spawnedBy).',
75
76
  }),
76
77
  template: core_1.Flags.string({
77
78
  description: 'Saved task template ID, or an exact template name with --space. Creates its whole task tree; supplied task flags override the root.',
@@ -0,0 +1,35 @@
1
+ import { BaseCommand } from '../../../base-command';
2
+ /**
3
+ * `skrr tasks deliverable confirm <task> <kind>:<identity>[@<location>] --reason "<what you checked>"`
4
+ *
5
+ * Somebody looked, and it is there.
6
+ *
7
+ * WHY THIS IS A SEPARATE COMMAND from `deliverable add`. Recording and vouching
8
+ * are different claims. `add` says "this exists, and I am the one who made it";
9
+ * this says "I checked, independently of whoever made it". Five of the six
10
+ * outcome kinds have no automatic observer — nothing in the platform watches a
11
+ * Drive folder or a Slack channel — so without this rung an expectation for a
12
+ * PDF or a posted message could only ever be closed by waiving it, which
13
+ * records "we decided we did not need this" about work that was actually done.
14
+ *
15
+ * The reason is required, for the same purpose a waiver's and a judgement's
16
+ * are: the entire value of a confirmation is that a person actually looked, and
17
+ * a confirmation with nothing written down is indistinguishable from a click.
18
+ *
19
+ * It UPSERTS. Confirming something the executor never recorded is legitimate —
20
+ * the thing is there either way — and making a person file the row first before
21
+ * they may vouch for it is ceremony that buys nothing.
22
+ */
23
+ export default class TasksDeliverableConfirm extends BaseCommand {
24
+ static description: string;
25
+ static examples: string[];
26
+ static args: {
27
+ task: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
28
+ coordinate: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
29
+ };
30
+ static flags: {
31
+ reason: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
32
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
33
+ };
34
+ run(): Promise<void>;
35
+ }