@skrr-ai/cli 0.1.37 → 0.1.39

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 (177) hide show
  1. package/bin/dev-fallback.js +175 -4
  2. package/dist/base-command.js +46 -2
  3. package/dist/commands/agents/actions/create.js +18 -7
  4. package/dist/commands/agents/actions/delete.d.ts +1 -0
  5. package/dist/commands/agents/actions/delete.js +7 -0
  6. package/dist/commands/agents/actions/update.js +5 -3
  7. package/dist/commands/agents/api-actions/add.js +5 -2
  8. package/dist/commands/agents/api-actions/index.d.ts +11 -0
  9. package/dist/commands/agents/api-actions/index.js +27 -1
  10. package/dist/commands/agents/avatar/default/index.d.ts +11 -0
  11. package/dist/commands/agents/avatar/default/index.js +17 -0
  12. package/dist/commands/agents/avatar/default/set.d.ts +2 -1
  13. package/dist/commands/agents/avatar/default/set.js +32 -7
  14. package/dist/commands/agents/avatar/upload.js +7 -1
  15. package/dist/commands/agents/categories/delete.js +8 -0
  16. package/dist/commands/agents/categories/index.d.ts +21 -5
  17. package/dist/commands/agents/categories/index.js +68 -15
  18. package/dist/commands/agents/chat.d.ts +21 -0
  19. package/dist/commands/agents/chat.js +94 -7
  20. package/dist/commands/agents/create.js +11 -4
  21. package/dist/commands/agents/list.d.ts +15 -0
  22. package/dist/commands/agents/list.js +31 -1
  23. package/dist/commands/agents/scene-background/upload.d.ts +30 -0
  24. package/dist/commands/agents/scene-background/upload.js +97 -0
  25. package/dist/commands/agents/show.js +13 -0
  26. package/dist/commands/agents/tools/calls.d.ts +23 -5
  27. package/dist/commands/agents/tools/calls.js +81 -18
  28. package/dist/commands/agents/update.js +15 -5
  29. package/dist/commands/code/{oversky-agent.d.ts → skrr-agent.d.ts} +3 -3
  30. package/dist/commands/code/{oversky-agent.js → skrr-agent.js} +11 -8
  31. package/dist/commands/convos/messages.d.ts +13 -0
  32. package/dist/commands/convos/messages.js +40 -16
  33. package/dist/commands/daemon/index.js +8 -1
  34. package/dist/commands/daemon/logs.d.ts +26 -0
  35. package/dist/commands/daemon/logs.js +56 -0
  36. package/dist/commands/daemon/uninstall.js +1 -1
  37. package/dist/commands/followups/act.js +3 -2
  38. package/dist/commands/followups/cancel.d.ts +6 -3
  39. package/dist/commands/followups/cancel.js +16 -6
  40. package/dist/commands/followups/observability.d.ts +23 -0
  41. package/dist/commands/followups/observability.js +114 -0
  42. package/dist/commands/followups/reschedule.js +1 -1
  43. package/dist/commands/followups/resolve.js +3 -3
  44. package/dist/commands/followups/show.js +2 -1
  45. package/dist/commands/followups/watch.js +9 -5
  46. package/dist/commands/harnesses/forget.js +28 -0
  47. package/dist/commands/harnesses/install.d.ts +41 -0
  48. package/dist/commands/harnesses/install.js +165 -0
  49. package/dist/commands/harnesses/installers.d.ts +45 -0
  50. package/dist/commands/harnesses/installers.js +155 -0
  51. package/dist/commands/harnesses/list.d.ts +82 -1
  52. package/dist/commands/harnesses/list.js +104 -12
  53. package/dist/commands/harnesses/models.js +10 -1
  54. package/dist/commands/harnesses/show.d.ts +15 -0
  55. package/dist/commands/harnesses/show.js +44 -3
  56. package/dist/commands/harnesses/update.js +1 -1
  57. package/dist/commands/harnesses/usage.d.ts +25 -0
  58. package/dist/commands/harnesses/usage.js +24 -2
  59. package/dist/commands/instructions/status.js +13 -1
  60. package/dist/commands/machines/dedicated/catalog.js +4 -15
  61. package/dist/commands/machines/dedicated/create.d.ts +8 -0
  62. package/dist/commands/machines/dedicated/create.js +63 -2
  63. package/dist/commands/machines/dedicated/destroy.js +19 -5
  64. package/dist/commands/machines/dedicated/exec.js +3 -1
  65. package/dist/commands/machines/dedicated/list.js +9 -6
  66. package/dist/commands/machines/dedicated/price-book.d.ts +17 -0
  67. package/dist/commands/machines/dedicated/price-book.js +35 -0
  68. package/dist/commands/machines/hosted/list.js +12 -0
  69. package/dist/commands/machines/list.js +11 -0
  70. package/dist/commands/machines/show.d.ts +1 -0
  71. package/dist/commands/machines/show.js +40 -11
  72. package/dist/commands/profiles/list.d.ts +23 -0
  73. package/dist/commands/profiles/list.js +96 -0
  74. package/dist/commands/search/skills.d.ts +11 -1
  75. package/dist/commands/search/skills.js +26 -8
  76. package/dist/commands/skills/details.d.ts +2 -0
  77. package/dist/commands/skills/details.js +24 -1
  78. package/dist/commands/skills/import-as-actions.d.ts +32 -0
  79. package/dist/commands/skills/import-as-actions.js +175 -0
  80. package/dist/commands/skills/install.d.ts +2 -0
  81. package/dist/commands/skills/install.js +24 -1
  82. package/dist/commands/skills/invocable.js +6 -2
  83. package/dist/commands/skills/local.d.ts +40 -0
  84. package/dist/commands/skills/local.js +160 -0
  85. package/dist/commands/skills/search.d.ts +11 -1
  86. package/dist/commands/skills/search.js +29 -8
  87. package/dist/commands/spaces/actions/add.js +23 -5
  88. package/dist/commands/spaces/actions/available.js +3 -0
  89. package/dist/commands/spaces/actions/list.d.ts +5 -0
  90. package/dist/commands/spaces/actions/list.js +49 -3
  91. package/dist/commands/spaces/actions/run.d.ts +28 -0
  92. package/dist/commands/spaces/actions/run.js +146 -0
  93. package/dist/commands/spaces/actions/update.js +27 -5
  94. package/dist/commands/spaces/label-summaries.d.ts +27 -0
  95. package/dist/commands/spaces/label-summaries.js +64 -0
  96. package/dist/commands/spaces/task-rollups.d.ts +5 -0
  97. package/dist/commands/spaces/task-rollups.js +15 -4
  98. package/dist/commands/spaces/workflows/lifecycle-diagnostics.js +63 -3
  99. package/dist/commands/tasks/actions/attach.js +32 -7
  100. package/dist/commands/tasks/complete.js +13 -0
  101. package/dist/commands/tasks/create.js +1 -1
  102. package/dist/commands/tasks/events/append.js +29 -6
  103. package/dist/commands/tasks/expectations/assess.d.ts +1 -0
  104. package/dist/commands/tasks/expectations/assess.js +43 -5
  105. package/dist/commands/tasks/expectations.js +77 -16
  106. package/dist/commands/tasks/follow-up.js +2 -2
  107. package/dist/commands/tasks/lifecycle/definitions.js +21 -2
  108. package/dist/commands/tasks/lifecycle/detach.d.ts +7 -0
  109. package/dist/commands/tasks/lifecycle/detach.js +43 -3
  110. package/dist/commands/tasks/lifecycle/migrate.d.ts +2 -0
  111. package/dist/commands/tasks/lifecycle/migrate.js +42 -3
  112. package/dist/commands/tasks/lifecycle/reopen.d.ts +2 -0
  113. package/dist/commands/tasks/lifecycle/reopen.js +40 -3
  114. package/dist/commands/tasks/lifecycle/transition.js +33 -2
  115. package/dist/commands/tasks/list.js +16 -9
  116. package/dist/commands/tasks/move.js +11 -13
  117. package/dist/commands/tasks/review-queue.d.ts +15 -0
  118. package/dist/commands/tasks/review-queue.js +180 -31
  119. package/dist/commands/tasks/show.d.ts +11 -0
  120. package/dist/commands/tasks/show.js +105 -12
  121. package/dist/commands/tasks/trust.js +7 -2
  122. package/dist/commands/tasks/update.js +46 -10
  123. package/dist/commands/tasks/updates/add.js +1 -1
  124. package/dist/commands/token/list.d.ts +22 -0
  125. package/dist/commands/token/list.js +68 -0
  126. package/dist/commands/token/revoke.d.ts +23 -0
  127. package/dist/commands/token/revoke.js +53 -0
  128. package/dist/lib/agent-actions.d.ts +57 -2
  129. package/dist/lib/agent-actions.js +113 -21
  130. package/dist/lib/agent-pin-mute.d.ts +10 -5
  131. package/dist/lib/agent-pin-mute.js +17 -4
  132. package/dist/lib/agent-visual-refs.d.ts +27 -0
  133. package/dist/lib/agent-visual-refs.js +127 -2
  134. package/dist/lib/agentic-stream.d.ts +6 -0
  135. package/dist/lib/agentic-stream.js +34 -3
  136. package/dist/lib/assignee-resolver.d.ts +1 -1
  137. package/dist/lib/assignee-resolver.js +8 -0
  138. package/dist/lib/bulk-task-targets.d.ts +12 -0
  139. package/dist/lib/bulk-task-targets.js +34 -1
  140. package/dist/lib/cli-installers.d.ts +130 -0
  141. package/dist/lib/cli-installers.js +145 -0
  142. package/dist/lib/command-miss.js +55 -7
  143. package/dist/lib/dedicated-lease-command.d.ts +16 -1
  144. package/dist/lib/dedicated-lease-command.js +26 -3
  145. package/dist/lib/dedicated-machines.d.ts +135 -1
  146. package/dist/lib/dedicated-machines.js +307 -3
  147. package/dist/lib/failed-lookup.d.ts +3 -0
  148. package/dist/lib/failed-lookup.js +40 -0
  149. package/dist/lib/first-party-harness-agent.js +9 -2
  150. package/dist/lib/first-party-harness-doctor.d.ts +15 -0
  151. package/dist/lib/first-party-harness-doctor.js +126 -27
  152. package/dist/lib/followups.d.ts +14 -6
  153. package/dist/lib/followups.js +31 -6
  154. package/dist/lib/harnesses.d.ts +8 -0
  155. package/dist/lib/instruction-input.d.ts +19 -1
  156. package/dist/lib/instruction-input.js +52 -1
  157. package/dist/lib/label-scope.d.ts +15 -0
  158. package/dist/lib/label-scope.js +25 -5
  159. package/dist/lib/local-skills.d.ts +98 -0
  160. package/dist/lib/local-skills.js +195 -0
  161. package/dist/lib/machines.d.ts +10 -10
  162. package/dist/lib/machines.js +23 -1
  163. package/dist/lib/search-query.d.ts +19 -0
  164. package/dist/lib/search-query.js +35 -0
  165. package/dist/lib/space-custom-actions.d.ts +72 -0
  166. package/dist/lib/space-custom-actions.js +117 -0
  167. package/dist/lib/space-resolver.d.ts +1 -21
  168. package/dist/lib/space-resolver.js +19 -10
  169. package/dist/lib/task-closure.d.ts +3 -2
  170. package/dist/lib/task-closure.js +18 -20
  171. package/dist/lib/task-lifecycle-output.js +117 -9
  172. package/dist/lib/task-ref-resolver.d.ts +1 -17
  173. package/dist/lib/task-ref-resolver.js +18 -8
  174. package/dist/lib/tasks.js +9 -3
  175. package/dist/node_modules/@skrr-ai/data-provider/index.js +5076 -4947
  176. package/oclif.manifest.json +15505 -14460
  177. package/package.json +9 -3
