@sentropic/track 0.88.0 → 0.89.0
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/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +175 -28
- package/dist/cli/index.js.map +1 -1
- package/dist/events/types.d.ts +1 -1
- package/dist/events/types.d.ts.map +1 -1
- package/dist/events/types.js +16 -0
- package/dist/events/types.js.map +1 -1
- package/dist/events/validate.d.ts +10 -0
- package/dist/events/validate.d.ts.map +1 -1
- package/dist/events/validate.js +81 -0
- package/dist/events/validate.js.map +1 -1
- package/dist/ingest/contract.d.ts +3 -2
- package/dist/ingest/contract.d.ts.map +1 -1
- package/dist/ingest/contract.js +33 -1
- package/dist/ingest/contract.js.map +1 -1
- package/dist/ingest/index.d.ts.map +1 -1
- package/dist/ingest/index.js +6 -5
- package/dist/ingest/index.js.map +1 -1
- package/dist/ingest/ingest.d.ts.map +1 -1
- package/dist/ingest/ingest.js +17 -0
- package/dist/ingest/ingest.js.map +1 -1
- package/dist/ingest/map.d.ts.map +1 -1
- package/dist/ingest/map.js +11 -0
- package/dist/ingest/map.js.map +1 -1
- package/dist/model/index.d.ts +1 -1
- package/dist/model/index.d.ts.map +1 -1
- package/dist/model/index.js +1 -1
- package/dist/model/index.js.map +1 -1
- package/dist/model/item.d.ts +65 -0
- package/dist/model/item.d.ts.map +1 -1
- package/dist/model/item.js +49 -0
- package/dist/model/item.js.map +1 -1
- package/dist/read/commands.d.ts +23 -11
- package/dist/read/commands.d.ts.map +1 -1
- package/dist/read/commands.js +142 -36
- package/dist/read/commands.js.map +1 -1
- package/dist/read/contract.d.ts +21 -3
- package/dist/read/contract.d.ts.map +1 -1
- package/dist/read/contract.js +26 -2
- package/dist/read/contract.js.map +1 -1
- package/dist/report/blocker-status.d.ts.map +1 -1
- package/dist/report/blocker-status.js +11 -0
- package/dist/report/blocker-status.js.map +1 -1
- package/dist/report/format.d.ts +55 -1
- package/dist/report/format.d.ts.map +1 -1
- package/dist/report/format.js +203 -72
- package/dist/report/format.js.map +1 -1
- package/dist/state/fold.d.ts.map +1 -1
- package/dist/state/fold.js +48 -0
- package/dist/state/fold.js.map +1 -1
- package/dist/track.d.ts +34 -1
- package/dist/track.d.ts.map +1 -1
- package/dist/track.js +95 -2
- package/dist/track.js.map +1 -1
- package/package.json +1 -1
- package/skills/track-operation/SKILL.md +12 -2
- package/dist/report/html.d.ts +0 -9
- package/dist/report/html.d.ts.map +0 -1
- package/dist/report/html.js +0 -73
- package/dist/report/html.js.map +0 -1
- package/dist/report/present.d.ts +0 -28
- package/dist/report/present.d.ts.map +0 -1
- package/dist/report/present.js +0 -32
- package/dist/report/present.js.map +0 -1
package/dist/cli/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AA8CA,MAAM,WAAW,KAAK;IACpB,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;IACxB,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;IACxB,kGAAkG;IAClG,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;CACxB;AAqZD,wBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA8J7E"}
|
package/dist/cli/index.js
CHANGED
|
@@ -11,11 +11,11 @@ import { initTrackDir, resolveTrackDir, resolveTrackDirOrNull } from './resolve.
|
|
|
11
11
|
import { DomainError } from '../model/item.js';
|
|
12
12
|
import { displayText, formatRows } from '../report/format.js';
|
|
13
13
|
import { Track } from '../track.js';
|
|
14
|
-
import { BLOCKER_KINDS, BLOCKER_SCOPES, DECISION_KINDS, DISPOSITIONS, EVIDENCE_KINDS, GATES, ITEM_KINDS, ITEM_ROLES, REALIZE_TARGETS, RESOLUTION_RULES, RESULTS, ROLE_CHANGE_TARGETS, SPEC_TARGETS, } from '../ingest/contract.js';
|
|
14
|
+
import { BLOCKER_KINDS, BLOCKER_SCOPES, DECISION_KINDS, DISPOSITIONS, EVIDENCE_KINDS, GATES, ITEM_KINDS, ITEM_ROLES, REALIZE_TARGETS, REOPEN_MOTIVE_VALUES, RESOLUTION_RULES, RESULTS, ROLE_CHANGE_TARGETS, SPEC_TARGETS, } from '../ingest/contract.js';
|
|
15
15
|
import { ingest } from '../ingest/ingest.js';
|
|
16
16
|
import { applyRestructurePlan } from './restructure-apply.js';
|
|
17
17
|
import { TrackReader } from '../read/contract.js';
|
|
18
|
-
import { queryText,
|
|
18
|
+
import { queryText, reportInline, reportText, resolveHandle, statusText } from '../read/commands.js';
|
|
19
19
|
import { STATUS_LEVELS } from '../report/status-by-level.js';
|
|
20
20
|
import { renderSnapshot } from '../report/snapshot.js';
|
|
21
21
|
import { VERSION } from '../version.js';
|
|
@@ -27,13 +27,15 @@ const USAGE = `usage: track <command>
|
|
|
27
27
|
may appear before or after the command; it redirects reads AND writes to that directory
|
|
28
28
|
--version | -v
|
|
29
29
|
init
|
|
30
|
-
item new --kind <feature|bug|chore> --title <t> --workspace <w> [--body <b>] [--parent <id>] [--role <workpackage|spec-phase|stream>] [--accountable <a>] [--responsible <a,a>]
|
|
30
|
+
item new --kind <feature|bug|chore> --title <t> --workspace <w> [--body <b>] [--parent <id>] [--role <workpackage|spec-phase|stream>] [--accountable <a>] [--responsible <a,a>] (trim actor IDs; blank responsible members are dropped)
|
|
31
31
|
item reparent <itemId> [--parent <pid>] [--detach]
|
|
32
|
+
item set-raci <itemId> [--accountable <a>] [--responsible <a,a>] [--client-token <t>] (trimmed blank members are rejected, unlike item new)
|
|
32
33
|
item set-role <itemId> <workpackage|stream>
|
|
33
34
|
item scope-declare <itemId> [--allowed <glob,glob>] [--forbidden <...>] [--conditional <...>] [--scope <json>]
|
|
34
35
|
item spec-amend <itemId> --base-hash <h> --result-hash <h> --patch <json> [--decision-id <id>] [--live-doc-ref <r>] [--proposal-ref <r>] [--summary <s>] [--client-token <t>]
|
|
35
36
|
item spec <itemId> <to-specify|specified>
|
|
36
37
|
item realize <itemId> <in-progress|done|cancelled>
|
|
38
|
+
item reopen <itemId> --motive <closed-without-owner-uat|regression-observed> --reason <r> [--client-token <t>]
|
|
37
39
|
item assign-code <itemId> --code <c> [--client-token <t>]
|
|
38
40
|
item show <itemId>
|
|
39
41
|
item ls [--workspace <w>] [--kind <feature|bug|chore>] [--format json|text|md]
|
|
@@ -44,7 +46,7 @@ const USAGE = `usage: track <command>
|
|
|
44
46
|
decision dossier <decisionId> [--context <c>] [--options-json <json> --recommendation <optionId> --rationale <r>]
|
|
45
47
|
decision disposition <itemId> <orientation|commitment> <required|skipped|not-applicable>
|
|
46
48
|
decision add-artifact <decisionId> --kind <h2a-decision-dossier|rendered-view|mockup> [--negotiation-ref <n>] [--dossier-hash <h>] [--view-ref <v>] [--source-dossier-hash <h>] [--label <l>] [--client-token <t>]
|
|
47
|
-
blocker raise --target <id> --kind <decision|dependency> [--ref <id>] [--reason <r>] [--rule <linked-done|linked-accepted|manual>] [--scope <intra|extra>] [--engagement-ref <e>]
|
|
49
|
+
blocker raise --target <id> --kind <decision|dependency> [--ref <id>] [--reason <r>] [--rule <linked-done|linked-accepted|manual>] [--scope <intra|extra>] [--engagement-ref <e>] [--owner <actor>]
|
|
48
50
|
blocker resolve <blockerId>
|
|
49
51
|
blocker resolve-external --engagement-ref <e>
|
|
50
52
|
accept criterion <itemId> --statement <s>
|
|
@@ -54,7 +56,7 @@ const USAGE = `usage: track <command>
|
|
|
54
56
|
accept waive <criterionId> --reason <r>
|
|
55
57
|
consolidate --items <id,id> --commit <mergeCommit> [--client-token <t>]
|
|
56
58
|
priority assess <itemId> --ubv <n> --tc <n> --rr <n> --js <n>
|
|
57
|
-
report [--decisions] [--require-accepted] [--active-roster] [--wp|--flat] [--inline] [--width <n>] [--level <spec|plan|wp|lot|task>] [--raw] [--resolve <handle>] [--sub-wp] [--format json|text|md
|
|
59
|
+
report [--scope <container-id|code|label>] [--since <sha|YYYY-MM-DD> [--until <sha|YYYY-MM-DD>]|--period <today|week|month|all>] [--decisions] [--require-accepted] [--active-roster] [--wp|--flat] [--inline] [--width <n>] [--level <spec|plan|wp|lot|task>] [--raw] [--resolve <handle>] [--sub-wp] [--format json|text|md] [--commit <sha>] [--now <iso>]
|
|
58
60
|
snapshot [--require-accepted] [--format json|text|md] [--commit <sha>]
|
|
59
61
|
export-graph [--repo-key <repo:key>] [--source-id <id>] [--observed-at <iso>]
|
|
60
62
|
query [--kind <k>] [--role <workpackage|spec-phase|stream>] [--workspace <w>] [--bucket <AWAITED|DROPPED|DONE|TO-DO>] [--realization <r>] [--acceptance <a>] [--format json|text|md] [--commit <sha>]
|
|
@@ -130,6 +132,98 @@ function resolveCommit(cwd, c) {
|
|
|
130
132
|
return c;
|
|
131
133
|
}
|
|
132
134
|
}
|
|
135
|
+
/** A calendar date is a local civil day at the CLI boundary, then emitted as an absolute instant. */
|
|
136
|
+
function resolveReportDate(raw, upper) {
|
|
137
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/u.exec(raw);
|
|
138
|
+
if (match === null)
|
|
139
|
+
return undefined;
|
|
140
|
+
const year = Number(match[1]);
|
|
141
|
+
const month = Number(match[2]);
|
|
142
|
+
const day = Number(match[3]);
|
|
143
|
+
const value = new Date(year, month - 1, day, upper ? 23 : 0, upper ? 59 : 0, upper ? 59 : 0, upper ? 999 : 0);
|
|
144
|
+
if (value.getFullYear() !== year || value.getMonth() !== month - 1 || value.getDate() !== day)
|
|
145
|
+
return undefined;
|
|
146
|
+
return value.toISOString();
|
|
147
|
+
}
|
|
148
|
+
/** Resolve a date or commit selector strictly: period bounds must never silently degrade into a literal. */
|
|
149
|
+
function resolveReportBoundary(cwd, raw, flag) {
|
|
150
|
+
const date = resolveReportDate(raw, flag === '--until');
|
|
151
|
+
if (date !== undefined)
|
|
152
|
+
return { at: date, ref: null };
|
|
153
|
+
try {
|
|
154
|
+
const ref = execFileSync('git', ['rev-parse', '--verify', '--end-of-options', `${raw}^{commit}`], {
|
|
155
|
+
cwd,
|
|
156
|
+
encoding: 'utf8',
|
|
157
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
158
|
+
}).trim();
|
|
159
|
+
const at = execFileSync('git', ['show', '-s', '--format=%cI', ref], {
|
|
160
|
+
cwd,
|
|
161
|
+
encoding: 'utf8',
|
|
162
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
163
|
+
}).trim();
|
|
164
|
+
if (Number.isNaN(Date.parse(at)))
|
|
165
|
+
throw new Error('invalid committer date');
|
|
166
|
+
return { at, ref };
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
throw new DomainError(`${flag} must be a YYYY-MM-DD date or a resolvable commit`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function localDayStart(value) {
|
|
173
|
+
return new Date(value.getFullYear(), value.getMonth(), value.getDate());
|
|
174
|
+
}
|
|
175
|
+
function localDayEnd(value) {
|
|
176
|
+
return new Date(value.getFullYear(), value.getMonth(), value.getDate(), 23, 59, 59, 999);
|
|
177
|
+
}
|
|
178
|
+
function namedReportPeriod(raw, now) {
|
|
179
|
+
const clock = new Date(now);
|
|
180
|
+
if (Number.isNaN(clock.getTime()))
|
|
181
|
+
throw new DomainError('--now must be an ISO timestamp');
|
|
182
|
+
if (raw === 'all')
|
|
183
|
+
return { requested: raw, fromRef: null, toRef: null };
|
|
184
|
+
let from;
|
|
185
|
+
let to;
|
|
186
|
+
if (raw === 'today') {
|
|
187
|
+
from = localDayStart(clock);
|
|
188
|
+
to = localDayEnd(clock);
|
|
189
|
+
}
|
|
190
|
+
else if (raw === 'week') {
|
|
191
|
+
const start = localDayStart(clock);
|
|
192
|
+
start.setDate(start.getDate() - ((start.getDay() + 6) % 7)); // Monday, in local time
|
|
193
|
+
from = start;
|
|
194
|
+
to = new Date(start.getFullYear(), start.getMonth(), start.getDate() + 6, 23, 59, 59, 999);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
from = new Date(clock.getFullYear(), clock.getMonth(), 1);
|
|
198
|
+
to = new Date(clock.getFullYear(), clock.getMonth() + 1, 0, 23, 59, 59, 999);
|
|
199
|
+
}
|
|
200
|
+
return { requested: raw, from: from.toISOString(), to: to.toISOString(), fromRef: null, toRef: null };
|
|
201
|
+
}
|
|
202
|
+
function resolveReportPeriod(flags, cwd, now) {
|
|
203
|
+
const since = opt(flags, 'since');
|
|
204
|
+
const until = opt(flags, 'until');
|
|
205
|
+
const named = opt(flags, 'period');
|
|
206
|
+
if (since !== undefined && named !== undefined)
|
|
207
|
+
throw new DomainError('--since and --period are mutually exclusive');
|
|
208
|
+
if (until !== undefined && since === undefined)
|
|
209
|
+
throw new DomainError('--until requires --since');
|
|
210
|
+
if (named !== undefined)
|
|
211
|
+
return namedReportPeriod(oneOf(named, ['today', 'week', 'month', 'all'], '--period'), now);
|
|
212
|
+
if (since === undefined)
|
|
213
|
+
return undefined;
|
|
214
|
+
const lower = resolveReportBoundary(cwd, since, '--since');
|
|
215
|
+
const upper = until === undefined ? undefined : resolveReportBoundary(cwd, until, '--until');
|
|
216
|
+
if (upper !== undefined && Date.parse(lower.at) > Date.parse(upper.at)) {
|
|
217
|
+
throw new DomainError('--since must not be after --until');
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
requested: until === undefined ? since : `${since}..${until}`,
|
|
221
|
+
from: lower.at,
|
|
222
|
+
...(upper !== undefined ? { to: upper.at } : {}),
|
|
223
|
+
fromRef: lower.ref,
|
|
224
|
+
toRef: upper?.ref ?? null,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
133
227
|
function remoteKeyFromUrl(remoteUrl) {
|
|
134
228
|
const trimmed = remoteUrl.trim().replace(/\.git$/i, '');
|
|
135
229
|
const sshUrl = trimmed.match(/^ssh:\/\/(?:[^@]+@)?([^/\s]+)(\/.+)$/i);
|
|
@@ -308,13 +402,17 @@ function extractTrackDirFlag(argv) {
|
|
|
308
402
|
}
|
|
309
403
|
return trackDirFlag !== undefined ? { trackDirFlag, rest } : { rest };
|
|
310
404
|
}
|
|
311
|
-
const REPORT_USAGE = `usage: track report [--raw] [--wp] [--flat] [--inline|--width <40..240>] [--decisions] [--active-roster] [--require-accepted] [--resolve <handle>] [--commit <sha>] [--now <iso>] [--sub-wp] [--format json|text|md
|
|
405
|
+
const REPORT_USAGE = `usage: track report [--scope <container-id|code|label>] [--since <sha|YYYY-MM-DD> [--until <sha|YYYY-MM-DD>] | --period <today|week|month|all>] [--raw] [--wp] [--flat] [--inline|--width <40..240>] [--decisions] [--active-roster] [--require-accepted] [--resolve <handle>] [--commit <sha>] [--now <iso>] [--sub-wp] [--format json|text|md] [--track-dir <directory-containing-events.jsonl>]
|
|
406
|
+
|
|
407
|
+
--scope selects one exact role-container by its id, durable assigned code, or current derived label. The selected container and all descendants are rendered through the same four-section report; unknown or ambiguous selectors fail loudly.
|
|
312
408
|
|
|
313
409
|
--resolve <handle> resolves a report handle (a positional [n.m] row handle, or a D#/Q# dossier number) back to its item id. It is the one command the report documents for acting on a row without printing a ULID in a column. Handles are positional and per-report: resolve them against the same log and baseline the report was rendered from.
|
|
314
410
|
|
|
315
411
|
--sub-wp lists sub-WP rows beside their parent. Without it, sub-levels are aggregated into their parent on a long window (>= 14 days) and listed on a short one — the WP is the reading unit of a long report.
|
|
316
412
|
|
|
317
|
-
--now <iso> pins the
|
|
413
|
+
--now <iso> pins the clock used by a named period and, without a selector, the whole-log upper bound. Without it, whole-log and --since reports stop at the journal head.
|
|
414
|
+
|
|
415
|
+
--since accepts a local YYYY-MM-DD date or a git commit (using its committer date); --until closes a --since range. --period today|week|month|all is the named alternative. --since and --period are mutually exclusive. A selected period changes FAIT only: À-FAIRE remains the full current open-work projection.
|
|
318
416
|
|
|
319
417
|
--track-dir is a global override and may appear before or after the command. It selects the directory that contains events.jsonl; it is especially useful for a read-only fixture. TRACK_DIR is the environment equivalent.
|
|
320
418
|
`;
|
|
@@ -528,6 +626,26 @@ function cmdItem(args, ctx) {
|
|
|
528
626
|
io.out('ok\n');
|
|
529
627
|
return 0;
|
|
530
628
|
}
|
|
629
|
+
if (sub === 'set-raci') {
|
|
630
|
+
const accountable = opt(flags, 'accountable');
|
|
631
|
+
const responsible = opt(flags, 'responsible');
|
|
632
|
+
if (accountable === undefined && responsible === undefined) {
|
|
633
|
+
throw new DomainError('item set-raci requires --accountable and/or --responsible');
|
|
634
|
+
}
|
|
635
|
+
const clientToken = opt(flags, 'client-token');
|
|
636
|
+
if (clientToken !== undefined && store(ctx).readAll().some((event) => event.clientToken === clientToken)) {
|
|
637
|
+
io.out('no-op: client-token already applied\n');
|
|
638
|
+
return 0;
|
|
639
|
+
}
|
|
640
|
+
track.setRaci(positional[0], {
|
|
641
|
+
...(accountable !== undefined ? { accountable } : {}),
|
|
642
|
+
...(responsible !== undefined
|
|
643
|
+
? { responsible: responsible.split(',').map((s) => s.trim()) }
|
|
644
|
+
: {}),
|
|
645
|
+
}, clientToken);
|
|
646
|
+
io.out('ok\n');
|
|
647
|
+
return 0;
|
|
648
|
+
}
|
|
531
649
|
if (sub === 'scope-declare') {
|
|
532
650
|
// Scope §B(a) — declare INERT path-scope globs on a WP/spec-phase. A comma-separated glob list per
|
|
533
651
|
// axis (`--allowed`/`--forbidden`/`--conditional`), OR a `--scope <json>` object; the two are
|
|
@@ -619,6 +737,23 @@ function cmdItem(args, ctx) {
|
|
|
619
737
|
io.out('ok\n');
|
|
620
738
|
return 0;
|
|
621
739
|
}
|
|
740
|
+
if (sub === 'reopen') {
|
|
741
|
+
// Regression expression — reopen a terminally-closed item (done/cancelled → in-progress) WITH its motive.
|
|
742
|
+
// Deliberately a SEPARATE verb from `item realize`, which keeps done/cancelled terminal. `--client-token`
|
|
743
|
+
// gives append-once idempotency at the CLI boundary (mirrors `assign-code`/`spec-amend`): without it a
|
|
744
|
+
// retry would hit "not closed" — the item is already reopened — instead of reading as the no-op it is.
|
|
745
|
+
const itemId = positional[0];
|
|
746
|
+
const motive = oneOf(req(flags, 'motive'), REOPEN_MOTIVE_VALUES, '--motive');
|
|
747
|
+
const reason = req(flags, 'reason');
|
|
748
|
+
const clientToken = opt(flags, 'client-token');
|
|
749
|
+
if (clientToken !== undefined && store(ctx).readAll().some((e) => e.clientToken === clientToken)) {
|
|
750
|
+
io.out('no-op: client-token already applied\n');
|
|
751
|
+
return 0;
|
|
752
|
+
}
|
|
753
|
+
track.reopenItem(itemId, { motive, reason }, clientToken);
|
|
754
|
+
io.out(`reopened ${itemId} (${motive})\n`);
|
|
755
|
+
return 0;
|
|
756
|
+
}
|
|
622
757
|
if (sub === 'assign-code') {
|
|
623
758
|
// A1 (wp-codes) — assign a durable, re-assignable display `code` to a workpackage/spec-phase (the
|
|
624
759
|
// canonical write; `assignCode` enforces roster-global uniqueness, re-asserted under the lock).
|
|
@@ -646,7 +781,7 @@ function cmdItem(args, ctx) {
|
|
|
646
781
|
rowsOut(rows, fmt(flags), io);
|
|
647
782
|
return 0;
|
|
648
783
|
}
|
|
649
|
-
io.err('usage: track item <new|reparent|set-role|scope-declare|spec-amend|spec|realize|assign-code|show|ls>\n');
|
|
784
|
+
io.err('usage: track item <new|reparent|set-raci|set-role|scope-declare|spec-amend|spec|realize|assign-code|show|ls>\n');
|
|
650
785
|
return 2;
|
|
651
786
|
}
|
|
652
787
|
/**
|
|
@@ -803,6 +938,11 @@ function cmdBlocker(args, ctx) {
|
|
|
803
938
|
const { positional, flags } = parseFlags(args.slice(1));
|
|
804
939
|
const track = writeTrack(ctx);
|
|
805
940
|
if (sub === 'raise') {
|
|
941
|
+
const rawOwner = opt(flags, 'owner');
|
|
942
|
+
const owner = rawOwner?.trim();
|
|
943
|
+
if (rawOwner !== undefined && owner === '') {
|
|
944
|
+
throw new DomainError('blocker raise: --owner must be a non-empty actor');
|
|
945
|
+
}
|
|
806
946
|
const id = track.openBlocker({
|
|
807
947
|
targetId: req(flags, 'target'),
|
|
808
948
|
kind: oneOf(req(flags, 'kind'), BLOCKER_KINDS, '--kind'),
|
|
@@ -815,6 +955,7 @@ function cmdBlocker(args, ctx) {
|
|
|
815
955
|
? { scope: oneOf(req(flags, 'scope'), BLOCKER_SCOPES, '--scope') }
|
|
816
956
|
: {}),
|
|
817
957
|
...(opt(flags, 'engagement-ref') !== undefined ? { engagementRef: req(flags, 'engagement-ref') } : {}),
|
|
958
|
+
...(owner !== undefined ? { owner } : {}),
|
|
818
959
|
});
|
|
819
960
|
io.out(`${id}\n`);
|
|
820
961
|
return 0;
|
|
@@ -932,15 +1073,16 @@ function cmdReport(args, ctx) {
|
|
|
932
1073
|
const { positional, flags } = parseFlags(args);
|
|
933
1074
|
if (positional.length > 0)
|
|
934
1075
|
throw new DomainError(`unexpected report argument(s): ${positional.join(' ')}`);
|
|
935
|
-
for (const name of ['commit', 'format', 'level', 'width', 'resolve', 'now'])
|
|
1076
|
+
for (const name of ['commit', 'format', 'level', 'width', 'resolve', 'now', 'scope', 'since', 'until', 'period'])
|
|
936
1077
|
assertValueFlag(flags, name);
|
|
1078
|
+
const scope = opt(flags, 'scope');
|
|
937
1079
|
// Criterion 10b — the one documented command that turns a short report handle back into an item.
|
|
938
1080
|
if (opt(flags, 'resolve') !== undefined) {
|
|
939
|
-
assertOnlyFlags(flags, ['resolve', 'commit', 'require-accepted']);
|
|
1081
|
+
assertOnlyFlags(flags, ['resolve', 'scope', 'commit', 'require-accepted']);
|
|
940
1082
|
io.out(resolveHandle(new TrackReader(ctx.eventsPath), {
|
|
941
1083
|
baselineCommit: resolveCommit(io.cwd, opt(flags, 'commit')),
|
|
942
1084
|
requireAccepted: assertBooleanFlag(flags, 'require-accepted'),
|
|
943
|
-
}, req(flags, 'resolve')));
|
|
1085
|
+
}, req(flags, 'resolve'), scope));
|
|
944
1086
|
return 0;
|
|
945
1087
|
}
|
|
946
1088
|
const raw = assertBooleanFlag(flags, 'raw');
|
|
@@ -961,16 +1103,16 @@ function cmdReport(args, ctx) {
|
|
|
961
1103
|
}
|
|
962
1104
|
assertOnlyFlags(flags, [
|
|
963
1105
|
'commit', 'require-accepted', 'decisions', 'active-roster', 'wp', 'flat', 'inline', 'width', 'format', 'now',
|
|
964
|
-
'sub-wp',
|
|
1106
|
+
'sub-wp', 'scope', 'since', 'until', 'period',
|
|
965
1107
|
]);
|
|
966
1108
|
const rawFormat = opt(flags, 'format');
|
|
967
|
-
if (rawFormat !== undefined && !['json', 'text', 'md'
|
|
968
|
-
throw new DomainError('--format must be one of: json|text|md
|
|
1109
|
+
if (rawFormat !== undefined && !['json', 'text', 'md'].includes(rawFormat)) {
|
|
1110
|
+
throw new DomainError('--format must be one of: json|text|md');
|
|
969
1111
|
}
|
|
970
1112
|
const widthArg = opt(flags, 'width');
|
|
971
1113
|
const inlineFlag = assertBooleanFlag(flags, 'inline');
|
|
972
1114
|
const inline = inlineFlag || widthArg !== undefined;
|
|
973
|
-
const format = oneOf(rawFormat ?? 'text', ['json', 'text', 'md'
|
|
1115
|
+
const format = oneOf(rawFormat ?? 'text', ['json', 'text', 'md'], '--format');
|
|
974
1116
|
if (inline && format !== 'text')
|
|
975
1117
|
throw new DomainError('--inline/--width accepts no --format, or --format text');
|
|
976
1118
|
const requireAccepted = assertBooleanFlag(flags, 'require-accepted');
|
|
@@ -982,8 +1124,10 @@ function cmdReport(args, ctx) {
|
|
|
982
1124
|
throw new DomainError('--wp and --flat are mutually exclusive');
|
|
983
1125
|
if (format === 'json' && flat)
|
|
984
1126
|
throw new DomainError('--flat is only meaningful for text or md reports');
|
|
985
|
-
if (
|
|
986
|
-
throw new DomainError('--
|
|
1127
|
+
if (scope !== undefined && flat)
|
|
1128
|
+
throw new DomainError('--scope requires the four-section conductor and rejects --flat');
|
|
1129
|
+
if (scope !== undefined && activeRoster)
|
|
1130
|
+
throw new DomainError('--scope includes the complete subtree and rejects --active-roster');
|
|
987
1131
|
let width;
|
|
988
1132
|
if (widthArg !== undefined) {
|
|
989
1133
|
if (!/^\d+$/u.test(widthArg))
|
|
@@ -992,6 +1136,11 @@ function cmdReport(args, ctx) {
|
|
|
992
1136
|
if (width < 40 || width > 240)
|
|
993
1137
|
throw new DomainError('--width must be an integer in [40,240]');
|
|
994
1138
|
}
|
|
1139
|
+
if (scope !== undefined && inline)
|
|
1140
|
+
throw new DomainError('--scope preserves the validated report shape and rejects --inline/--width');
|
|
1141
|
+
if (scope !== undefined && format === 'md') {
|
|
1142
|
+
throw new DomainError('--scope currently supports the owner text report and JSON projection only');
|
|
1143
|
+
}
|
|
995
1144
|
// Every format is a deterministic read over the folded log. The default human view is
|
|
996
1145
|
// the conductor; --flat explicitly requests the legacy bucket projection. JSON preserves
|
|
997
1146
|
// its established flat machine contract unless --wp is explicit, so --flat is rejected there
|
|
@@ -1002,30 +1151,28 @@ function cmdReport(args, ctx) {
|
|
|
1002
1151
|
requireAccepted,
|
|
1003
1152
|
// The owner-facing conductor always classifies decision dossiers. JSON keeps its
|
|
1004
1153
|
// established opt-in decision payload, while --flat retains the legacy opt-in.
|
|
1005
|
-
decisions: requestedDecisions || (!flat && format !== 'json'),
|
|
1006
|
-
wpTree: wp || (!flat && format !== 'json'),
|
|
1154
|
+
decisions: scope !== undefined || requestedDecisions || (!flat && format !== 'json'),
|
|
1155
|
+
wpTree: scope !== undefined || wp || (!flat && format !== 'json'),
|
|
1007
1156
|
activeRoster,
|
|
1008
1157
|
};
|
|
1009
|
-
// Criterion 21 — the report
|
|
1010
|
-
//
|
|
1011
|
-
//
|
|
1158
|
+
// Criterion 21 — with no selector the report runs from the first event to the journal head; an injected
|
|
1159
|
+
// clock is an explicit reproducible upper bound. Named calendar periods use the wall clock only to turn
|
|
1160
|
+
// `today`/`week`/`month` into absolute local-time bounds, while the library itself remains clockless.
|
|
1012
1161
|
const nowArg = opt(flags, 'now');
|
|
1013
1162
|
if (nowArg !== undefined && Number.isNaN(new Date(nowArg).getTime())) {
|
|
1014
1163
|
throw new DomainError('--now must be an ISO timestamp');
|
|
1015
1164
|
}
|
|
1016
|
-
const
|
|
1165
|
+
const periodClock = nowArg ?? new Date().toISOString();
|
|
1166
|
+
const period = resolveReportPeriod(flags, io.cwd, periodClock);
|
|
1017
1167
|
// Criterion 25 — the EXPLICIT owner request for sub-WP rows. Without it the reading unit follows the
|
|
1018
1168
|
// window: the WP on a long one, the sub-level on a short one.
|
|
1019
1169
|
const subWp = assertBooleanFlag(flags, 'sub-wp');
|
|
1020
1170
|
const reader = new TrackReader(ctx.eventsPath);
|
|
1021
1171
|
if (inline) {
|
|
1022
|
-
io.out(reportInline(reader, options, width === undefined ? {} : { width }));
|
|
1023
|
-
}
|
|
1024
|
-
else if (format === 'html') {
|
|
1025
|
-
io.out(reportHtml(reader, options, now, subWp));
|
|
1172
|
+
io.out(reportInline(reader, options, width === undefined ? {} : { width }, nowArg, period));
|
|
1026
1173
|
}
|
|
1027
1174
|
else {
|
|
1028
|
-
io.out(reportText(reader, options, format,
|
|
1175
|
+
io.out(reportText(reader, options, format, nowArg, subWp, scope, period));
|
|
1029
1176
|
}
|
|
1030
1177
|
return 0;
|
|
1031
1178
|
}
|