@skrr-ai/cli 0.1.14 → 0.1.15

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.
Files changed (90) hide show
  1. package/dist/base-command.d.ts +14 -0
  2. package/dist/base-command.js +60 -1
  3. package/dist/commands/agents/create.d.ts +1 -0
  4. package/dist/commands/agents/create.js +6 -1
  5. package/dist/commands/agents/instructions/add.d.ts +21 -0
  6. package/dist/commands/agents/instructions/add.js +115 -0
  7. package/dist/commands/agents/instructions/disable.d.ts +13 -0
  8. package/dist/commands/agents/instructions/disable.js +50 -0
  9. package/dist/commands/agents/instructions/edit.d.ts +16 -0
  10. package/dist/commands/agents/instructions/edit.js +79 -0
  11. package/dist/commands/agents/instructions/enable.d.ts +13 -0
  12. package/dist/commands/agents/instructions/enable.js +50 -0
  13. package/dist/commands/agents/instructions/move.d.ts +15 -0
  14. package/dist/commands/agents/instructions/move.js +61 -0
  15. package/dist/commands/agents/instructions/preview.d.ts +13 -0
  16. package/dist/commands/agents/instructions/preview.js +59 -0
  17. package/dist/commands/agents/instructions/remove.d.ts +13 -0
  18. package/dist/commands/agents/instructions/remove.js +49 -0
  19. package/dist/commands/agents/instructions/set.d.ts +17 -0
  20. package/dist/commands/agents/instructions/set.js +79 -0
  21. package/dist/commands/agents/instructions/show.d.ts +15 -0
  22. package/dist/commands/agents/instructions/show.js +76 -0
  23. package/dist/commands/agents/instructions/upgrade.d.ts +16 -0
  24. package/dist/commands/agents/instructions/upgrade.js +77 -0
  25. package/dist/commands/agents/show.js +5 -0
  26. package/dist/commands/agents/triggers/policy.js +8 -2
  27. package/dist/commands/agents/update.d.ts +1 -0
  28. package/dist/commands/agents/update.js +8 -2
  29. package/dist/commands/daemon/index.js +2 -0
  30. package/dist/commands/daemon/recover-offline-queue.d.ts +9 -0
  31. package/dist/commands/daemon/recover-offline-queue.js +24 -0
  32. package/dist/commands/daemon/start.js +29 -1
  33. package/dist/commands/instruction-blocks/create.d.ts +19 -0
  34. package/dist/commands/instruction-blocks/create.js +94 -0
  35. package/dist/commands/instruction-blocks/delete.d.ts +13 -0
  36. package/dist/commands/instruction-blocks/delete.js +35 -0
  37. package/dist/commands/instruction-blocks/list.d.ts +13 -0
  38. package/dist/commands/instruction-blocks/list.js +71 -0
  39. package/dist/commands/instruction-blocks/show.d.ts +12 -0
  40. package/dist/commands/instruction-blocks/show.js +35 -0
  41. package/dist/commands/instruction-blocks/update.d.ts +23 -0
  42. package/dist/commands/instruction-blocks/update.js +94 -0
  43. package/dist/commands/spaces/auto-executor/preview.d.ts +1 -0
  44. package/dist/commands/spaces/auto-executor/preview.js +62 -3
  45. package/dist/commands/spaces/auto-executor/update.d.ts +4 -0
  46. package/dist/commands/spaces/auto-executor/update.js +78 -7
  47. package/dist/commands/spaces/capacity.js +14 -2
  48. package/dist/commands/spaces/update.d.ts +4 -0
  49. package/dist/commands/spaces/update.js +49 -2
  50. package/dist/commands/tasks/complete.d.ts +12 -5
  51. package/dist/commands/tasks/complete.js +50 -28
  52. package/dist/commands/tasks/continue-ready.js +9 -1
  53. package/dist/commands/tasks/create.d.ts +1 -0
  54. package/dist/commands/tasks/create.js +25 -1
  55. package/dist/commands/tasks/deliverable/add.d.ts +23 -0
  56. package/dist/commands/tasks/deliverable/add.js +70 -0
  57. package/dist/commands/tasks/events/append.d.ts +1 -1
  58. package/dist/commands/tasks/events/append.js +4 -4
  59. package/dist/commands/tasks/expectations.d.ts +12 -0
  60. package/dist/commands/tasks/expectations.js +92 -0
  61. package/dist/commands/tasks/judge.d.ts +29 -0
  62. package/dist/commands/tasks/judge.js +68 -0
  63. package/dist/commands/tasks/report.d.ts +1 -1
  64. package/dist/commands/tasks/report.js +3 -3
  65. package/dist/commands/tasks/update.d.ts +1 -0
  66. package/dist/commands/tasks/update.js +34 -4
  67. package/dist/commands/tasks/waive.d.ts +25 -0
  68. package/dist/commands/tasks/waive.js +58 -0
  69. package/dist/lib/agent-config.js +5 -0
  70. package/dist/lib/agent-instructions.d.ts +15 -0
  71. package/dist/lib/agent-instructions.js +55 -0
  72. package/dist/lib/command-hygiene.d.ts +6 -1
  73. package/dist/lib/command-hygiene.js +49 -29
  74. package/dist/lib/exec-runtime-binary.d.ts +13 -0
  75. package/dist/lib/exec-runtime-binary.js +45 -0
  76. package/dist/lib/git-task.d.ts +37 -0
  77. package/dist/lib/git-task.js +151 -0
  78. package/dist/lib/instruction-blocks.d.ts +29 -0
  79. package/dist/lib/instruction-blocks.js +159 -0
  80. package/dist/lib/runtime-start-verification.d.ts +49 -0
  81. package/dist/lib/runtime-start-verification.js +108 -0
  82. package/dist/lib/task-execution.d.ts +18 -2
  83. package/dist/lib/task-execution.js +19 -14
  84. package/dist/lib/task-transcript.js +1 -1
  85. package/dist/lib/tasks.d.ts +50 -0
  86. package/dist/lib/tasks.js +66 -1
  87. package/dist/node_modules/@skrr-ai/data-provider/index.js +2027 -1904
  88. package/dist/node_modules/@skrr-ai/inference-broker/package.json +1 -1
  89. package/oclif.manifest.json +13706 -11858
  90. package/package.json +4 -1
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const core_1 = require("@oclif/core");
4
+ const data_provider_1 = require("@skrr-ai/data-provider");
4
5
  const base_command_1 = require("../../base-command");