@@ -4,6 +4,7 @@ const core_1 = require("@oclif/core");
4
4
  const data_provider_1 = require("@skrr-ai/data-provider");
5
5
  const data_provider_2 = require("@skrr-ai/data-provider");
6
6
  const base_command_1 = require("../../base-command");
7
+ const data_provider_3 = require("@skrr-ai/data-provider");
7
8
  const task_closure_1 = require("../../lib/task-closure");
8
9
  /**
9
10
  * `skrr tasks expectations <task>` — what this task owes, and how far each got.
@@ -13,18 +14,21 @@ const task_closure_1 = require("../../lib/task-closure");
13
14
  * could disagree with the gate that refuses the task — and the reader would
14
15
  * have no way to tell which was lying.
15
16
  */
16
- const STATE_LABEL = {
17
- met: 'done',
18
- waived: 'dropped',
19
- blocked: 'stuck',
20
- // Its own word, because its next action is its own: somebody looks, rather
21
- // than somebody works. Reading it as 'not yet' sends people to redo finished
22
- // work, which is precisely the confusion the state was added to remove.
23
- unverified: 'needs a look',
24
- underway: 'on its way',
25
- owed: 'not yet',
26
- unassessed: 'needs assessment',
27
- };
17
+ /*
18
+ * The labels come from `@skrr-ai/data-provider`, not from here.
19
+ *
20
+ * This file said `dropped` where the user had typed `waived` and `done` where
21
+ * they had typed `met`; the web said `Waived` and `Met`; and an unjudged row
22
+ * read as `not yet` in one column and `needs assessment` in the trailer. Three
23
+ * spellings of one vocabulary is how a user stops trusting that the word they
24
+ * wrote is the word that was stored.
25
+ *
26
+ * A state derived from a DECISION now reads back as that decision's own word.
27
+ * `owed` and `unassessed` stay distinct — "nobody produced it" and "nobody
28
+ * judged it" are different next actions — and the legend below says so, which
29
+ * the near-synonymous English never could on its own.
30
+ */
31
+ const STATE_LABEL = data_provider_3.EXPECTATION_STATE_LABEL;
28
32
  /**
29
33
  * The evidence gap, on the rows where a reader can act on it.
30
34
  *
@@ -75,7 +79,9 @@ function evidenceGap(expectation) {
75
79
  function assessorSuffix(expectation) {
76
80
  if (!expectation.assessedByKind)
77
81
  return '';
78
- if (!['met', 'waived'].includes(expectation.state))
82
+ // `unmet` joins them: a rejection is a judgement, and whose it was matters at
83
+ // least as much as whose `met` it was.
84
+ if (!['met', 'waived', 'unmet'].includes(expectation.state))
79
85
  return '';
80
86
  return ` (${(0, task_closure_1.describeAssessor)(expectation.assessedByKind)})`;
81
87
  }
@@ -144,9 +150,13 @@ class TasksExpectations extends base_command_1.BaseCommand {
144
150
  // "2 of 2 met" for a Task where one expectation was dropped, which is the
145
151
  // one thing a reader checking this line needs to know.
146
152
  const waived = gating.filter((e) => e.state === 'waived').length;
147
- const met = gating.filter((e) => e.met).length - waived;
153
+ const met = gating.filter((e) => e.passed ?? e.state === 'met').length;
154
+ const rejected = gating.filter((e) => e.state === 'unmet').length;
148
155
  this.log('');
149
- this.log(`${met} of ${gating.length} met${waived > 0 ? ` · ${waived} waived` : ''} · overall: ${acceptance.state}`);
156
+ this.log(`${met} of ${gating.length} met` +
157
+ (waived > 0 ? ` · ${waived} waived` : '') +
158
+ (rejected > 0 ? ` · ${rejected} unmet` : '') +
159
+ ` · overall: ${acceptance.state}`);
150
160
  // Space defaults are copied into the Task at creation. `source=space`
151
161
  // remains useful provenance, but these are Task-owned Expectations now and
152
162
  // are assessed exactly like explicitly declared ones.
@@ -173,6 +183,24 @@ class TasksExpectations extends base_command_1.BaseCommand {
173
183
  if (settleable > 0) {
174
184
  this.log(`${settleable} awaiting assessment — use \`${this.config.bin} tasks expectations assess\`.`);
175
185
  }
186
+ /*
187
+ * A rejection is not an absence, and it used to read as one.
188
+ *
189
+ * `unmet` collapsed into `owed`/`unassessed`, so a reviewer's written "no"
190
+ * displayed as "needs assessment" — inviting someone to do the judging that
191
+ * had already been done, while the bulk settle skipped the row because it
192
+ * could see the assessment the display had hidden.
193
+ */
194
+ const rejectedRows = acceptance.expectations.filter((e) => e.state === 'unmet');
195
+ if (rejectedRows.length > 0) {
196
+ this.log(`${rejectedRows.length} judged unmet — fix the work and re-assess, or waive ` +
197
+ `${rejectedRows.length === 1 ? 'it' : 'them'}:`);
198
+ for (const row of rejectedRows) {
199
+ const who = row.assessedByKind ? ` by ${(0, task_closure_1.describeAssessor)(row.assessedByKind)}` : '';
200
+ const when = row.assessedAt ? ` on ${String(row.assessedAt).slice(0, 10)}` : '';
201
+ this.log(` ${row.id}${who}${when}${row.reason ? `: ${row.reason}` : ''}`);
202
+ }
203
+ }
176
204
  // Named separately from `unjudged`, and with a different command, because
