@skrr-ai/cli 0.1.23 → 0.1.25

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 (85) hide show
  1. package/bin/run.js +54 -1
  2. package/dist/base-command.d.ts +4 -4
  3. package/dist/base-command.js +81 -6
  4. package/dist/commands/harnesses/leases/list.js +1 -1
  5. package/dist/commands/harnesses/products.js +1 -0
  6. package/dist/commands/machines/dedicated/create.d.ts +18 -0
  7. package/dist/commands/machines/dedicated/create.js +90 -0
  8. package/dist/commands/machines/dedicated/destroy.d.ts +14 -0
  9. package/dist/commands/machines/dedicated/destroy.js +56 -0
  10. package/dist/commands/machines/dedicated/grow.d.ts +13 -0
  11. package/dist/commands/machines/dedicated/grow.js +46 -0
  12. package/dist/commands/machines/dedicated/health-check.d.ts +12 -0
  13. package/dist/commands/machines/dedicated/health-check.js +42 -0
  14. package/dist/commands/machines/dedicated/incident-close.d.ts +12 -0
  15. package/dist/commands/machines/dedicated/incident-close.js +42 -0
  16. package/dist/commands/machines/dedicated/index.d.ts +6 -0
  17. package/dist/commands/machines/dedicated/index.js +39 -0
  18. package/dist/commands/machines/dedicated/list.d.ts +11 -0
  19. package/dist/commands/machines/dedicated/list.js +58 -0
  20. package/dist/commands/machines/dedicated/restart.d.ts +12 -0
  21. package/dist/commands/machines/dedicated/restart.js +42 -0
  22. package/dist/commands/machines/dedicated/show.d.ts +12 -0
  23. package/dist/commands/machines/dedicated/show.js +58 -0
  24. package/dist/commands/machines/dedicated/snapshot.d.ts +12 -0
  25. package/dist/commands/machines/dedicated/snapshot.js +42 -0
  26. package/dist/commands/machines/dedicated/start.d.ts +12 -0
  27. package/dist/commands/machines/dedicated/start.js +42 -0
  28. package/dist/commands/machines/dedicated/stop.d.ts +12 -0
  29. package/dist/commands/machines/dedicated/stop.js +42 -0
  30. package/dist/commands/machines/hosted/index.js +4 -1
  31. package/dist/commands/spaces/auto-executor/preview.d.ts +8 -0
  32. package/dist/commands/spaces/auto-executor/preview.js +41 -0
  33. package/dist/commands/spaces/task-rollups.d.ts +14 -0
  34. package/dist/commands/spaces/task-rollups.js +66 -0
  35. package/dist/commands/spaces/update.js +16 -12
  36. package/dist/commands/spaces/work-sync/reconcile.d.ts +38 -0
  37. package/dist/commands/spaces/work-sync/reconcile.js +90 -0
  38. package/dist/commands/spaces/work-sync/telemetry.d.ts +29 -0
  39. package/dist/commands/spaces/work-sync/telemetry.js +118 -0
  40. package/dist/commands/tasks/activity.js +5 -1
  41. package/dist/commands/tasks/assign.js +1 -1
  42. package/dist/commands/tasks/complete.d.ts +5 -1
  43. package/dist/commands/tasks/complete.js +17 -3
  44. package/dist/commands/tasks/create.d.ts +1 -0
  45. package/dist/commands/tasks/create.js +17 -4
  46. package/dist/commands/tasks/deliverable/add.js +22 -4
  47. package/dist/commands/tasks/deliverable/list.js +23 -2
  48. package/dist/commands/tasks/events/append.js +31 -3
  49. package/dist/commands/tasks/expectations/assess.d.ts +1 -1
  50. package/dist/commands/tasks/expectations/assess.js +33 -7
  51. package/dist/commands/tasks/expectations.js +110 -10
  52. package/dist/commands/tasks/output.d.ts +0 -1
  53. package/dist/commands/tasks/output.js +21 -1
  54. package/dist/commands/tasks/report.js +3 -1
  55. package/dist/commands/tasks/result/show.js +20 -2
  56. package/dist/commands/tasks/resume/save.js +11 -1
  57. package/dist/commands/tasks/self-schedule.js +7 -4
  58. package/dist/commands/tasks/show.js +19 -0
  59. package/dist/commands/tasks/timeline.js +9 -1
  60. package/dist/commands/tasks/updates/add.js +23 -2
  61. package/dist/lib/api-fetch.js +9 -1
  62. package/dist/lib/assignee-resolver.d.ts +7 -6
  63. package/dist/lib/daemonBroker.d.ts +20 -0
  64. package/dist/lib/daemonBroker.js +58 -17
  65. package/dist/lib/dedicated-machines.d.ts +66 -0
  66. package/dist/lib/dedicated-machines.js +161 -0
  67. package/dist/lib/harnesses.d.ts +5 -0
  68. package/dist/lib/harnesses.js +5 -2
  69. package/dist/lib/idempotency-scope.d.ts +10 -0
  70. package/dist/lib/idempotency-scope.js +16 -0
  71. package/dist/lib/node-adapter.d.ts +1 -1
  72. package/dist/lib/node-adapter.js +12 -10
  73. package/dist/lib/task-legacy.d.ts +24 -0
  74. package/dist/lib/task-legacy.js +48 -0
  75. package/dist/lib/tasks.d.ts +32 -0
  76. package/dist/lib/tasks.js +47 -1
  77. package/dist/lib/triggers.js +7 -0
  78. package/dist/lib/work-sync.d.ts +105 -0
  79. package/dist/lib/work-sync.js +29 -0
  80. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/linux.js +71 -21
  81. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/linux.js +71 -21
  82. package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
  83. package/dist/node_modules/@skrr-ai/data-provider/index.js +2141 -2061
  84. package/oclif.manifest.json +15661 -14585
  85. package/package.json +1 -1
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const base_command_1 = require("../../../base-command");
5
+ const work_sync_1 = require("../../../lib/work-sync");
6
+ /**
7
+ * `skrr spaces work-sync reconcile <space>` — answer the divergence question
8
+ * without a browser, and repair what it finds.
9
+ *
10
+ * Root `CLAUDE.md` records that two Work Sync exit criteria were knowingly
11
+ * overridden, and that "an unexplained board divergence is a live hypothesis, not
12
+ * an impossibility, and is the first thing to suspect." `work-sync telemetry`
13
+ * reports whether the browser's convergence detector has ever RUN. This answers
14
+ * the underlying question directly: it compares every published projection entry
15
+ * against authoritative task truth server-side, and `upserts` is the count that
16
+ * disagreed.
17
+ *
18
+ * That matters because the detector only runs where a board is open. A Space
19
+ * nobody has visited since the detector shipped reports zero comparisons forever,
20
+ * which is indistinguishable from health in the telemetry and is not health. This
21
+ * command needs no board, no browser, and no visitor.
22
+ *
23
+ * IT WRITES, and the help says so plainly rather than burying it. The endpoint is
24
+ * editor-gated because it republishes projection rows that clients will see. It
25
+ * never mutates task truth — the projection is derived, and a republished card is
26
+ * a corrected copy of a Task nobody touched — and on a healthy scope it finds
27
+ * nothing and changes nothing, so the verification and the repair are the same
28
+ * call. There is deliberately no dry-run flag to add: a read-only variant would be
29
+ * a second implementation of the comparison, which is how the two answers start
30
+ * disagreeing.
31
+ */
32
+ class SpacesWorkSyncReconcile extends base_command_1.BaseCommand {
33
+ static description = 'Compare a Space Work Sync projection against task truth and repair what diverged (writes)';
34
+ static examples = [
35
+ '<%= config.bin %> spaces work-sync reconcile <space-id>',
36
+ '<%= config.bin %> spaces work-sync reconcile <space-id> --json',
37
+ ];
38
+ static args = {
39
+ space: core_1.Args.string({ description: 'Space ID', required: true, ignoreStdin: true }),
40
+ };
41
+ static flags = {
42
+ json: core_1.Flags.boolean({ description: 'Output as JSON' }),
43
+ };
44
+ async run() {
45
+ this.requireAuth();
46
+ const { args, flags } = await this.parse(SpacesWorkSyncReconcile);
47
+ let result;
48
+ try {
49
+ result = await (0, work_sync_1.reconcileWorkSyncScope)(args.space);
50
+ }
51
+ catch (err) {
52
+ this.handleApiError(err);
53
+ return;
54
+ }
55
+ if (flags.json) {
56
+ this.log(JSON.stringify(result, null, 2));
57
+ return;
58
+ }
59
+ const diverged = (result.upserts || 0) +
60
+ (result.removes || 0) +
61
+ (result.summaryUpserts || 0) +
62
+ (result.summaryRemoves || 0);
63
+ // Lead with the verdict, because it is the whole reason to run this. A table
64
+ // of four zeros under no summary line makes the reader do the addition.
65
+ if (diverged === 0) {
66
+ this.log('No divergence: every published projection entry matches task truth.');
67
+ }
68
+ else {
69
+ this.log(`Divergence found and repaired: ${diverged} ${diverged === 1 ? 'entry' : 'entries'}.`);
70
+ this.log('');
71
+ this.log(` stale task cards republished ${result.upserts || 0}`);
72
+ this.log(` task cards removed ${result.removes || 0}`);
73
+ this.log(` stale summaries republished ${result.summaryUpserts || 0}`);
74
+ this.log(` summaries removed ${result.summaryRemoves || 0}`);
75
+ }
76
+ if (result.backlog > 0) {
77
+ this.log('');
78
+ this.log(`${result.backlog} repair ${result.backlog === 1 ? 'row was' : 'rows were'} already queued ` +
79
+ 'by earlier publish failures. A successful run settles them; one that failed again is kept.');
80
+ }
81
+ if (result.truncated) {
82
+ this.log('');
83
+ // Never let a partial pass read as a clean one. The scope read is capped at
84
+ // 1000 rows, so a larger Space was only checked as far as the cap.
85
+ this.log('PARTIAL: this Space is larger than the 1000-row reconciliation cap, so the ' +
86
+ 'verdict above covers a prefix of it and not the whole Space.');
87
+ }
88
+ }
89
+ }
90
+ exports.default = SpacesWorkSyncReconcile;
@@ -0,0 +1,29 @@
1
+ import { BaseCommand } from '../../../base-command';
2
+ /**
3
+ * `skrr spaces work-sync telemetry <space>` — Work Sync's own exit criteria.
4
+ *
5
+ * Work Sync v1 is ON at 100% (owner decision, 2026-09-08) with two criteria
6
+ * knowingly overridden rather than satisfied. The server has computed the verdict
7
+ * the whole time, and nothing outside the API could read it — so checking the
8
+ * criteria meant querying the production database by hand, which is why nobody
9
+ * did. An exit criterion that can only be read by hand is one that does not get
10
+ * read.
11
+ *
12
+ * The verdict is printed as the server decided it. This command does not
13
+ * re-derive it: the same rule that keeps `tasks expectations` reading the server's
14
+ * judgement rather than computing its own applies here, and for the same reason —
15
+ * a CLI that reached a different conclusion from the service would leave the
16
+ * reader unable to tell which was lying.
17
+ */
18
+ export default class SpacesWorkSyncTelemetry extends BaseCommand {
19
+ static description: string;
20
+ static examples: string[];
21
+ static args: {
22
+ space: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
23
+ };
24
+ static flags: {
25
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
26
+ limit: import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
27
+ };
28
+ run(): Promise<void>;
29
+ }
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const base_command_1 = require("../../../base-command");
5
+ const work_sync_1 = require("../../../lib/work-sync");
6
+ /**
7
+ * `skrr spaces work-sync telemetry <space>` — Work Sync's own exit criteria.
8
+ *
9
+ * Work Sync v1 is ON at 100% (owner decision, 2026-09-08) with two criteria
10
+ * knowingly overridden rather than satisfied. The server has computed the verdict
11
+ * the whole time, and nothing outside the API could read it — so checking the
12
+ * criteria meant querying the production database by hand, which is why nobody
13
+ * did. An exit criterion that can only be read by hand is one that does not get
14
+ * read.
15
+ *
16
+ * The verdict is printed as the server decided it. This command does not
17
+ * re-derive it: the same rule that keeps `tasks expectations` reading the server's
18
+ * judgement rather than computing its own applies here, and for the same reason —
19
+ * a CLI that reached a different conclusion from the service would leave the
20
+ * reader unable to tell which was lying.
21
+ */
22
+ class SpacesWorkSyncTelemetry extends base_command_1.BaseCommand {
23
+ static description = "Show Work Sync's convergence telemetry for a Space and the server's rollout verdict";
24
+ static examples = [
25
+ '<%= config.bin %> spaces work-sync telemetry <space-id>',
26
+ '<%= config.bin %> spaces work-sync telemetry <space-id> --json',
27
+ ];
28
+ static args = {
29
+ space: core_1.Args.string({ description: 'Space ID', required: true, ignoreStdin: true }),
30
+ };
31
+ static flags = {
32
+ json: core_1.Flags.boolean({ description: 'Output as JSON' }),
33
+ limit: core_1.Flags.integer({
34
+ description: 'Most recent telemetry rows to summarize (1-10000, default 5000)',
35
+ }),
36
+ };
37
+ async run() {
38
+ this.requireAuth();
39
+ const { args, flags } = await this.parse(SpacesWorkSyncTelemetry);
40
+ let summary;
41
+ try {
42
+ summary = await (0, work_sync_1.fetchWorkSyncTelemetrySummary)(args.space, { limit: flags.limit });
43
+ }
44
+ catch (err) {
45
+ this.handleApiError(err);
46
+ return;
47
+ }
48
+ if (flags.json) {
49
+ this.log(JSON.stringify(summary, null, 2));
50
+ return;
51
+ }
52
+ this.log(`Verdict: ${summary.rollout.verdict}`);
53
+ this.log(`Sampled rows: ${summary.sampledEvents}`);
54
+ this.log(`Repair backlog: ${summary.repairBacklog}`);
55
+ if (summary.coverage && summary.coverage.hits + summary.coverage.misses > 0) {
56
+ const { hits, misses, hitRate } = summary.coverage;
57
+ this.log(`Coverage: ${hits} hits · ${misses} misses` +
58
+ (hitRate === null ? '' : ` (${Math.round(hitRate * 1000) / 10}% of cold opens)`));
59
+ }
60
+ // Before the zero-events early return: writes are measured from the command
61
+ // queue, not from board telemetry, so a Space can have one without the other.
62
+ const commandRows = Object.entries(summary.commands || {}).sort(([a], [b]) => a.localeCompare(b));
63
+ if (commandRows.length > 0) {
64
+ const percent = (value) => value === null ? 'n/a' : `${Math.round(value * 1000) / 10}%`;
65
+ this.log('');
66
+ this.log('Writes (durable command queue):');
67
+ for (const [kind, stats] of commandRows) {
68
+ this.log(` ${kind.padEnd(20)} ${stats.commands} commands · ${stats.accepted} accepted · ` +
69
+ `${stats.pending} pending · ${stats.deadLettered} refused`);
70
+ this.log(` ${''.padEnd(20)} version conflicts ${stats.versionConflicts} ` +
71
+ `(${percent(stats.rates.versionConflict)} of settled) · authorization changed ` +
72
+ `${stats.authorizationChanged} (${percent(stats.rates.authorizationChanged)}) · ` +
73
+ `duplicate deliveries ${stats.duplicateDeliveries} ` +
74
+ `(${percent(stats.rates.duplicateDelivery)} of deliveries)`);
75
+ }
76
+ }
77
+ // Zero sampled rows is a REAL answer and the most misread one: it means
78
+ // nothing has been measured in this Space, not that everything is fine. The
79
+ // server already distinguishes it (`insufficient_evidence`); say so here too
80
+ // rather than printing an empty table under a clean-looking verdict.
81
+ if (summary.sampledEvents === 0) {
82
+ this.log('');
83
+ this.log('No telemetry has been recorded for this Space. That is not evidence of health — ' +
84
+ 'it means no board in this Space has reported, so nothing has been measured.');
85
+ return;
86
+ }
87
+ const rows = Object.entries(summary.events).sort(([a], [b]) => a.localeCompare(b));
88
+ if (rows.length > 0) {
89
+ this.log('');
90
+ this.log('Events:');
91
+ for (const [event, stats] of rows) {
92
+ const parts = [`count ${stats.count}`];
93
+ if (stats.mismatchCount > 0)
94
+ parts.push(`mismatches ${stats.mismatchCount}`);
95
+ if (stats.p50DurationMs !== null) {
96
+ parts.push(`p50 ${stats.p50DurationMs}ms`, `p95 ${stats.p95DurationMs}ms`);
97
+ }
98
+ if (stats.p50Bytes !== null) {
99
+ parts.push(`p50 ${stats.p50Bytes}B`, `p95 ${stats.p95Bytes}B`);
100
+ }
101
+ this.log(` ${event.padEnd(20)} ${parts.join(' · ')}`);
102
+ }
103
+ }
104
+ if (summary.rollout.reasons.length > 0) {
105
+ this.log('');
106
+ this.log('Holding because:');
107
+ for (const reason of summary.rollout.reasons) {
108
+ // An unexplained code is the thing this command exists to avoid: the
109
+ // reader is being told a rollout is held and needs to know by what.
110
+ this.log(` ${reason}`);
111
+ const explanation = work_sync_1.WORK_SYNC_HOLD_REASONS[reason];
112
+ if (explanation)
113
+ this.log(` ${explanation}`);
114
+ }
115
+ }
116
+ }
117
+ }
118
+ exports.default = SpacesWorkSyncTelemetry;
@@ -6,6 +6,7 @@ const core_1 = require("@oclif/core");
6
6
  const data_provider_1 = require("@skrr-ai/data-provider");