5
6
  const format_1 = require("../../lib/format");
6
7
  const task_execution_1 = require("../../lib/task-execution");
@@ -33,11 +34,22 @@ class SpacesCapacity extends base_command_1.BaseCommand {
33
34
  return;
34
35
  }
35
36
  const space = capacity.space;
37
+ // `maxActiveTasks` / `availableSlots` are null when the space has no cap.
38
+ // Interpolating them directly is what produced `0/null active` (OSK-7087);
39
+ // the shared formatters in @skrr-ai/data-provider are the one place that
40
+ // decides how an absent ceiling reads, for the web and the CLI alike.
36
41
  this.log(`Space: ${space.title ?? args.id}`);
37
42
  this.log(`ID: ${space.id ?? args.id}`);
38
43
  this.log(`Auto exec: ${space.autoExecuteEnabled ? 'enabled' : 'disabled'}`);
39
- this.log(`Capacity: ${capacity.activeCount}/${capacity.maxActiveTasks} active`);
40
- this.log(`Available: ${capacity.availableSlots}`);
44
+ this.log(`Capacity: ${(0, data_provider_1.formatRunningCapacity)(capacity.activeCount, capacity.maxActiveTasks)} active` +
45
+ (capacity.maxActiveTasks === null ? ' (no concurrency cap)' : ''));
46
+ this.log(`Available: ${(0, data_provider_1.formatLimit)(capacity.availableSlots)}`);
47
+ if (capacity.budget) {
48
+ const { consumedStarts, maxStarts, remaining, unlimited } = capacity.budget;
49
+ this.log(unlimited
50
+ ? `Starts: ${consumedStarts} used this round (no per-run budget)`
51
+ : `Starts: ${consumedStarts}/${maxStarts} used this round, ${remaining} left`);
52
+ }
41
53
  this.log(`Sampled: ${capacity.sampledTaskCount} tasks`);
42
54
  if (capacity.activeTasks.length > 0) {
43
55
  this.log('');
@@ -20,6 +20,10 @@ export default class SpacesUpdate extends BaseCommand {
20
20
  'estimate-migration': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
21
21
  'default-estimate': import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
22
22
  'use-default-estimate': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
23
+ 'default-expect': import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
24
+ 'delivery-mode': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
25
+ 'integration-branch': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
26
+ 'auto-merge-tier': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
23
27
  'worktree-isolation': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
24
28
  };
25
29
  run(): Promise<void>;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const core_1 = require("@oclif/core");
4
4
  const data_provider_1 = require("@skrr-ai/data-provider");
5
+ const tasks_1 = require("../../lib/tasks");
5
6
  const base_command_1 = require("../../base-command");
6
7
  const web_url_1 = require("../../lib/web-url");
7
8
  const prompt_1 = require("../../lib/prompt");
@@ -70,6 +71,29 @@ class SpacesUpdate extends base_command_1.BaseCommand {
70
71
  allowNo: true,
71
72
  description: 'Whether rollups treat an unestimated task as --default-estimate',
72
73
  }),
