@skrr-ai/cli 0.1.23 → 0.1.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/run.js +54 -1
- package/dist/base-command.js +74 -1
- package/dist/commands/harnesses/leases/list.js +1 -1
- package/dist/commands/harnesses/products.js +1 -0
- package/dist/commands/machines/dedicated/create.d.ts +18 -0
- package/dist/commands/machines/dedicated/create.js +90 -0
- package/dist/commands/machines/dedicated/destroy.d.ts +14 -0
- package/dist/commands/machines/dedicated/destroy.js +56 -0
- package/dist/commands/machines/dedicated/grow.d.ts +13 -0
- package/dist/commands/machines/dedicated/grow.js +46 -0
- package/dist/commands/machines/dedicated/health-check.d.ts +12 -0
- package/dist/commands/machines/dedicated/health-check.js +42 -0
- package/dist/commands/machines/dedicated/incident-close.d.ts +12 -0
- package/dist/commands/machines/dedicated/incident-close.js +42 -0
- package/dist/commands/machines/dedicated/index.d.ts +6 -0
- package/dist/commands/machines/dedicated/index.js +39 -0
- package/dist/commands/machines/dedicated/list.d.ts +11 -0
- package/dist/commands/machines/dedicated/list.js +58 -0
- package/dist/commands/machines/dedicated/restart.d.ts +12 -0
- package/dist/commands/machines/dedicated/restart.js +42 -0
- package/dist/commands/machines/dedicated/show.d.ts +12 -0
- package/dist/commands/machines/dedicated/show.js +58 -0
- package/dist/commands/machines/dedicated/snapshot.d.ts +12 -0
- package/dist/commands/machines/dedicated/snapshot.js +42 -0
- package/dist/commands/machines/dedicated/start.d.ts +12 -0
- package/dist/commands/machines/dedicated/start.js +42 -0
- package/dist/commands/machines/dedicated/stop.d.ts +12 -0
- package/dist/commands/machines/dedicated/stop.js +42 -0
- package/dist/commands/machines/hosted/index.js +4 -1
- package/dist/commands/spaces/update.js +16 -12
- package/dist/commands/spaces/work-sync/reconcile.d.ts +38 -0
- package/dist/commands/spaces/work-sync/reconcile.js +90 -0
- package/dist/commands/spaces/work-sync/telemetry.d.ts +29 -0
- package/dist/commands/spaces/work-sync/telemetry.js +96 -0
- package/dist/commands/tasks/activity.js +5 -1
- package/dist/commands/tasks/complete.d.ts +5 -1
- package/dist/commands/tasks/complete.js +17 -3
- package/dist/commands/tasks/create.d.ts +1 -0
- package/dist/commands/tasks/create.js +7 -0
- package/dist/commands/tasks/deliverable/add.js +22 -4
- package/dist/commands/tasks/deliverable/list.js +23 -2
- package/dist/commands/tasks/events/append.js +31 -3
- package/dist/commands/tasks/expectations/assess.d.ts +1 -1
- package/dist/commands/tasks/expectations/assess.js +33 -7
- package/dist/commands/tasks/expectations.js +110 -10
- package/dist/commands/tasks/output.d.ts +0 -1
- package/dist/commands/tasks/output.js +21 -1
- package/dist/commands/tasks/report.js +3 -1
- package/dist/commands/tasks/result/show.js +20 -2
- package/dist/commands/tasks/resume/save.js +11 -1
- package/dist/commands/tasks/self-schedule.js +7 -4
- package/dist/commands/tasks/show.js +19 -0
- package/dist/commands/tasks/timeline.js +9 -1
- package/dist/commands/tasks/updates/add.js +23 -2
- package/dist/lib/dedicated-machines.d.ts +66 -0
- package/dist/lib/dedicated-machines.js +161 -0
- package/dist/lib/harnesses.d.ts +5 -0
- package/dist/lib/harnesses.js +5 -2
- package/dist/lib/task-legacy.d.ts +24 -0
- package/dist/lib/task-legacy.js +48 -0
- package/dist/lib/tasks.d.ts +32 -0
- package/dist/lib/tasks.js +47 -1
- package/dist/lib/work-sync.d.ts +70 -0
- package/dist/lib/work-sync.js +29 -0
- package/dist/node_modules/@skrr-ai/data-provider/index.js +2079 -2055
- package/oclif.manifest.json +12229 -11213
- package/package.json +1 -1
|
@@ -0,0 +1,96 @@
|
|
|
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
|
+
// Zero sampled rows is a REAL answer and the most misread one: it means
|
|
56
|
+
// nothing has been measured in this Space, not that everything is fine. The
|
|
57
|
+
// server already distinguishes it (`insufficient_evidence`); say so here too
|
|
58
|
+
// rather than printing an empty table under a clean-looking verdict.
|
|
59
|
+
if (summary.sampledEvents === 0) {
|
|
60
|
+
this.log('');
|
|
61
|
+
this.log('No telemetry has been recorded for this Space. That is not evidence of health — ' +
|
|
62
|
+
'it means no board in this Space has reported, so nothing has been measured.');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const rows = Object.entries(summary.events).sort(([a], [b]) => a.localeCompare(b));
|
|
66
|
+
if (rows.length > 0) {
|
|
67
|
+
this.log('');
|
|
68
|
+
this.log('Events:');
|
|
69
|
+
for (const [event, stats] of rows) {
|
|
70
|
+
const parts = [`count ${stats.count}`];
|
|
71
|
+
if (stats.mismatchCount > 0)
|
|
72
|
+
parts.push(`mismatches ${stats.mismatchCount}`);
|
|
73
|
+
if (stats.p50DurationMs !== null) {
|
|
74
|
+
parts.push(`p50 ${stats.p50DurationMs}ms`, `p95 ${stats.p95DurationMs}ms`);
|
|
75
|
+
}
|
|
76
|
+
if (stats.p50Bytes !== null) {
|
|
77
|
+
parts.push(`p50 ${stats.p50Bytes}B`, `p95 ${stats.p95Bytes}B`);
|
|
78
|
+
}
|
|
79
|
+
this.log(` ${event.padEnd(20)} ${parts.join(' · ')}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (summary.rollout.reasons.length > 0) {
|
|
83
|
+
this.log('');
|
|
84
|
+
this.log('Holding because:');
|
|
85
|
+
for (const reason of summary.rollout.reasons) {
|
|
86
|
+
// An unexplained code is the thing this command exists to avoid: the
|
|
87
|
+
// reader is being told a rollout is held and needs to know by what.
|
|
88
|
+
this.log(` ${reason}`);
|
|
89
|
+
const explanation = work_sync_1.WORK_SYNC_HOLD_REASONS[reason];
|
|
90
|
+
if (explanation)
|
|
91
|
+
this.log(` ${explanation}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
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
|
-
|
|
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), [
|
|
@@ -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
|
-
|
|
50
|
-
|
|
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, ' +
|
|
@@ -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>] —
|
|
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({
|
|
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.
|
|
33
|
-
|
|
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
|
-
|
|
14
|
-
|
|
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
|
-
|
|
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({
|
|
14
|
-
|
|
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
|
-
|
|
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
|
|
82
|
+
expectationId,
|
|
57
83
|
idempotencyKey: flags['idempotency-key'] ||
|
|
58
|
-
(0, outbox_1.idempotencyKeyFor)('task-expectation-assessment', task.id, JSON.stringify({ expectationId
|
|
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
|
|
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 ${
|
|
100
|
+
this.log(`Recorded ${expectationId} as ${flags.decision}.`);
|
|
75
101
|
}
|
|
76
102
|
}
|
|
77
103
|
exports.default = TasksExpectationsAssess;
|
|
@@ -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 data_provider_2 = require("@skrr-ai/data-provider");
|
|
5
6
|
const base_command_1 = require("../../base-command");
|
|
6
7
|
/**
|
|
7
8
|
* `skrr tasks expectations <task>` — what this task owes, and how far each got.
|
|
@@ -23,16 +24,58 @@ const STATE_LABEL = {
|
|
|
23
24
|
owed: 'not yet',
|
|
24
25
|
unassessed: 'needs assessment',
|
|
25
26
|
};
|
|
27
|
+
/**
|
|
28
|
+
* The evidence gap, on the rows where a reader can act on it.
|
|
29
|
+
*
|
|
30
|
+
* `unverified` and `underway` BOTH mean "something was produced and pinned to
|
|
31
|
+
* this Expectation, and it is not enough yet" — they differ only in whether the
|
|
32
|
+
* thing has been observed as available. Printing the gap on the first and
|
|
33
|
+
* nothing on the second is what made the careful path unreadable: an operator
|
|
34
|
+
* who pinned a deliverable saw `on its way` and had no way to learn that the
|
|
35
|
+
* bar was `external_receipt` and their own report could never clear it.
|
|
36
|
+
*
|
|
37
|
+
* It stays off the rows where it is noise: a met or waived row has nothing
|
|
38
|
+
* outstanding, and an `owed`/`unassessed` row has no deliverable to compare
|
|
39
|
+
* against, so naming a required authority there describes a hypothetical.
|
|
40
|
+
*/
|
|
41
|
+
const PINNED_STATES = new Set(['unverified', 'underway']);
|
|
42
|
+
function evidenceGap(expectation) {
|
|
43
|
+
/*
|
|
44
|
+
* A `met` row settled BELOW its declared bar says so.
|
|
45
|
+
*
|
|
46
|
+
* `@merged-to-main` asks for `external_receipt` and is satisfiable by a person
|
|
47
|
+
* typing "I checked git log". That is allowed on purpose — refusing it is the
|
|
48
|
+
* deadlock the ladder exists to prevent — but every surface then read as though
|
|
49
|
+
* a receipt had arrived: the row said `done`, the rollup said `met`, and the
|
|
50
|
+
* recorded gap was visible only to someone who dumped the assessment rows and
|
|
51
|
+
* already knew the field existed.
|
|
52
|
+
*/
|
|
53
|
+
if (expectation.evidenceShortfall) {
|
|
54
|
+
const { provided, required } = expectation.evidenceShortfall;
|
|
55
|
+
return ` (vouched at ${provided}, asked for ${required})`;
|
|
56
|
+
}
|
|
57
|
+
if (!PINNED_STATES.has(expectation.state))
|
|
58
|
+
return '';
|
|
59
|
+
if (!expectation.evidenceLevel || !expectation.requiredEvidence)
|
|
60
|
+
return '';
|
|
61
|
+
if (expectation.evidenceLevel === expectation.requiredEvidence)
|
|
62
|
+
return '';
|
|
63
|
+
return ` (has ${expectation.evidenceLevel}, needs ${expectation.requiredEvidence})`;
|
|
64
|
+
}
|
|
26
65
|
function line(expectation) {
|
|
27
66
|
const label = STATE_LABEL[expectation.state] || expectation.state;
|
|
28
67
|
const from = expectation.source === 'space' ? ' [space default]' : '';
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
68
|
+
/*
|
|
69
|
+
* `sentence` before `text`.
|
|
70
|
+
*
|
|
71
|
+
* The server resolves `sentence` through `describeExpectation`, which is what
|
|
72
|
+
* appends a preset's destination — so `@wiki-page-published:Engineering/Deploy`
|
|
73
|
+
* and the same preset at `Ops/Oncall` are distinguishable. Printing `text`
|
|
74
|
+
* rendered the preset's fixed copy and dropped the one part that says WHICH
|
|
75
|
+
* thing is owed. The fallback is for a row that predates the resolved field.
|
|
76
|
+
*/
|
|
77
|
+
const sentence = expectation.sentence || expectation.text;
|
|
78
|
+
return ` ${expectation.id.padEnd(18)} ${label.padEnd(18)} ${sentence}${evidenceGap(expectation)}${from}`;
|
|
36
79
|
}
|
|
37
80
|
class TasksExpectations extends base_command_1.BaseCommand {
|
|
38
81
|
static description = 'Show what must be true before the Task can close and the evidence-backed assessment state';
|
|
@@ -79,12 +122,36 @@ class TasksExpectations extends base_command_1.BaseCommand {
|
|
|
79
122
|
this.log(line(expectation));
|
|
80
123
|
}
|
|
81
124
|
const gating = acceptance.expectations.filter((e) => e.gating);
|
|
82
|
-
|
|
125
|
+
// `met: true` is the GATE's answer — a waiver unblocks closure exactly as a
|
|
126
|
+
// pass does — but it is the wrong REPORT. Counting them together printed
|
|
127
|
+
// "2 of 2 met" for a Task where one expectation was dropped, which is the
|
|
128
|
+
// one thing a reader checking this line needs to know.
|
|
129
|
+
const waived = gating.filter((e) => e.state === 'waived').length;
|
|
130
|
+
const met = gating.filter((e) => e.met).length - waived;
|
|
83
131
|
this.log('');
|
|
84
|
-
this.log(`${met} of ${gating.length} met · overall: ${acceptance.state}`);
|
|
132
|
+
this.log(`${met} of ${gating.length} met${waived > 0 ? ` · ${waived} waived` : ''} · overall: ${acceptance.state}`);
|
|
85
133
|
// Space defaults are copied into the Task at creation. `source=space`
|
|
86
134
|
// remains useful provenance, but these are Task-owned Expectations now and
|
|
87
135
|
// are assessed exactly like explicitly declared ones.
|
|
136
|
+
/*
|
|
137
|
+
* A typed check nothing evaluates behaves exactly like `manual`.
|
|
138
|
+
*
|
|
139
|
+
* `EXPECTATION_CHECK_METHODS` declares six methods and the judge evaluates
|
|
140
|
+
* one: a `deliverable` check is compared against the ledger, and
|
|
141
|
+
* `task_state`, `goal_state`, `metric` and `llm_review` return `unassessed`
|
|
142
|
+
* forever. Verified against production — a `metric` check naming measureId,
|
|
143
|
+
* operator and target stores fine and is never looked at. The row read
|
|
144
|
+
* identically to a hand-written sentence, so somebody who chose
|
|
145
|
+
* `metric: cycle_time lt 2` had no way to learn the platform was not
|
|
146
|
+
* measuring it (OSK-8488).
|
|
147
|
+
*/
|
|
148
|
+
const unevaluated = acceptance.expectations.filter((e) => (0, data_provider_2.declaresUnevaluatedCheck)(e));
|
|
149
|
+
if (unevaluated.length > 0) {
|
|
150
|
+
const methods = [...new Set(unevaluated.map((e) => e.check?.method).filter(Boolean))].join(', ');
|
|
151
|
+
this.log(`${unevaluated.length} declare${unevaluated.length === 1 ? 's' : ''} a typed check nothing ` +
|
|
152
|
+
`evaluates yet (${methods}) — a person settles ${unevaluated.length === 1 ? 'it' : 'them'} ` +
|
|
153
|
+
'exactly as if the check said `manual`.');
|
|
154
|
+
}
|
|
88
155
|
const settleable = acceptance.expectations.filter((e) => e.state === 'unassessed').length;
|
|
89
156
|
if (settleable > 0) {
|
|
90
157
|
this.log(`${settleable} awaiting assessment — use \`${this.config.bin} tasks expectations assess\`.`);
|
|
@@ -98,7 +165,40 @@ class TasksExpectations extends base_command_1.BaseCommand {
|
|
|
98
165
|
`\`${this.config.bin} tasks deliverable confirm\`, or lower the bar with ` +
|
|
99
166
|
`\`${this.config.bin} tasks trust\`.`);
|
|
100
167
|
}
|
|
101
|
-
|
|
168
|
+
// `underway` had no trailer at all, which is why pinning a deliverable read
|
|
169
|
+
// as no progress: the row said `on its way` and nothing named the lever.
|
|
170
|
+
// A Deliverable is recorded `local` by default, so this is the state the
|
|
171
|
+
// ORDINARY `tasks deliverable add` lands in — the common case, not an edge.
|
|
172
|
+
const underway = acceptance.expectations.filter((e) => e.state === 'underway').length;
|
|
173
|
+
if (underway > 0) {
|
|
174
|
+
this.log(`${underway} recorded but not yet observed as available — say it arrived with ` +
|
|
175
|
+
`\`${this.config.bin} tasks deliverable confirm\`, or re-record it with ` +
|
|
176
|
+
`\`--status available\`.`);
|
|
177
|
+
}
|
|
178
|
+
// Counted separately from the unmet ones: these no longer block anything, and
|
|
179
|
+
// the reader's question about them is not "what do I do" but "how good is this
|
|
180
|
+
// record". Saying it once beats repeating the rungs on every row.
|
|
181
|
+
const shortfalls = acceptance.expectations.filter((e) => e.evidenceShortfall).length;
|
|
182
|
+
if (shortfalls > 0) {
|
|
183
|
+
this.log(`${shortfalls} settled below the evidence ${shortfalls === 1 ? 'bar it' : 'bars they'} ` +
|
|
184
|
+
`declared — allowed, and recorded as such on the assessment.`);
|
|
185
|
+
}
|
|
186
|
+
// Said once, for the whole task: `external_receipt` is the one bar no CLI
|
|
187
|
+
// command can clear. The only receipt producer in the platform is PR
|
|
188
|
+
// integration, so an operator staring at a row that needs one would
|
|
189
|
+
// otherwise keep looking for the command that supplies it.
|
|
190
|
+
const receiptBarred = acceptance.expectations.filter((e) => !e.met && e.requiredEvidence === 'external_receipt').length;
|
|
191
|
+
if (receiptBarred > 0) {
|
|
192
|
+
this.log(`${receiptBarred} ask${receiptBarred === 1 ? 's' : ''} for an external provider receipt, ` +
|
|
193
|
+
`which only an integration can supply — settle by hand with ` +
|
|
194
|
+
`\`${this.config.bin} tasks expectations assess\` (the gap is recorded), or lower the ` +
|
|
195
|
+
`bar with \`${this.config.bin} tasks trust\`.`);
|
|
196
|
+
}
|
|
197
|
+
// Settled, not "met" — a waiver settles an Expectation as surely as a pass,
|
|
198
|
+
// and splitting the two in the count above must not make this trailer fire
|
|
199
|
+
// on a Task where nothing is actually outstanding.
|
|
200
|
+
const settled = gating.filter((e) => e.met).length;
|
|
201
|
+
if (gating.length > settled) {
|
|
102
202
|
this.log('');
|
|
103
203
|
this.log('The current Result cannot be accepted while these Expectations remain unmet.');
|
|
104
204
|
}
|
|
@@ -7,6 +7,19 @@ const task_resolver_1 = require("../../lib/task-resolver");
|
|
|
7
7
|
const web_url_1 = require("../../lib/web-url");
|
|
8
8
|
const task_transcript_1 = require("../../lib/task-transcript");
|
|
9
9
|
/** Compatibility command name. The authoritative object is Result, not output. */
|
|
10
|
+
/**
|
|
11
|
+
* "awaiting review" is only true while a review can still be recorded.
|
|
12
|
+
*
|
|
13
|
+
* A closed Task refuses one outright (`TASK_REOPEN_REQUIRED`), so printing
|
|
14
|
+
* "awaiting" described a state that could never be left — and the reader has no
|
|
15
|
+
* way to tell that from a review that is genuinely pending.
|
|
16
|
+
*/
|
|
17
|
+
function reviewLine(review, task) {
|
|
18
|
+
if (review?.decision)
|
|
19
|
+
return review.decision;
|
|
20
|
+
const terminal = ['completed', 'cancelled'].includes(String(task?.statusType ?? ''));
|
|
21
|
+
return terminal ? 'none recorded — the Task is closed' : 'awaiting review';
|
|
22
|
+
}
|
|
10
23
|
class TasksOutput extends base_command_1.BaseCommand {
|
|
11
24
|
static hidden = true;
|
|
12
25
|
static description = 'Show the current Result, Review, Deliverables, and final Run reply';
|
|
@@ -93,10 +106,17 @@ class TasksOutput extends base_command_1.BaseCommand {
|
|
|
93
106
|
if (result.conclusion) {
|
|
94
107
|
this.log(` conclusion (${result.conclusion.type}): ${JSON.stringify(result.conclusion.value)}`);
|
|
95
108
|
}
|
|
96
|
-
this.log(`Review: ${review
|
|
109
|
+
this.log(`Review: ${reviewLine(review, task)}`);
|
|
97
110
|
if (review?.rationale)
|
|
98
111
|
this.log(` ${review.rationale}`);
|
|
99
112
|
}
|
|
113
|
+
else if (task.specVersion === undefined) {
|
|
114
|
+
// A Task predating the canonical model has no TaskResult row and never
|
|
115
|
+
// will. "none submitted" reads as "the work was never reported", which
|
|
116
|
+
// for a completed legacy Task is the opposite of the truth.
|
|
117
|
+
this.log('Result: not recorded under the canonical model (this Task predates it)');
|
|
118
|
+
this.log(' its closeout is in `skrr tasks comments list` / `tasks transcript`');
|
|
119
|
+
}
|
|
100
120
|
else {
|
|
101
121
|
this.log('Result: none submitted');
|
|
102
122
|
}
|
|
@@ -14,7 +14,9 @@ const web_url_1 = require("../../lib/web-url");
|
|
|
14
14
|
*/
|
|
15
15
|
class TasksReport extends base_command_1.BaseCommand {
|
|
16
16
|
static hidden = true;
|
|
17
|
-
static description = '
|
|
17
|
+
static description = 'DEPRECATED compatibility spelling for `tasks result submit`. Every call submits a ' +
|
|
18
|
+
'versioned Result; `--kind` is ignored. Use `tasks result submit` (submit only) or ' +
|
|
19
|
+
'`tasks complete` (submit and close).';
|
|
18
20
|
static examples = [
|
|
19
21
|
'<%= config.bin %> tasks report <task-id> --outcome "Adopt the event ledger" --finding "The task page omits work-event details" --evidence "Task detail reads only legacy activity" --next "Add a Result section"',
|
|
20
22
|
'<%= config.bin %> tasks report <task-id> --kind handoff --outcome "Ready for review" --finding "All expectations are met" --deliverable "file:src/task.ts" --json',
|