7
7
  const base_command_1 = require("../../base-command");
8
8
  const format_1 = require("../../lib/format");
9
+ const task_legacy_1 = require("../../lib/task-legacy");
9
10
  function activityRow(entry) {
10
11
  const actor = entry.actor;
11
12
  const actorText = actor
@@ -110,7 +111,10 @@ class TasksActivity extends base_command_1.BaseCommand {
110
111
  const r = response;
111
112
  const items = r?.data ?? [];
112
113
  if (items.length === 0) {
113
- this.log('No activity.');
114
+ // "No activity." was a claim about the TASK. It is only ever a claim about
115
+ // this VIEW: a Task predating the canonical model has its history in the
116
+ // legacy activity log, which this endpoint does not read.
117
+ this.log(await (0, task_legacy_1.emptyCanonicalView)(args.id, 'Activity'));
114
118
  return;
115
119
  }
116
120
  (0, format_1.renderTable)(items.map(activityRow), [
@@ -74,7 +74,7 @@ class TasksAssign extends base_command_1.BaseCommand {
74
74
  options: ['agent', 'human'],
75
75
  }),
76
76
  list: core_1.Flags.boolean({
77
- description: "List the task's space members (who can be assigned) and exit",
77
+ description: "List the task's space members and exit (your own unshared agents can also be assigned, by id)",
78
78
  }),
79
79
  json: core_1.Flags.boolean({ description: 'Output as JSON' }),
80
80
  };
@@ -1,9 +1,13 @@
1
1
  import { BaseCommand } from '../../base-command';
2
- export declare function describePendingReview({ statusType, taskId, resultId, bin, }: {
2
+ export declare function describePendingReview({ statusType, taskId, resultId, bin, advisories, }: {
3
3
  statusType: unknown;
4
4
  taskId: string;
5
5
  resultId?: string;
6
6
  bin: string;
7
+ advisories?: Array<{
8
+ code: string;
9
+ message: string;
10
+ }>;
7
11
  }): string | null;
8
12
  export default class TasksComplete extends BaseCommand {
9
13
  static description: string;
@@ -43,11 +43,24 @@ function optionalString(value) {
43
43
  function stringArray(value) {
44
44
  return Array.isArray(value) ? value.map((item) => String(item).trim()).filter(Boolean) : [];
45
45
  }
46
- function describePendingReview({ statusType, taskId, resultId, bin, }) {
46
+ function describePendingReview({ statusType, taskId, resultId, bin, advisories, }) {
47
47
  if (statusType !== 'review')
48
48
  return null;
49
- return ('The Result was submitted, but the Task is not done yet. An independent reviewer must ' +
50
- `accept this exact Result with: ${bin} tasks review ${taskId} --result-id ${resultId || '<result-id>'} --decision accepted`);
49
+ // Say which thing is actually holding the Task. This used to assert a missing
50
+ // reviewer unconditionally, so a Task held by unmet Expectations was reported
51
+ // as needing review — and the command it handed over failed every time, both
52
+ // because the block was elsewhere and because a submitter may not accept their
53
+ // own Result (`TASK_REVIEW_SELF_ACCEPTANCE`).
54
+ const expectationAdvisory = (advisories || []).find((row) => row.code === 'task_acceptance_not_met' || /expectation/i.test(row.code || ''));
55
+ if (expectationAdvisory) {
56
+ return ('The Result was submitted, but the Task is not done yet: its Expectations are not all ' +
57
+ `settled. ${expectationAdvisory.message} ` +
58
+ `Settle each with: ${bin} tasks expectations assess ${taskId} <expectation-id> ` +
59
+ '--decision met|waived --reason "..."');
60
+ }
61
+ return ('The Result was submitted, but the Task is not done yet. It is waiting on an independent ' +
62
+ `accepted Review — someone OTHER than the submitter runs: ${bin} tasks review ${taskId} ` +
63
+ `--result-id ${resultId || '<result-id>'} --decision accepted`);
51
64
  }
52
65
  class TasksComplete extends base_command_1.BaseCommand {
53
66
  static description = 'Submit one versioned Result and advance the Task workflow';
@@ -247,6 +260,7 @@ class TasksComplete extends base_command_1.BaseCommand {
247
260
  taskId,
248
261
  resultId: result.result.id,
249
262
  bin: this.config.bin,
263
+ advisories: result.advisories,
250
264
  });
251
265
  if (pending)
252
266
  this.log(pending);
@@ -58,6 +58,7 @@ export default class TasksCreate extends BaseCommand {
58
58
  'dri-agent': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
59
59
  'dri-user': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
60
60
  expect: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
61
+ 'review-policy': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
61
62
  'execution-mode': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
62
63
  'start-date': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
63
64
  'due-date': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
@@ -192,6 +192,13 @@ class TasksCreate extends base_command_1.BaseCommand {
192
192
  " Injected into the agent's instructions and checked against what the run produced. " +
193
193
  'Omit to inherit the space default.',
194
194
  }),
195
+ 'review-policy': core_1.Flags.string({
196
+ options: ['none', 'required'],
197
+ description: 'Whether closing this Task needs someone other than the Result author to accept it. ' +
198
+ "Default 'none': the Expectations you declare are already your definition of done. " +
199
+ "Use 'required' when the work warrants a second pair of eyes — it applies to every " +
200
+ 'executor, human or agent.',
201
+ }),
195
202
  'execution-mode': core_1.Flags.string({
196
203
  description: 'Who executes the task: agent or human. Required when both Humans and Agents are ' +
197
204
  'assigned. Passing "human" with no assignee flags files the task against you, ' +
@@ -453,8 +460,9 @@ class TasksCreate extends base_command_1.BaseCommand {
453
460
  defaultAgentResolution = {
454
461
  applied: false,
455
462
  source: 'none',
456
- note: 'Created unassigned: the agent bound to this session is not a member of that space. ' +
457
- 'Add it to the space, or pass an assignee explicitly.',
463
+ note: 'Created unassigned: the agent bound to this session is not a member of that space, ' +
464
+ 'and it is not your own agent shared with no one else, which is the only other way ' +
465
+ 'an agent can work there. Add it to the space, or pass an assignee explicitly.',
458
466
  };
459
467
  try {
460
468
  const result = await this.createTaskOrRecover(payload);
@@ -565,9 +573,14 @@ class TasksCreate extends base_command_1.BaseCommand {
565
573
  const dropped = (0, tasks_1.droppedDefaultAssignees)(resolution, memberIds);
566
574
  const warnings = [];
567
575
  if (dropped.agentIds.length > 0) {
576
+ // Membership is one of TWO consents the server accepts (AgentTaskConsent);
577
+ // the other — your own agent, shared with no one else — needs the agent's
578
+ // sharing, which a dry run does not read. So this says what decides it
579
+ // rather than predicting a drop that owner delegation would not make.
568
580
  warnings.push(`The defaulted assignee agent ${dropped.agentIds.join(', ')} is not a member of ` +
569
- `space ${spaceId}, so the real create will drop it and record the task ` +
570
- 'unassigned. Add the agent to the space, or pass an assignee explicitly.');
581
+ `space ${spaceId}. The real create keeps it only if it is your own agent shared ` +
582
+ 'with no one else; otherwise it records the task unassigned. Add the agent to the ' +
583
+ 'space, or pass an assignee explicitly.');
571
584
  }
572
585
  if (dropped.userIds.length > 0) {
573
586
  warnings.push(`The defaulted assignee ${dropped.userIds.join(', ')} is not a member of space ` +
@@ -17,7 +17,20 @@ const tasks_1 = require("../../../lib/tasks");
17
17
  * optional structured one.
18
18
  */
19
19
  class TasksDeliverableAdd extends base_command_1.BaseCommand {
20
- static description = 'Record something this task produced (a commit, a page, a URL, a file, a message, a dataset)';
20
+ static description = 'Record something this task produced (a commit, a page, a URL, a file, a message, a dataset).\n' +
21
+ '\n' +
22
+ 'IDENTITY is the part before @; LOCATION is the part after it. They are not the same ' +
23
+ 'grammar as `--expect`, where the same-looking `code:origin/main` names a DESTINATION ' +
24
+ 'rather than a thing that exists. Here `origin/main` is where the commit landed, and ' +
25
+ 'the commit SHA is what is identified.\n' +
26
+ '\n' +
27
+ 'Two Deliverables share one Resource when their identity matches, scoped to the ' +
28
+ 'workspace. A bare relative path has no global meaning, so `docs/report.md` from two ' +
29
+ 'different repositories in one workspace IS the same Resource — pass --provider and a ' +
30
+ "provider id when you mean a specific system's object, which moves the identity into " +
31
+ "that provider's namespace. Without --version or a content digest the version is " +
32
+ '`locator_only`: nothing pins which revision it is, and a later sighting of the same ' +
33
+ 'coordinate may be different bytes.';
21
34
  static examples = [
22
35
  '<%= config.bin %> tasks deliverable add <task-id> code:a1b2c3d@origin/main',
23
36
  '<%= config.bin %> tasks deliverable add <task-id> page:d_92@Engineering/deploy',
@@ -27,8 +40,9 @@ class TasksDeliverableAdd extends base_command_1.BaseCommand {
27
40
  static args = {
28
41
  task: core_1.Args.string({ description: 'Task id or ref', required: true, ignoreStdin: true }),
29
42
  coordinate: core_1.Args.string({
30
- description: '<kind>[/<format>]:<identity>[@<location>] — e.g. code:a1b2c3d@origin/main, ' +
31
- 'file/pdf:report_92@Drive/Reports',
43
+ description: '<kind>[/<format>]:<identity>[@<location>] — identity before @, location after. ' +
44
+ 'e.g. code:a1b2c3d@origin/main, file/pdf:report_92@Drive/Reports. ' +
45
+ 'Kinds: code, file, page, url, message, dataset',
32
46
  required: true,
33
47
  ignoreStdin: true,
34
48
  }),
@@ -44,7 +58,11 @@ class TasksDeliverableAdd extends base_command_1.BaseCommand {
44
58
  hidden: true,
45
59
  }),
46
60
  title: core_1.Flags.string({ description: 'Human-readable Deliverable name' }),
47
- provider: core_1.Flags.string({ description: 'Provider, such as github or drive' }),
61
+ provider: core_1.Flags.string({
62
+ description: 'Provider, such as github or drive. This CHANGES the Resource identity: it moves the ' +
63
+ "key from the locator namespace into the provider's, so the same coordinate with and " +
64
+ 'without --provider are two different Resources',
65
+ }),
48
66
  version: core_1.Flags.string({ description: 'Exact provider version, such as a full commit SHA' }),
49
67
  'content-digest': core_1.Flags.string({ description: 'Integrity digest for this exact version' }),
50
68
  expectation: core_1.Flags.string({ multiple: true, description: 'Expectation id this satisfies' }),
@@ -4,6 +4,25 @@ const core_1 = require("@oclif/core");
4
4
  const data_provider_1 = require("@skrr-ai/data-provider");
5
5
  const base_command_1 = require("../../../base-command");
6
6
  const task_resolver_1 = require("../../../lib/task-resolver");
7
+ /**
8
+ * `locator_only` is the storage word, and it appeared on nearly every row with
9
+ * nothing saying what it meant. It is not a defect — it is the honest statement
10
+ * that nothing pins WHICH revision this is — so the label says that.
11
+ */
12
+ function fidelityLabel(value) {
13
+ switch (value) {
14
+ case 'managed_copy':
15
+ return 'exact version archived';
16
+ case 'content_digest':
17
+ return 'exact version (content verified)';
18
+ case 'provider_version':
19
+ return 'exact version (provider revision)';
20
+ case 'locator_only':
21
+ return 'version not pinned — the coordinate may change under you';
22
+ default:
23
+ return value || 'unknown fidelity';
24
+ }
25
+ }
7
26
  class TasksDeliverableList extends base_command_1.BaseCommand {
8
27
  static description = 'List Deliverables with exact version, location, and evidence state';
9
28
  static args = {
@@ -29,8 +48,10 @@ class TasksDeliverableList extends base_command_1.BaseCommand {
29
48
  if (!result.data.length)
30
49
  return this.log('No Deliverables recorded.');
31
50
  for (const item of result.data) {
32
- this.log(`${item.linkId} · ${item.title || item.identity || item.kind}\n` +
33
- ` ${item.location || 'no locator'} · ${item.versionFidelity} · ${item.status} · ${item.evidenceLevel}`);
51
+ this.log(`${item.linkId} · ${item.kind}${item.format ? `/${item.format}` : ''} · ` +
52
+ `${item.title || item.identity || '(untitled)'}\n` +
53
+ ` ${item.location || 'no locator'} · ${fidelityLabel(item.versionFidelity)} · ` +
54
+ `${item.status} · ${item.evidenceLevel}`);
34
55
  }
35
56
  }
36
57
  }
@@ -10,8 +10,16 @@ const task_artifact_flags_1 = require("../../../lib/task-artifact-flags");
10
10
  const tasks_1 = require("../../../lib/tasks");
11
11
  class TasksEventsAppend extends base_command_1.BaseCommand {
12
12
  static hidden = true;
13
- static summary = 'Append a typed, durable task event';
14
- static description = 'Compatibility adapter for historical Event vocabulary; use `tasks updates add`.';
13
+ // The SUMMARY is what `tasks --help` and the first line of `--help` show, and
14
+ // it is the only line most readers see. Saying only "Append a typed, durable
15
+ // task event" there while the deprecation lived in the long DESCRIPTION meant
16
+ // the retired vocabulary read as the intended path — which is how it kept
17
+ // getting exercised.
18
+ static summary = 'DEPRECATED compatibility spelling for `tasks updates add` — append a legacy task event';
19
+ static description = 'DEPRECATED compatibility adapter for the historical Event vocabulary. Every kind is ' +
20
+ 'stored as a canonical Update, so `--kind handoff` and `--kind checkpoint` both become ' +
21
+ '`progress`, and `--kind started|lifecycle` writes only to the Activity ledger and will ' +
22
+ 'NOT appear in `tasks updates list`. Use `tasks updates add` instead.';
15
23
  static examples = [
16
24
  '<%= config.bin %> tasks events append <task-id> --kind checkpoint --idempotency-key "<task-id>:first-pass" --summary "Parser handles nested fences"',
17
25
  '<%= 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"',
@@ -91,7 +99,27 @@ class TasksEventsAppend extends base_command_1.BaseCommand {
91
99
  }
92
100
  if (path && result && typeof result === 'object' && 'entry' in result)
93
101
  result = result.entry;
94
- this.log(flags.json ? JSON.stringify(result, null, 2) : `Appended event to task ${task.id}.`);
102
+ if (flags.json)
103
+ return this.log(JSON.stringify(result, null, 2));
104
+ // The old receipt named neither what was written nor where to find it, so a
105
+ // caller could not tell a successful compatibility write from a no-op — and
106
+ // this command silently becomes a canonical Update of a DIFFERENT type.
107
+ const written = result || {};
108
+ if (written.canonicalType) {
109
+ this.log(`Recorded on ${task.id} as a ${written.canonicalType} Update` +
110
+ `${written.id ? ` (${written.id})` : ''}.`);
111
+ this.log(`\`tasks events append\` is a compatibility spelling; \`${this.config.bin} tasks updates add\` ` +
112
+ `is the canonical command, and \`${this.config.bin} tasks updates list\` reads them back.`);
113
+ return;
114
+ }
115
+ // `--kind started|lifecycle` produces no Update at all: it writes a
116
+ // `task.lifecycle.changed` row to the Activity ledger. The old receipt was
117
+ // byte-identical to the Update case, so a caller was told "appended" and
118
+ // then could not find it in the surface the deprecation notice points at.
119
+ this.log(`Recorded on ${task.id} in the Activity ledger` +
120
+ `${written.id ? ` (${written.id})` : ''} — NOT as a Task Update.`);
121
+ this.log(`Read it with \`${this.config.bin} tasks activity ${task.id}\`; it will not appear in ` +
122
+ `\`${this.config.bin} tasks updates list\`.`);
95
123
  }
96
124
  }
97
125
  exports.default = TasksEventsAppend;
@@ -3,7 +3,7 @@ export default class TasksExpectationsAssess extends BaseCommand {
3
3
  static description: string;
4
4
  static args: {
5
5
  task: import("@oclif/core/lib/interfaces").Arg<string | undefined, Record<string, unknown>>;
6
- expectation: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
6
+ expectation: import("@oclif/core/lib/interfaces").Arg<string | undefined, Record<string, unknown>>;
7
7
  };
8
8
  static flags: {
9
9
  task: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
@@ -9,9 +9,24 @@ const api_fetch_1 = require("../../../lib/api-fetch");
9
9
  const session_task_endpoints_1 = require("../../../lib/session-task-endpoints");
10
10
  class TasksExpectationsAssess extends base_command_1.BaseCommand {
11
11
  static description = 'Assess one current Task Expectation with evidence and a reason';
12
+ // Both positionals are declared optional because oclif REJECTS an args table
13
+ // whose optional entry precedes a required one — it throws `Invalid argument
14
+ // spec` at parse time, before any network call, which made this command
15
+ // impossible to invoke in any form. Since a declared Expectation hard-gates
16
+ // closure (`TaskClosurePolicy.assertTaskMayClose`) and re-assessment is the
17
+ // only way back, that parse failure was a permanent close-deadlock for every
18
+ // Task carrying one. The two supported shapes are disambiguated in `run()`.
12
19
  static args = {
13
- task: core_1.Args.string({ required: false, ignoreStdin: true, description: 'Task id or ref' }),
14
- expectation: core_1.Args.string({ required: true, ignoreStdin: true, description: 'Expectation id' }),
20
+ task: core_1.Args.string({
21
+ required: false,
22
+ ignoreStdin: true,
23
+ description: 'Task id or ref; omit inside a task runtime',
24
+ }),
25
+ expectation: core_1.Args.string({
26
+ required: false,
27
+ ignoreStdin: true,
28
+ description: 'Expectation id',
29
+ }),
15
30
  };
16
31
  static flags = {
17
32
  task: core_1.Flags.string({ description: 'Task id; overrides runtime context' }),
@@ -31,7 +46,18 @@ class TasksExpectationsAssess extends base_command_1.BaseCommand {
31
46
  async run() {
32
47
  this.requireAuth();
33
48
  const { args, flags } = await this.parse(TasksExpectationsAssess);
34
- const task = (0, task_resolver_1.resolveTaskIdentity)({ positionalId: args.task, flagTask: flags.task });
49
+ // `assess <task> <expectation>` gives both; `assess <expectation>` inside a
50
+ // task runtime gives one, and it is the Expectation.
51
+ const expectationId = args.expectation ?? args.task;
52
+ if (!expectationId) {
53
+ this.error('An Expectation id is required: `tasks expectations assess <task> <expectation>`', {
54
+ exit: 1,
55
+ });
56
+ }
57
+ const task = (0, task_resolver_1.resolveTaskIdentity)({
58
+ positionalId: args.expectation ? args.task : undefined,
59
+ flagTask: flags.task,
60
+ });
35
61
  const evidenceRefs = (flags.evidence || []).map((raw) => {
36
62
  const split = raw.indexOf(':');
37
63
  if (split < 1)
@@ -53,16 +79,16 @@ class TasksExpectationsAssess extends base_command_1.BaseCommand {
53
79
  try {
54
80
  const data = {
55
81
  ...payload,
56
- expectationId: args.expectation,
82
+ expectationId,
57
83
  idempotencyKey: flags['idempotency-key'] ||
58
- (0, outbox_1.idempotencyKeyFor)('task-expectation-assessment', task.id, JSON.stringify({ expectationId: args.expectation, ...payload })),
84
+ (0, outbox_1.idempotencyKeyFor)('task-expectation-assessment', task.id, JSON.stringify({ expectationId, ...payload })),
59
85
  };
60
86
  const sessionPath = await (0, session_task_endpoints_1.sessionScopedTaskPath)('assessment');
61
87
  result = sessionPath
62
88
  ? await (0, api_fetch_1.apiFetch)(sessionPath, { method: 'POST', body: data })
63
89
  : await data_provider_1.dataService.assessTaskExpectation({
64
90
  taskId: task.id,
65
- expectationId: args.expectation,
91
+ expectationId,
66
92
  data,
67
93
  });
68
94
  }
@@ -71,7 +97,7 @@ class TasksExpectationsAssess extends base_command_1.BaseCommand {
71
97
  }
72
98
  if (flags.json)
73
99
  return this.log(JSON.stringify(result, null, 2));
74
- this.log(`Recorded ${args.expectation} as ${flags.decision}.`);
100
+ this.log(`Recorded ${expectationId} as ${flags.decision}.`);
75
101
  }
76
102
  }
77
103
  exports.default = TasksExpectationsAssess;