177
205
  // the two look alike on the board and are not: one needs a verdict on prose,
178
206
  // the other needs somebody to look at a thing that already exists.
@@ -217,7 +245,40 @@ class TasksExpectations extends base_command_1.BaseCommand {
217
245
  const settled = gating.filter((e) => e.met).length;
218
246
  if (gating.length > settled) {
219
247
  this.log('');
220
- this.log('The current Result cannot be accepted while these Expectations remain unmet.');
248
+ /*
249
+ * Say what is OWED, not what is forbidden.
250
+ *
251
+ * This read: "The current Result cannot be accepted while these
252
+ * Expectations remain unmet." That gate stopped existing in `56a6ded703`
253
+ * — unmet Expectations are advisory, and `assertTaskMayClose` throws on
254
+ * exactly one code, which is not this one. The Task closes; the unmet
255
+ * items are stamped onto `resolution.closure`.
256
+ *
257
+ * So the sentence asserted an outcome this command never evaluated, and
258
+ * asserted it wrongly: an operator who believed it would go hunting for a
259
+ * way to force a close that needs no forcing. It is the same failure as
260
+ * the hard-coded "it is ready for review" that this batch removed,
261
+ * pointing the other way.
262
+ */
263
+ this.log(
264
+ // Tense-neutral on purpose: this command reads acceptance only, and
265
+ // fetching the Task as well — one extra round trip on every call — just
266
+ // to choose between "closing is allowed" and "was closed over these" is
267
+ // not worth it. This sentence is true before the close and after it.
268
+ `${gating.length - settled} Expectation(s) still owed. They do not block closing; ` +
269
+ 'whatever is outstanding when a Task closes is recorded on `resolution.closure`.');
270
+ }
271
+ /*
272
+ * A legend, because two of these states are near-synonyms in English and
273
+ * genuinely different in what they ask of the reader.
274
+ */
275
+ const shown = new Set(acceptance.expectations.map((e) => e.state));
276
+ const legend = ['owed', 'unassessed', 'unmet']
277
+ .filter((state) => shown.has(state))
278
+ .map((state) => `${data_provider_3.EXPECTATION_STATE_LABEL[state]} = ${data_provider_3.EXPECTATION_STATE_HINT[state]}`);
279
+ if (legend.length > 1) {
280
+ this.log('');
281
+ this.log(legend.join(' · '));
221
282
  }
222
283
  }