74
+ 'default-expect': core_1.Flags.string({
75
+ multiple: true,
76
+ description: tasks_1.EXPECT_FLAG_HELP +
77
+ ' This is the space DEFAULT, and it is where the contract actually gets set: nobody ' +
78
+ 'declares one per task, but a space default is configured once and then holds. A ' +
79
+ 'per-task --expect wins. Merged into deliveryPolicy without clobbering its other fields.',
80
+ }),
81
+ 'delivery-mode': core_1.Flags.string({
82
+ options: ['none', 'branch', 'pr', 'pr_automerge', 'direct'],
83
+ description: "How this space's coding tasks integrate their work. 'pr' opens a pull request; " +
84
+ "'direct' pushes to the integration branch. Note that auto-merging modes are gated " +
85
+ 'by --auto-merge-tier: naming one here does not by itself authorise landing code.',
86
+ }),
87
+ 'integration-branch': core_1.Flags.string({
88
+ description: "The branch this space's coding tasks integrate into, e.g. main or dev. Also the " +
89
+ 'default target for a code expectation that names none.',
90
+ }),
91
+ 'auto-merge-tier': core_1.Flags.string({
92
+ options: ['off', 'checks_only', 'trusted'],
93
+ description: 'How far a task may go without a person. "off" (default) downgrades every auto-merging ' +
94
+ 'mode to a human-approved PR; "checks_only" honours pr_automerge; "trusted" also honours ' +
95
+ "direct. This is the space owner's sanction and it outranks any per-task override.",
96
+ }),
73
97
  'worktree-isolation': core_1.Flags.string({
74
98
  options: ['shared', 'ephemeral'],
75
99
  description: "Space default for how this space's autonomous task runs use git worktrees. " +
@@ -105,8 +129,24 @@ class SpacesUpdate extends base_command_1.BaseCommand {
105
129
  // which the API replaces wholesale on update. Merge into the policy the user
106
130
  // supplied via --from-json, else the space's current policy, so setting just
107
131
  // the isolation default never clobbers mode / integrationBranch / trust tier.
132
+ // Parse before any write: a bad spec must fail here, not reach the server
133
+ // as a silently-dropped field. A space default nobody is enforcing is worse
134
+ // than none, because every task under it then shows a contract that isn't real.
135
+ let defaultExpectations;
136
+ if (flags['default-expect'] !== undefined && flags['default-expect'].length > 0) {
137
+ const parsed = (0, tasks_1.parseExpectations)(flags['default-expect']);
138
+ if (!parsed.ok) {
139
+ this.error(parsed.error, { exit: 1 });
140
+ }
141
+ else {
142
+ defaultExpectations = parsed.value;
143
+ }
144
+ }
108
145
  const isolation = flags['worktree-isolation'];
109
- if (isolation) {
146
+ const deliveryMode = flags['delivery-mode'];
147
+ const integrationBranch = flags['integration-branch'];
148
+ const autoMergeTier = flags['auto-merge-tier'];
149
+ if (isolation || defaultExpectations || deliveryMode || integrationBranch || autoMergeTier) {
110
150
  let basePolicy = {};
111
151
  if (payload.deliveryPolicy && typeof payload.deliveryPolicy === 'object') {
112
152
  basePolicy = { ...payload.deliveryPolicy };
@@ -123,7 +163,14 @@ class SpacesUpdate extends base_command_1.BaseCommand {
123
163
  // Fall through: send a policy carrying only the isolation default.
124
164
  }
125
165
  }
126
- payload.deliveryPolicy = { ...basePolicy, executionIsolation: isolation };
166
+ payload.deliveryPolicy = {
167
+ ...basePolicy,
168
+ ...(isolation ? { executionIsolation: isolation } : {}),
169
+ ...(deliveryMode ? { mode: deliveryMode } : {}),
170
+ ...(integrationBranch ? { integrationBranch } : {}),
171
+ ...(autoMergeTier ? { autoMergeTrustTier: autoMergeTier } : {}),
172
+ ...(defaultExpectations ? { defaultExpectations } : {}),
173
+ };
127
174
  }
128
175
  // Goal links are NOT part of the PATCH body — the server rejects a PATCH
129
176
  // /api/spaces/:id carrying `goalIds` with `space_relations_atomic_only` and
@@ -1,6 +1,7 @@
1
1
  import { dataService } from '@skrr-ai/data-provider';
2
2
  import type { TaskReport } from '@skrr-ai/data-provider';
3
3
  import { BaseCommand } from '../../base-command';
4
+ import { type TaskDeliveryReceipt } from '../../lib/git-task';
4
5
  type CompletionStatus = 'completed' | 'failed';
5
6
  type TaskCommentResult = Awaited<ReturnType<typeof dataService.addTaskComment>>;
6
7
  type MovedTaskResult = Awaited<ReturnType<typeof dataService.moveTask>>;
@@ -9,15 +10,18 @@ export interface CompleteEndpointResponse {
9
10
  task: MovedTaskResult | null;
10
11
  comment: TaskCommentResult | null;
11
12
  commentFailed?: boolean;
13
+ deliveryHeld?: boolean;
14
+ deliveryReason?: string;
12
15
  }
13
16
  export interface CompleteTransport {
14
17
  /** POST /api/tasks/:taskId/complete — atomic move + TASK_OUTPUT comment. */
15
18
  postComplete: (taskId: string, payload: {
16
19
  status: CompletionStatus;
17
20
  summary: string;
18
- artifacts: string;
21
+ deliverables: string;
19
22
  verdict: string;
20
23
  report?: TaskReport;
24
+ deliveryReceipt?: TaskDeliveryReceipt;
21
25
  }) => Promise<CompleteEndpointResponse>;
22
26
  /** Legacy step 1 — POST /api/tasks/:taskId/comments. */
23
27
  addComment: (taskId: string, body: string) => Promise<TaskCommentResult>;
@@ -31,6 +35,8 @@ export interface DurableCompletionResult {
31
35
  movedTask: MovedTaskResult | null;
32
36
  moved: boolean;
33
37
  commentFailed: boolean;
38
+ deliveryHeld?: boolean;
39
+ deliveryReason?: string;
34
40
  /** true when the atomic /complete endpoint handled the finalization. */
35
41
  usedAtomicEndpoint: boolean;
36
42
  }
@@ -60,7 +66,7 @@ export declare function resolveCompletionTarget(taskRef: string, lookup: (taskRe
60
66
  * The repo contract calls this "the only terminal closeout", and a task has one
61
67
  * canonical outcome — but a second run used to succeed silently, post another
62
68
  * TASK_OUTPUT block, and leave `tasks output` rendering the throwaway one as
63
- * authoritative. The real summary and its artifacts link were still on the task
69
+ * authoritative. The real summary and its deliverables link were still on the task
64
70
  * but no longer canonical, which is the worst of the available failures: no
65
71
  * error, no warning, exit 0, and a record that now says something different.
66
72
  *
@@ -120,9 +126,10 @@ export declare function completeTaskDurably(opts: {
120
126
  status: CompletionStatus;
121
127
  body: string;
122
128
  summary: string;
123
- artifacts: string;
129
+ deliverables: string;
124
130
  verdict: string;
125
131
  report?: TaskReport;
132
+ deliveryReceipt?: TaskDeliveryReceipt;
126
133
  noMove: boolean;
127
134
  transport: CompleteTransport;
128
135
  }): Promise<DurableCompletionResult>;
@@ -139,8 +146,8 @@ export default class TasksComplete extends BaseCommand {
139
146
  amend: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
140
147
  status: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
141
148
  summary: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
142
- artifacts: import("@oclif/core/lib/interfaces").OptionFlag<string[], import("@oclif/core/lib/interfaces").CustomOptions>;
143
- artifact: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
149
+ deliverables: import("@oclif/core/lib/interfaces").OptionFlag<string[], import("@oclif/core/lib/interfaces").CustomOptions>;
150
+ deliverable: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
144
151
  verdict: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
145
152
  outcome: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
146
153
  finding: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
@@ -21,6 +21,7 @@ const task_instruction_offer_1 = require("../../lib/task-instruction-offer");
21
21
  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
+ const git_task_1 = require("../../lib/git-task");
24
25
  /**
25
26
  * Mutation endpoints require the task's canonical UUID even though the CLI
26
27
  * accepts human-readable refs and titles. Resolve the display reference before
@@ -55,7 +56,7 @@ async function resolveCompletionTarget(taskRef, lookup) {
55
56
  * The repo contract calls this "the only terminal closeout", and a task has one
56
57
  * canonical outcome — but a second run used to succeed silently, post another
57
58
  * TASK_OUTPUT block, and leave `tasks output` rendering the throwaway one as
58
- * authoritative. The real summary and its artifacts link were still on the task
59
+ * authoritative. The real summary and its deliverables link were still on the task
59
60
  * but no longer canonical, which is the worst of the available failures: no
60
61
  * error, no warning, exit 0, and a record that now says something different.
61
62
  *
@@ -128,7 +129,7 @@ exports.TaskCompletionMismatchError = TaskCompletionMismatchError;
128
129
  * error handling.
129
130
  */
130
131
  async function completeTaskDurably(opts) {
131
- const { taskId, status, body, summary, artifacts, verdict, report, noMove, transport } = opts;
132
+ const { taskId, status, body, summary, deliverables, verdict, report, deliveryReceipt, noMove, transport, } = opts;
132
133
  if (noMove) {
133
134
  const comment = await transport.addComment(taskId, body);
134
135
  return {
@@ -143,9 +144,10 @@ async function completeTaskDurably(opts) {
143
144
  const response = await transport.postComplete(taskId, {
144
145
  status,
145
146
  summary,
146
- artifacts,
147
+ deliverables,
147
148
  verdict,
148
149
  ...(report ? { report } : {}),
150
+ ...(deliveryReceipt ? { deliveryReceipt } : {}),
149
151
  });
150
152
  // Reconcile before reporting. On the session-scoped path the server owns
151
153
  // the task selection, so a divergence here means the task we were asked to
@@ -160,6 +162,8 @@ async function completeTaskDurably(opts) {
160
162
  movedTask: response.task ?? null,
161
163
  moved: true,
162
164
  commentFailed: response.commentFailed === true,
165
+ ...(response.deliveryHeld ? { deliveryHeld: true } : {}),
166
+ ...(response.deliveryReason ? { deliveryReason: response.deliveryReason } : {}),
163
167
  usedAtomicEndpoint: true,
164
168
  };
165
169
  }
@@ -174,7 +178,13 @@ async function completeTaskDurably(opts) {
174
178
  // behavior (comment first, then move).
175
179
  const comment = await transport.addComment(taskId, body);
176
180
  const movedTask = await transport.moveTask(taskId, status === 'completed' ? 'done' : 'failed');
177
- return { comment, movedTask, moved: true, commentFailed: false, usedAtomicEndpoint: false };
181
+ return {
182
+ comment,
183
+ movedTask,
184
+ moved: true,
185
+ commentFailed: false,
186
+ usedAtomicEndpoint: false,
187
+ };
178
188
  }
179
189
  class TasksComplete extends base_command_1.BaseCommand {
180
190
  static description = 'Post a canonical TASK_OUTPUT comment and finish a task';
@@ -199,7 +209,7 @@ class TasksComplete extends base_command_1.BaseCommand {
199
209
  description: 'Task ID or identifier; overrides daemon runtime context',
200
210
  }),
201
211
  'from-json': core_1.Flags.string({
202
- description: 'Path to JSON with taskId, status, summary, artifacts, and verdict; "-" for stdin',
212
+ description: 'Path to JSON with taskId, status, summary, deliverables, and verdict; "-" for stdin',
203
213
  }),
204
214
  amend: core_1.Flags.boolean({
205
215
  description: 'Supersede the canonical outcome of an already-completed task. Without it, a differing report on a closed task is refused',
@@ -210,13 +220,13 @@ class TasksComplete extends base_command_1.BaseCommand {
210
220
  default: 'completed',
211
221
  }),
212
222
  summary: core_1.Flags.string({ description: 'Completion summary' }),
213
- artifacts: core_1.Flags.string({
214
- description: 'Artifacts produced, or "none". Repeatable, and interchangeable with --artifact. ' +
215
- `The whole TASK_OUTPUT block (status + summary + artifacts + verdict) must fit in ${data_provider_1.TASK_OUTPUT_BODY_MAX} ` +
223
+ deliverables: core_1.Flags.string({
224
+ description: 'Deliverables produced, or "none". Repeatable, and interchangeable with --deliverable. ' +
225
+ `The whole TASK_OUTPUT block (status + summary + deliverables + verdict) must fit in ${data_provider_1.TASK_OUTPUT_BODY_MAX} ` +
216
226
  'characters — link to commits, PRs, or file paths rather than pasting content.',
217
- // Repeatable, even though `--artifact` already is. The two spellings sit
218
- // one letter apart and both read as "the artifacts go here", so the wrong
219
- // one gets typed — and oclif answered that with "Flag --artifacts can only
227
+ // Repeatable, even though `--deliverable` already is. The two spellings sit
228
+ // one letter apart and both read as "the deliverables go here", so the wrong
229
+ // one gets typed — and oclif answered that with "Flag --deliverables can only
220
230
  // be specified once", which names the flag you used and not the sibling
221
231
  // that does what you wanted. Dogfooded (OSK-266) by losing a completion
222
232
  // call to exactly that, then re-running with the two references pre-joined
@@ -224,9 +234,9 @@ class TasksComplete extends base_command_1.BaseCommand {
224
234
  multiple: true,
225
235
  default: ['none'],
226
236
  }),
227
- artifact: core_1.Flags.string({
237
+ deliverable: core_1.Flags.string({
228
238
  description: 'A single artifact reference (repeatable); joined with commas. Prefer this ' +
229
- 'over pre-joining into --artifacts.',
239
+ 'over pre-joining into --deliverables.',
230
240
  multiple: true,
231
241
  }),
232
242
  verdict: core_1.Flags.string({ description: 'Final verdict', default: 'approved' }),
@@ -271,13 +281,13 @@ class TasksComplete extends base_command_1.BaseCommand {
271
281
  summary: flags.summary,
272
282
  // Both spellings, pooled, then normalized to the one-line form the server
273
283
  // stores. Pooling rather than letting one win: someone who passes both has
274
- // named artifacts in each, and silently dropping half of them is worse
284
+ // named deliverables in each, and silently dropping half of them is worse
275
285
  // than either flag being unavailable.
276
- artifacts: (0, data_provider_1.normalizeTaskOutputArtifacts)([
277
- ...(flags.artifact ?? []),
286
+ deliverables: (0, data_provider_1.normalizeTaskOutputDeliverables)([
287
+ ...(flags.deliverable ?? []),
278
288
  // The literal default is dropped when it is only the default; a user
279
- // who typed `--artifacts none` alongside a real one meant the real one.
280
- ...(flags.artifacts ?? []).filter((value) => value !== 'none'),
289
+ // who typed `--deliverables none` alongside a real one meant the real one.
290
+ ...(flags.deliverables ?? []).filter((value) => value !== 'none'),
281
291
  ]) || 'none',
282
292
  verdict: flags.verdict,
283
293
  });
@@ -312,14 +322,14 @@ class TasksComplete extends base_command_1.BaseCommand {
312
322
  }
313
323
  let status;
314
324
  let summary;
315
- let artifacts;
325
+ let deliverables;
316
326
  let verdict;
317
327
  let report;
318
328
  try {
319
329
  status = normalizeStatus(input.status);
320
330
  summary = requireOutputField(input.summary, 'summary');
321
- artifacts =
322
- optionalOutputField((0, data_provider_1.normalizeTaskOutputArtifacts)(input.artifacts)) || 'none';
331
+ deliverables =
332
+ optionalOutputField((0, data_provider_1.normalizeTaskOutputDeliverables)(input.deliverables)) || 'none';
323
333
  verdict =
324
334
  optionalOutputField(input.verdict) || (status === 'completed' ? 'approved' : 'failed');
325
335
  const jsonReport = input.report && typeof input.report === 'object'
@@ -360,7 +370,7 @@ class TasksComplete extends base_command_1.BaseCommand {
360
370
  // budget the server enforces. Discovering an undocumented cap by HTTP 400
361
371
  // after a run has already finished is the failure being fixed; a caller must
362
372
  // be able to see the limit without provoking it.
363
- const budget = (0, data_provider_1.measureTaskOutputBudget)({ status, summary, artifacts, verdict });
373
+ const budget = (0, data_provider_1.measureTaskOutputBudget)({ status, summary, deliverables, verdict });
364
374
  if (!budget.fits) {
365
375
  this.failWithCliError({
366
376
  message: (0, data_provider_1.describeTaskOutputOverflow)(budget),
@@ -375,14 +385,21 @@ class TasksComplete extends base_command_1.BaseCommand {
375
385
  },
376
386
  });
377
387
  }
378
- const body = formatTaskOutput({ status, summary, artifacts, verdict });
388
+ const body = formatTaskOutput({ status, summary, deliverables, verdict });
389
+ // Artifact text such as a commit SHA is a delivery claim. The local probe
390
+ // both verifies remote-default ancestry and anchors a commit that has no
391
+ // local branch before the completion request can release this run.
392
+ const deliveryReceipt = status === 'completed' && !flags['no-move']
393
+ ? ((0, git_task_1.inspectTaskDeliveryArtifacts)({ deliverables, taskRef: taskIdentity.id }) ?? undefined)
394
+ : undefined;
379
395
  // Computed once, OUTSIDE the try, so the original attempt and the queued
380
396
  // replay carry the same key. Must match the payload the outbox stores.
381
397
  const completionPayload = {
382
398
  summary,
383
- artifacts,
399
+ deliverables,
384
400
  verdict,
385
401
  ...(report ? { report } : {}),
402
+ ...(deliveryReceipt ? { deliveryReceipt } : {}),
386
403
  noMove: !!flags['no-move'],
387
404
  };
388
405
  const completionIdempotencyKey = (0, outbox_1.idempotencyKeyFor)('task-complete', taskIdentity.id, JSON.stringify(completionPayload));
@@ -456,7 +473,7 @@ class TasksComplete extends base_command_1.BaseCommand {
456
473
  if (decision === 'refuse') {
457
474
  this.failWithCliError({
458
475
  message: `Task ${completionTaskId} is already ${priorStatus}, and this report differs from its canonical ` +
459
- `outcome — completing again would silently replace it (artifacts included).\n` +
476
+ `outcome — completing again would silently replace it (deliverables included).\n` +
460
477
  ` → re-run with \`--amend\` to supersede it deliberately, or read what is there now with ` +
461
478
  `\`${this.config.bin} tasks output ${completionTaskId}\`.`,
462
479
  code: 'TASK_ALREADY_COMPLETED',
@@ -473,9 +490,10 @@ class TasksComplete extends base_command_1.BaseCommand {
473
490
  status,
474
491
  body,
475
492
  summary,
476
- artifacts,
493
+ deliverables,
477
494
  verdict,
478
495
  report,
496
+ deliveryReceipt,
479
497
  noMove: !!flags['no-move'],
480
498
  transport: {
481
499
  postComplete: (taskId, payload) => (0, api_fetch_1.apiFetch)(
@@ -496,7 +514,7 @@ class TasksComplete extends base_command_1.BaseCommand {
496
514
  });
497
515
  }
498
516
  catch (err) {
499
- // The completion summary/artifacts/verdict are authored content — the
517
+ // The completion summary/deliverables/verdict are authored content — the
500
518
  // record of what the agent actually did. Losing that to a 503 costs the
501
519
  // reasoning, not just a retry, so an OUTAGE queues it durably (OSK-2806)
502
520
  // while every 4xx still fails immediately through handleApiError.
@@ -531,11 +549,12 @@ class TasksComplete extends base_command_1.BaseCommand {
531
549
  taskOutput: {
532
550
  status,
533
551
  summary,
534
- artifacts,
552
+ deliverables,
535
553
  verdict,
536
554
  ...(report ? { report } : {}),
537
555
  body,
538
556
  },
557
+ ...(result.deliveryHeld ? { deliveryHeld: true, deliveryReason: result.deliveryReason } : {}),
539
558
  };
540
559
  if (flags.json) {
541
560
  const url = movedTask
@@ -558,6 +577,9 @@ class TasksComplete extends base_command_1.BaseCommand {
558
577
  ? `Task ${completedTaskId} was already ${newStatus}; the outcome was amended.`
559
578
  : `Moved task ${completedTaskId} to ${newStatus}`);
560
579
  }
580
+ if (result.deliveryHeld) {
581
+ this.warn('Delivery is not verified on the default branch; the task was held at in_review instead of done.');
582
+ }
561
583
  if (report) {
562
584
  const url = (0, web_url_1.taskUrlFrom)(this.cliConfig.baseURL, movedTask ?? { id: completedTaskId });
563
585
  if (url)
@@ -70,7 +70,15 @@ class TasksContinueReady extends base_command_1.BaseCommand {
70
70
  this.handleApiError(err);
71
71
  }
72
72
  }
73
- const freeSlots = capacity ? capacity.availableSlots : Number.POSITIVE_INFINITY;
73
+ // `availableSlots` is null for an UNLIMITED space, which is the default.
74
+ // `Math.min(n, null)` is 0, so the previous arithmetic silently computed a
75
+ // start limit of zero and reported every ready task as "skipped for
76
+ // capacity" on exactly the spaces that had no capacity limit at all. The
77
+ // platform's own helper warns about this: callers must branch on null
78
+ // rather than do arithmetic on it (SpaceTaskConcurrency.availableSlotsFor).
79
+ const freeSlots = capacity && capacity.availableSlots !== null
80
+ ? capacity.availableSlots
81
+ : Number.POSITIVE_INFINITY;
74
82
  const startLimit = Math.max(0, Math.min(flags['max-starts'], freeSlots));
75
83
  const selected = ready.slice(0, startLimit);
76
84
  const skippedForCapacity = ready.slice(startLimit);
@@ -53,6 +53,7 @@ export default class TasksCreate extends BaseCommand {
53
53
  'assignee-user': import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
54
54
  'dri-agent': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
55
55
  'dri-user': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
56
+ expect: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
56
57
  'execution-mode': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
57
58
  'start-date': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
58
59
  'due-date': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
@@ -136,6 +136,12 @@ class TasksCreate extends base_command_1.BaseCommand {
136
136
  'dri-user': core_1.Flags.string({
137
137
  description: 'Explicit accountable human DRI (separate from assignees)',
138
138
  }),
139
+ expect: core_1.Flags.string({
140
+ multiple: true,
141
+ description: tasks_1.EXPECT_FLAG_HELP +
142
+ " Injected into the agent's instructions and checked against what the run produced. " +
143
+ 'Omit to inherit the space default.',
144
+ }),
139
145
  'execution-mode': core_1.Flags.string({
140
146
  description: 'Who executes the task: agent or human. Required when both Humans and Agents are ' +
141
147
  'assigned. Passing "human" with no assignee flags files the task against you, ' +
@@ -184,7 +190,25 @@ class TasksCreate extends base_command_1.BaseCommand {
184
190
  retryable: false,
185
191
  });
186
192
  }
187
- const payload = (0, tasks_1.buildTaskCreatePayload)(body, flags);
193
+ // Resolve --expect BEFORE any write. A bad spec must fail here rather than
194
+ // reaching the server as a silently-dropped field: a declaration that did
195
+ // not stick is worse than none, because the board then shows a contract
196
+ // nobody is enforcing.
197
+ const parsedExpectations = (0, tasks_1.parseExpectations)(flags.expect);
198
+ if (!parsedExpectations.ok) {
199
+ this.failWithCliError({
200
+ message: parsedExpectations.error,
201
+ code: 'INVALID_TASK_EXPECTATION',
202
+ exit: 1,
203
+ retryable: false,
204
+ });
205
+ }
206
+ const payload = (0, tasks_1.buildTaskCreatePayload)(body, {
207
+ ...flags,
208
+ expectations: parsedExpectations.ok && parsedExpectations.value.length > 0
209
+ ? parsedExpectations.value
210
+ : undefined,
211
+ });
188
212
  const isTemplateInstantiation = typeof payload.templateId === 'string' && payload.templateId.length > 0;
189
213
  // Template defaults (including its assigned agents) are part of the saved
190
214
  // resource. Do not let the ordinary "who owns an empty task?" policy
@@ -0,0 +1,23 @@
1
+ import { BaseCommand } from '../../../base-command';
2
+ /**
3
+ * `skrr tasks deliverable add <task> <kind>:<identity>[@<location>]`
4
+ *
5
+ * The command an agent runs to say what it produced. Deliberately small: one
6
+ * positional carrying a coordinate it already knows, because the measured
7
+ * behaviour is that agents emit a real commit sha 77% of the time when asked
8
+ * for it in a required field, and 0% of the time when asked for it in an
9
+ * optional structured one.
10
+ */
11
+ export default class TasksDeliverableAdd extends BaseCommand {
12
+ static description: string;
13
+ static examples: string[];
14
+ static args: {
15
+ task: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
16
+ coordinate: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
17
+ };
18
+ static flags: {
19
+ lifecycle: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
20
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
21
+ };
22
+ run(): Promise<void>;
23
+ }
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const data_provider_1 = require("@skrr-ai/data-provider");
5
+ const base_command_1 = require("../../../base-command");
6
+ const tasks_1 = require("../../../lib/tasks");
7
+ /**
8
+ * `skrr tasks deliverable add <task> <kind>:<identity>[@<location>]`
9
+ *
10
+ * The command an agent runs to say what it produced. Deliberately small: one
11
+ * positional carrying a coordinate it already knows, because the measured
12
+ * behaviour is that agents emit a real commit sha 77% of the time when asked
13
+ * for it in a required field, and 0% of the time when asked for it in an
14
+ * optional structured one.
15
+ */
16
+ class TasksDeliverableAdd extends base_command_1.BaseCommand {
17
+ static description = 'Record something this task produced (a commit, a page, a URL, a file, a message, a dataset)';
18
+ static examples = [
19
+ '<%= config.bin %> tasks deliverable add <task-id> code:a1b2c3d@origin/main',
20
+ '<%= config.bin %> tasks deliverable add <task-id> page:d_92@Engineering/deploy',
21
+ '<%= config.bin %> tasks deliverable add <task-id> file/pdf:f_71@Drive/Reports',
22
+ '<%= config.bin %> tasks deliverable add <task-id> code:a1b2c3d --lifecycle in_review',
23
+ ];
24
+ static args = {
25
+ task: core_1.Args.string({ description: 'Task id or ref', required: true, ignoreStdin: true }),
26
+ coordinate: core_1.Args.string({
27
+ description: '<kind>[/<format>]:<identity>[@<location>] — e.g. code:a1b2c3d@origin/main, ' +
28
+ 'file/pdf:report_92@Drive/Reports',
29
+ required: true,
30
+ ignoreStdin: true,
31
+ }),
32
+ };
33
+ static flags = {
34
+ lifecycle: core_1.Flags.string({
35
+ options: ['local', 'in_review', 'durable', 'blocked', 'superseded'],
36
+ description: 'How far along it is. Defaults to `local` — recording that a commit exists is not a ' +
37
+ 'claim that it landed. Only an external receipt should assert `durable`.',
38
+ }),
39
+ json: core_1.Flags.boolean({ description: 'Output as JSON' }),
40
+ };
41
+ async run() {
42
+ this.requireAuth();
43
+ const { args, flags } = await this.parse(TasksDeliverableAdd);
44
+ const parsed = (0, tasks_1.parseDeliverableCoordinate)(args.coordinate);
45
+ if (!parsed.ok) {
46
+ // For an agent this message IS the documentation — it hits the error and
47
+ // corrects, so the refusal carries the whole form and vocabulary.
48
+ this.error(parsed.error, { exit: 1 });
49
+ return;
50
+ }
51
+ let result;
52
+ try {
53
+ result = await data_provider_1.dataService.addTaskDeliverable(args.task, {
54
+ ...parsed.value,
55
+ ...(flags.lifecycle ? { lifecycle: flags.lifecycle } : {}),
56
+ });
57
+ }
58
+ catch (err) {
59
+ this.handleApiError(err);
60
+ return;
61
+ }
62
+ if (flags.json) {
63
+ this.log(JSON.stringify(result, null, 2));
64
+ return;
65
+ }
66
+ const where = parsed.value.location ? ` at ${parsed.value.location}` : '';
67
+ this.log(`Recorded ${parsed.value.kind}:${parsed.value.identity}${where} (${flags.lifecycle || 'local'}).`);
68
+ }
69
+ }
70
+ exports.default = TasksDeliverableAdd;
@@ -15,7 +15,7 @@ export default class TasksEventsAppend extends BaseCommand {
15
15
  changed: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
16
16
  next: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
17
17
  evidence: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
18
- artifact: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
18
+ deliverable: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
19
19
  };
20
20
  run(): Promise<void>;
21
21
  }
@@ -15,7 +15,7 @@ class TasksEventsAppend extends base_command_1.BaseCommand {
15
15
  static examples = [
16
16
  '<%= config.bin %> tasks events append <task-id> --kind checkpoint --idempotency-key "<task-id>:first-pass" --summary "Parser handles nested fences"',
17
17
  '<%= config.bin %> tasks events append <task-id> --kind decision --idempotency-key "<task-id>:storage-choice" --summary "Chose the CRDT path" --evidence "A direct contentHtml write is reverted by the next render tick"',
18
- '<%= config.bin %> tasks events append <task-id> --kind verification --idempotency-key "<task-id>:verified" --summary "Repro no longer reproduces" --artifact <commit-sha>',
18
+ '<%= config.bin %> tasks events append <task-id> --kind verification --idempotency-key "<task-id>:verified" --summary "Repro no longer reproduces" --deliverable <commit-sha>',
19
19
  ];
20
20
  static args = {
21
21
  id: core_1.Args.string({
@@ -54,9 +54,9 @@ class TasksEventsAppend extends base_command_1.BaseCommand {
54
54
  evidence: core_1.Flags.string({
55
55
  description: 'Why it is true — an observation, command output, or a before → after',
56
56
  }),
57
- artifact: core_1.Flags.string({
57
+ deliverable: core_1.Flags.string({
58
58
  multiple: true,
59
- description: 'Durable reference: a file path, commit sha, or URL. Repeatable. "in the conversation" is not an artifact',
59
+ description: 'Durable reference: a file path, commit sha, or URL. Repeatable. "in the conversation" is not a deliverable',
60
60
  }),
61
61
  };
62
62
  async run() {
@@ -70,7 +70,7 @@ class TasksEventsAppend extends base_command_1.BaseCommand {
70
70
  changed: flags.changed,
71
71
  next: flags.next,
72
72
  evidence: flags.evidence,
73
- artifacts: flags.artifact?.map((ref) => ({ label: 'artifact', ref })),
73
+ artifacts: flags.deliverable?.map((ref) => ({ label: 'deliverable', ref })),
74
74
  };
75
75
  const path = await (0, session_task_endpoints_1.sessionScopedTaskPath)('event');
76
76
  let result;
@@ -0,0 +1,12 @@
1
+ import { BaseCommand } from '../../base-command';
2
+ export default class TasksExpectations extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ task: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
7
+ };
8
+ static flags: {
9
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
+ };
11
+ run(): Promise<void>;
12
+ }