223
284
  }
@@ -73,8 +73,8 @@ class TasksFollowUp extends base_command_1.BaseCommand {
73
73
  description: 'Optional SLA fallback delay in seconds for event or poll_until follow-ups',
74
74
  }),
75
75
  visibility: core_1.Flags.string({
76
- description: 'Whether follow-up runs silently or sends a visible message',
77
- options: ['silent', 'notify'],
76
+ description: 'Always silent — the wake speaks only when the check finds something. Kept for compatibility; notify is not available',
77
+ options: ['silent'],
78
78
  }),
79
79
  priority: core_1.Flags.string({
80
80
  description: 'Follow-up task priority',
@@ -5,7 +5,13 @@ const data_provider_1 = require("@skrr-ai/data-provider");
5
5
  const base_command_1 = require("../../../base-command");
6
6
  class TasksLifecycleDefinitions extends base_command_1.BaseCommand {
7
7
  static description = 'List the lifecycle definitions and built-in packs a Task can attach';
8
- static args = { id: core_1.Args.string({ required: true, ignoreStdin: true }) };
8
+ static args = {
9
+ id: core_1.Args.string({
10
+ required: true,
11
+ ignoreStdin: true,
12
+ description: 'Task ID or identifier — definitions are listed for its Space',
13
+ }),
14
+ };
9
15
  static flags = { json: core_1.Flags.boolean({ description: 'Output JSON' }) };
10
16
  async run() {
11
17
  this.requireAuth();
@@ -24,12 +30,25 @@ class TasksLifecycleDefinitions extends base_command_1.BaseCommand {
24
30
  this.log('Built-in packs:');
25
31
  for (const pack of response.packs) {
26
32
  this.log(` ${pack.id} v${pack.packVersion} — ${pack.title}${pack.description ? ` — ${pack.description}` : ''}`);
33
+ if (pack.stageKeys?.length) {
34
+ this.log(` stages: ${pack.stageKeys.join(' → ')}`);
35
+ }
27
36
  }
28
37
  }
29
38
  if (response.definitions.length) {
30
39
  this.log('Space definitions:');
31
40
  for (const definition of response.definitions) {
32
- this.log(` ${definition.id} v${definition.version} — ${definition.title}${definition.packId ? ` (pack ${definition.packId})` : ''}`);
41
+ this.log(` ${definition.id} v${definition.version} — ${definition.title}${definition.packId ? ` (pack ${definition.packId} v${definition.packVersion ?? '?'})` : ''}`);
42
+ // `attach --version` and `migrate --version` take a VERSION id, and
43
+ // this was the only place they could come from — and it printed the
44
+ // definition id and a version NUMBER, neither of which those flags
45
+ // accept. `migrate` was unusable without reading the API.
46
+ if (definition.publishedVersionId) {
47
+ this.log(` published version: ${definition.publishedVersionId} (pass to --version)`);
48
+ }
49
+ if (definition.stageKeys?.length) {
50
+ this.log(` stages: ${definition.stageKeys.join(' → ')}`);
51
+ }
33
52
  }
34
53
  }
35
54
  if (!response.packs.length && !response.definitions.length) {
@@ -1,11 +1,18 @@
1
1
  import { BaseCommand } from '../../../base-command';
2
2
  export default class TasksLifecycleDetach extends BaseCommand {
3
3
  static description: string;
4
+ /**
5
+ * oclif prints `description` in help and nothing else, so a one-liner that
6
+ * repeats the summary tells a reader nothing about an operation that is not
7
+ * obviously reversible. Say what it does to each thing it touches.
8
+ */
9
+ static examples: string[];
4
10
  static args: {
5
11
  id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
6
12
  };
7
13
  static flags: {
8
14
  reason: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
15
+ 'dry-run': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
9
16
  json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
17
  };
11
18
  run(): Promise<void>;
@@ -5,15 +5,49 @@ const data_provider_1 = require("@skrr-ai/data-provider");
5
5
  const base_command_1 = require("../../../base-command");
6
6
  class TasksLifecycleDetach extends base_command_1.BaseCommand {
7
7
  static description = 'Detach the lifecycle from a Task — fences the generation, leaves the run as history';
8
- static args = { id: core_1.Args.string({ required: true, ignoreStdin: true }) };
8
+ /**
9
+ * oclif prints `description` in help and nothing else, so a one-liner that
10
+ * repeats the summary tells a reader nothing about an operation that is not
11
+ * obviously reversible. Say what it does to each thing it touches.
12
+ */
13
+ static examples = [
14
+ '<%= config.bin %> tasks lifecycle detach OSK-1234 --dry-run',
15
+ '<%= config.bin %> tasks lifecycle detach OSK-1234 --reason "Wrong protocol for this work"',
16
+ ];
17
+ static args = {
18
+ id: core_1.Args.string({ required: true, ignoreStdin: true, description: 'Task ID or identifier' }),
19
+ };
9
20
  static flags = {
10
- reason: core_1.Flags.string({ description: 'Why the lifecycle is detaching' }),
21
+ reason: core_1.Flags.string({ description: 'Why the lifecycle is detaching; recorded in history' }),
22
+ 'dry-run': core_1.Flags.boolean({
23
+ description: 'Describe what detaching would do and exit without writing',
24
+ }),
11
25
  json: core_1.Flags.boolean({ description: 'Output JSON' }),
12
26
  };
13
27
  async run() {
14
28
  this.requireAuth();
15
29
  const { args, flags } = await this.parse(TasksLifecycleDetach);
16
30
  const taskId = await this.resolveTaskReference(args.id);
31
+ if (flags['dry-run']) {
32
+ let current;
33
+ try {
34
+ current = await data_provider_1.dataService.getTaskLifecycle(taskId);
35
+ }
36
+ catch (error) {
37
+ this.handleApiError(error);
38
+ }
39
+ if (flags.json)
40
+ return this.log(JSON.stringify({ dryRun: true, ...current }, null, 2));
41
+ const lifecycle = current.lifecycle;
42
+ if (!lifecycle)
43
+ return this.log('No lifecycle is attached to this Task — nothing to detach.');
44
+ this.log(`Would detach ${lifecycle.packId ? `pack ${lifecycle.packId}` : `definition ${lifecycle.definitionId}`}` +
45
+ ` at generation ${lifecycle.generation}, stage ${lifecycle.currentStageKey ?? '—'}.`);
46
+ this.log(' The run is cancelled and kept as history; nothing already recorded is removed.');
47
+ this.log(' The generation is bumped, so no in-flight writer from the old run can land.');
48
+ this.log(' Re-attaching afterwards starts a NEW generation — it does not resume this one.');
49
+ return;
50
+ }
17
51
  let response;
18
52
  try {
19
53
  response = await data_provider_1.dataService.detachTaskLifecycle({
@@ -26,7 +60,13 @@ class TasksLifecycleDetach extends base_command_1.BaseCommand {
26
60
  }
27
61
  if (flags.json)
28
62
  return this.log(JSON.stringify(response, null, 2));
29
- this.log('Detached lifecycle from the Task.');
63
+ const lifecycle = response.lifecycle;
64
+ // Name what changed. "Detached lifecycle from the Task." left the reader to
65
+ // diff the state afterwards to learn what had happened.
66
+ this.log(`Detached${lifecycle?.packId ? ` pack ${lifecycle.packId}` : ''} — the run is cancelled and` +
67
+ ` kept as history, and the binding is now generation ${lifecycle?.generation ?? '?'}` +
68
+ ` (${lifecycle?.status ?? 'detached'}).`);
69
+ this.log('Re-attach with: skrr tasks lifecycle attach <task-id> --pack <pack>');
30
70
  }
31
71
  }
32
72
  exports.default = TasksLifecycleDetach;
@@ -1,11 +1,13 @@
1
1
  import { BaseCommand } from '../../../base-command';
2
2
  export default class TasksLifecycleMigrate extends BaseCommand {
3
3
  static description: string;
4
+ static examples: string[];
4
5
  static args: {
5
6
  id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
6
7
  };
7
8
  static flags: {
8
9
  version: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
10
+ 'dry-run': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
9
11
  json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
12
  };
11
13
  run(): Promise<void>;
@@ -5,15 +5,51 @@ const data_provider_1 = require("@skrr-ai/data-provider");
5
5
  const base_command_1 = require("../../../base-command");
6
6
  class TasksLifecycleMigrate extends base_command_1.BaseCommand {
7
7
  static description = 'Migrate a bound lifecycle to a newer published version, fencing the old generation';
8
- static args = { id: core_1.Args.string({ required: true, ignoreStdin: true }) };
8
+ static examples = [
9
+ '<%= config.bin %> tasks lifecycle definitions OSK-1234 # where a --version comes from',
10
+ '<%= config.bin %> tasks lifecycle migrate OSK-1234 --version wfv_… --dry-run',
11
+ ];
12
+ static args = {
13
+ id: core_1.Args.string({ required: true, ignoreStdin: true, description: 'Task ID or identifier' }),
14
+ };
9
15
  static flags = {
10
- version: core_1.Flags.string({ required: true, description: 'Target WorkflowDefinition version id' }),
16
+ version: core_1.Flags.string({
17
+ required: true,
18
+ description: 'Target published version id — `tasks lifecycle definitions <task-id>` prints it',
19
+ }),
20
+ 'dry-run': core_1.Flags.boolean({
21
+ description: 'Describe what migrating would do and exit without writing',
22
+ }),
11
23
  json: core_1.Flags.boolean({ description: 'Output JSON' }),
12
24
  };
13
25
  async run() {
14
26
  this.requireAuth();
15
27
  const { args, flags } = await this.parse(TasksLifecycleMigrate);
16
28
  const taskId = await this.resolveTaskReference(args.id);
29
+ // Moving a Task between published protocols is not obviously reversible,
30
+ // and the old output said only that a run had started. A flag rather than a
31
+ // prompt, so the command stays usable from a script.
32
+ if (flags['dry-run']) {
33
+ let current;
34
+ try {
35
+ current = await data_provider_1.dataService.getTaskLifecycle(taskId);
36
+ }
37
+ catch (error) {
38
+ this.handleApiError(error);
39
+ }
40
+ if (flags.json)
41
+ return this.log(JSON.stringify({ dryRun: true, ...current }, null, 2));
42
+ const lifecycle = current.lifecycle;
43
+ if (!lifecycle)
44
+ return this.log('No lifecycle is attached to this Task — nothing to migrate.');
45
+ this.log(`Would migrate from version ${lifecycle.versionId} to ${flags.version}, as generation ` +
46
+ `${Number(lifecycle.generation) + 1}.`);
47
+ this.log(` It re-enters at the CURRENT stage (${lifecycle.currentStageKey ?? '—'}) when that stage` +
48
+ " still exists on the target version; otherwise the protocol's entry stage decides.");
49
+ this.log(' The old generation is fenced — its run can no longer write.');
50
+ this.log(' Earlier generations stay readable in `tasks lifecycle history`.');
51
+ return;
52
+ }
17
53
  let result;
18
54
  try {
19
55
  result = await data_provider_1.dataService.migrateTaskLifecycle({ taskId, versionId: flags.version });
@@ -23,7 +59,10 @@ class TasksLifecycleMigrate extends base_command_1.BaseCommand {
23
59
  }
24
60
  if (flags.json)
25
61
  return this.log(JSON.stringify(result, null, 2));
26
- this.log(`Migrated lifecycle — run ${result.run.id} (${result.run.status}).`);
62
+ const lifecycle = result.lifecycle;
63
+ this.log(`Migrated to version ${lifecycle?.versionId ?? flags.version} — now generation ` +
64
+ `${lifecycle?.generation ?? '?'} at stage ${lifecycle?.currentStageKey ?? '—'}` +
65
+ ` (run ${result.run.id}, ${result.run.status}).`);
27
66
  }
28
67
  }
29
68
  exports.default = TasksLifecycleMigrate;
@@ -1,12 +1,14 @@
1
1
  import { BaseCommand } from '../../../base-command';
2
2
  export default class TasksLifecycleReopen extends BaseCommand {
3
3
  static description: string;
4
+ static examples: string[];
4
5
  static args: {
5
6
  id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
6
7
  };
7
8
  static flags: {
8
9
  stage: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
9
10
  reason: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
11
+ 'dry-run': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
12
  json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
11
13
  };
12
14
  run(): Promise<void>;
@@ -5,16 +5,50 @@ const data_provider_1 = require("@skrr-ai/data-provider");
5
5
  const base_command_1 = require("../../../base-command");
6
6
  class TasksLifecycleReopen extends base_command_1.BaseCommand {
7
7
  static description = 'Fence the current lifecycle generation and reopen at an earlier stage';
8
- static args = { id: core_1.Args.string({ required: true, ignoreStdin: true }) };
8
+ static examples = [
9
+ '<%= config.bin %> tasks lifecycle reopen OSK-1234 --dry-run',
10
+ '<%= config.bin %> tasks lifecycle reopen OSK-1234 --stage fix --reason "The fix did not take"',
11
+ ];
12
+ static args = {
13
+ id: core_1.Args.string({ required: true, ignoreStdin: true, description: 'Task ID or identifier' }),
14
+ };
9
15
  static flags = {
10
16
  stage: core_1.Flags.string({ description: 'Stage key to re-enter at; defaults to the entry stage' }),
11
- reason: core_1.Flags.string({ description: 'Why the lifecycle is reopening' }),
17
+ reason: core_1.Flags.string({ description: 'Why the lifecycle is reopening; recorded in history' }),
18
+ 'dry-run': core_1.Flags.boolean({
19
+ description: 'Describe what reopening would do and exit without writing',
20
+ }),
12
21
  json: core_1.Flags.boolean({ description: 'Output JSON' }),
13
22
  };
14
23
  async run() {
15
24
  this.requireAuth();
16
25
  const { args, flags } = await this.parse(TasksLifecycleReopen);
17
26
  const taskId = await this.resolveTaskReference(args.id);
27
+ // Reopen advances a generation and is not obviously reversible. A preview
28
+ // that names the target stage is cheaper than learning what it did by
29
+ // diffing state afterwards, and a flag keeps the command scriptable where
30
+ // an interactive prompt would not.
31
+ if (flags['dry-run']) {
32
+ let current;
33
+ try {
34
+ current = await data_provider_1.dataService.getTaskLifecycle(taskId);
35
+ }
36
+ catch (error) {
37
+ this.handleApiError(error);
38
+ }
39
+ if (flags.json)
40
+ return this.log(JSON.stringify({ dryRun: true, ...current }, null, 2));
41
+ const lifecycle = current.lifecycle;
42
+ if (!lifecycle)
43
+ return this.log('No lifecycle is attached to this Task — nothing to reopen.');
44
+ const entry = flags.stage || current.stages?.[0]?.key;
45
+ this.log(`Would reopen at stage ${entry ?? '(entry stage)'} as generation ${Number(lifecycle.generation) + 1}` +
46
+ ` (currently generation ${lifecycle.generation}, stage ${lifecycle.currentStageKey ?? '—'}).`);
47
+ this.log(' The current run is cancelled; its transitions stay readable in history.');
48
+ this.log(" A terminal Task is brought back to the entry stage's declared status.");
49
+ this.log(' Stages the earlier generation finished keep reading as done in that generation.');
50
+ return;
51
+ }
18
52
  let result;
19
53
  try {
20
54
  result = await data_provider_1.dataService.reopenTaskLifecycle({
@@ -28,7 +62,10 @@ class TasksLifecycleReopen extends base_command_1.BaseCommand {
28
62
  }
29
63
  if (flags.json)
30
64
  return this.log(JSON.stringify(result, null, 2));
31
- this.log(`Reopened lifecycle — run ${result.run.id} (${result.run.status}).`);
65
+ // Name where it landed, not just that it happened.
66
+ const lifecycle = result.lifecycle;
67
+ this.log(`Reopened at stage ${lifecycle?.currentStageKey ?? '—'} as generation ${lifecycle?.generation ?? '?'}` +
68
+ ` — run ${result.run.id} (${result.run.status}). The previous generation stays in history.`);
32
69
  }
33
70
  }
34
71
  exports.default = TasksLifecycleReopen;
@@ -42,6 +42,23 @@ class TasksLifecycleTransition extends base_command_1.BaseCommand {
42
42
  this.error('--payload must be a JSON object.', { exit: 2 });
43
43
  }
44
44
  }
45
+ /*
46
+ * The fence only protects the interval it is given.
47
+ *
48
+ * `--expected-revision` is the caller's claim about the state they DECIDED
49
+ * against. When it is omitted this command re-reads the lifecycle and sends
50
+ * back what it just read — and a compare-and-set whose expected value was
51
+ * fetched microseconds before the write can only catch that microsecond,
52
+ * never the interval that matters, which is between the human's read and
53
+ * their write. So the default is a convenience, not protection, and it was
54
+ * silent about that: a `confirm` decided at revision 0 applied against
55
+ * revision 1 and closed a Task, with nothing in the output saying the world
56
+ * had moved in between.
57
+ *
58
+ * Kept as the default because requiring a number on every call would be
59
+ * paid by everyone to protect a minority of flows — but it now says what it
60
+ * fenced on and how to fence properly.
61
+ */
45
62
  let expectedRevision = flags['expected-revision'];
46
63
  if (expectedRevision === undefined) {
47
64
  let current;
@@ -55,6 +72,10 @@ class TasksLifecycleTransition extends base_command_1.BaseCommand {
55
72
  this.error('No lifecycle is attached to this Task.', { exit: 2 });
56
73
  }
57
74
  expectedRevision = current.lifecycle.revision;
75
+ if (!flags.json) {
76
+ this.log(`Fencing on revision ${expectedRevision}, read just now — not the revision you saw. ` +
77
+ 'Pass --expected-revision <n> to fence on the state you decided against.');
78
+ }
58
79
  }
59
80
  const idempotencyKey = flags['idempotency-key'] ?? `cli-lifecycle:${taskId}:${args.transition}:r${expectedRevision}`;
60
81
  let response;
@@ -71,8 +92,18 @@ class TasksLifecycleTransition extends base_command_1.BaseCommand {
71
92
  this.handleApiError(error);
72
93
  }
73
94
  if (flags.json)
74
- return this.log(JSON.stringify(response, null, 2));
75
- (0, task_lifecycle_output_1.renderLifecycleTransitionResult)(response, (line) => this.log(line));
95
+ this.log(JSON.stringify(response, null, 2));
96
+ else
97
+ (0, task_lifecycle_output_1.renderLifecycleTransitionResult)(response, (line) => this.log(line));
98
+ // Exit non-zero when the write did not land. `applied` is the only verdict
99
+ // that means the stage moved; `rejected`, `superseded` and `gated` all mean
100
+ // it did not. Exiting 0 on all four made
101
+ // `transition A && transition B` run B after A had been fenced off — which
102
+ // is precisely the lost update `expectedRevision` exists to prevent, waved
103
+ // through by the shell.
104
+ if (response.status !== 'applied') {
105
+ this.exit(1);
106
+ }
76
107
  }
77
108
  }
78
109
  exports.default = TasksLifecycleTransition;
@@ -146,13 +146,13 @@ function buildListParams(flags, assigneeUserId) {
146
146
  ...(flag('actionability-reason')
147
147
  ? { actionabilityReason: flag('actionability-reason') }
148
148
  : {}),
149
- ...(flag('actionable-by') ||
150
- flag('actionable-for-agent') ||
151
- flag('needs-me') ||
152
- flag('actionability-state') ||
153
- flag('actionability-reason')
154
- ? { includeActionability: true }
155
- : {}),
149
+ // This table always renders a NEXT column, so it always has to ask for the
150
+ // data behind it. Requesting it only alongside an actionability FILTER left
151
+ // the default board printing a header with nothing under it — and a plain
152
+ // `tasks list` is exactly where a person looks to find out what anything is
153
+ // waiting for. The server computes it in one batched read, so the cost is
154
+ // a column that works rather than a column that is there.
155
+ includeActionability: true,
156
156
  ...(flag('priority') ? { priority: flag('priority') } : {}),
157
157
  ...(flag('estimate') !== undefined ? { estimate: flag('estimate') } : {}),
158
158
  ...(flag('min-estimate') !== undefined
@@ -427,8 +427,13 @@ class TasksList extends base_command_1.BaseCommand {
427
427
  priority: String(t.priority ?? '-'),
428
428
  estimate: t.estimate === null || t.estimate === undefined ? '-' : String(t.estimate),
429
429
  assignees,
430
+ // `reason` is the sentence the server wrote for a person; `reasonCode`
431
+ // is the enum it wrote for a machine. Printing the code rendered every
432
+ // stage of every lifecycle as `human:lifecycle_stage` — six different
433
+ // waits, one string — while the sentence naming the stage sat unread in
434
+ // the same payload.
430
435
  next: actionability
431
- ? `${String(actionability.nextActor ?? '-')}:${String(actionability.reasonCode ?? actionability.state ?? '-')}`
436
+ ? `${String(actionability.nextActor ?? '-')}: ${String(actionability.reason || actionability.reasonCode || actionability.state || '-')}`
432
437
  : String(t.executionMode ?? '-'),
433
438
  match: String(t._matchField ?? '-'),
434
439
  };
@@ -440,7 +445,9 @@ class TasksList extends base_command_1.BaseCommand {
440
445
  { key: 'priority', header: 'PRIORITY' },
441
446
  { key: 'estimate', header: 'ESTIMATE' },
442
447
  { key: 'assignees', header: 'ASSIGNEES' },
443
- { key: 'next', header: 'NEXT' },
448
+ // Bounded: the reason is a sentence, and an unbounded column wraps the
449
+ // table into unreadability on a narrow terminal.
450
+ { key: 'next', header: 'NEXT', maxWidth: 46 },
444
451
  // Only meaningful on a search — the server computes it from the same
445
452
  // relevance rank that orders the rows, and a plain listing has none.
446
453
  ...(flags.q ? [{ key: 'match', header: 'MATCH' }] : []),
@@ -5,6 +5,7 @@ const data_provider_1 = require("@skrr-ai/data-provider");
5
5
  const api_fetch_1 = require("../../lib/api-fetch");
6
6
  const session_task_endpoints_1 = require("../../lib/session-task-endpoints");
7
7
  const base_command_1 = require("../../base-command");
8
+ const task_closure_1 = require("../../lib/task-closure");
8
9
  const web_url_1 = require("../../lib/web-url");
9
10
  const task_resolver_1 = require("../../lib/task-resolver");
10
11
  const projects_1 = require("../../lib/projects");
@@ -155,19 +156,16 @@ class TasksMove extends base_command_1.BaseCommand {
155
156
  // the loosely-typed receipt, so the narrowing cast stays; what matters is
156
157
  // that the shape it names is the one data-provider owns.
157
158
  const resolutionClosure = t.resolution?.closure;
158
- if (resolutionClosure?.overrode?.includes('TASK_RESULT_REVIEW_REQUIRED')) {
159
- this.log(' Closed past the owner-declared required Review — the override is recorded (resolution.closure.overrode).');
160
- }
161
- if (resolutionClosure?.closable === false) {
162
- const parts = [];
163
- const unmet = resolutionClosure.expectations?.unmet?.length ?? 0;
164
- if (unmet > 0) {
165
- parts.push(`${unmet} Expectation${unmet === 1 ? '' : 's'} still unmet`);
166
- }
167
- parts.push(...(resolutionClosure.blockers || []).filter((code) => code !== 'TASK_EXPECTATIONS_NOT_MET' && code !== 'TASK_RESULT_REVIEW_REQUIRED'));
168
- if (parts.length) {
169
- this.log(` Closed over ${parts.join('; ')} (resolution.closure).`);
170
- }
159
+ /*
160
+ * Through the shared renderer. This hand-rolled the rendering and pushed
161
+ * blocker CODES at a human — `Closed over TASK_RESULT_REQUIRED`, which is a
162
+ * wire identifier and, read plainly, the opposite of what just happened. The
163
+ * worst instance was the one this batch cares most about: a Task closed over
164
+ * a reviewer's objection printed `TASK_RESULT_CHANGES_REQUESTED`, rendering
165
+ * "somebody looked and objected" as an identifier.
166
+ */
167
+ for (const sentence of (0, task_closure_1.closureSnapshotLines)(resolutionClosure)) {
168
+ this.log(` ${sentence}`);
171
169
  }
172
170
  }
173
171
  /**
@@ -23,7 +23,22 @@ export default class TasksReviewQueue extends BaseCommand {
23
23
  space: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
24
24
  actionable: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
25
25
  limit: import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
26
+ 'include-closed': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
26
27
  json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
27
28
  };
28
29
  run(): Promise<void>;
30
+ /**
31
+ * An empty queue must not be how you learn a Space id is wrong.
32
+ *
33
+ * A UUID-shaped ref is accepted without a lookup (`base-command` short-circuits
34
+ * on the SHAPE, which is a cheap and correct optimisation nearly everywhere),
35
+ * and the list route deliberately never 404s — an unknown Space falls back to
36
+ * the ACL enumeration and returns an honest empty page. Those two reasonable
37
+ * decisions compose into "Nothing is awaiting a judgement", exit 0, for a
38
+ * Space that does not exist. A typo read as done.
39
+ *
40
+ * So the existence check is paid for ONLY on the path that is about to make
41
+ * the completeness claim — never on the path that found work.
42
+ */
43
+ private assertSpaceExists;
29
44
  }