@sentropic/track 0.87.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 +209 -20
- 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 +24 -18
- package/dist/read/commands.d.ts.map +1 -1
- package/dist/read/commands.js +167 -26
- package/dist/read/commands.js.map +1 -1
- package/dist/read/contract.d.ts +32 -3
- package/dist/read/contract.d.ts.map +1 -1
- package/dist/read/contract.js +42 -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/build.d.ts +6 -0
- package/dist/report/build.d.ts.map +1 -1
- package/dist/report/build.js +2 -31
- package/dist/report/build.js.map +1 -1
- package/dist/report/format.d.ts +227 -8
- package/dist/report/format.d.ts.map +1 -1
- package/dist/report/format.js +946 -106
- package/dist/report/format.js.map +1 -1
- package/dist/report/index.d.ts +1 -1
- package/dist/report/index.d.ts.map +1 -1
- package/dist/report/index.js +1 -1
- package/dist/report/index.js.map +1 -1
- package/dist/report/rollup.d.ts +14 -0
- package/dist/report/rollup.d.ts.map +1 -1
- package/dist/report/rollup.js +32 -0
- package/dist/report/rollup.js.map +1 -1
- package/dist/report/snapshot.d.ts +20 -1
- package/dist/report/snapshot.d.ts.map +1 -1
- package/dist/report/snapshot.js +9 -2
- package/dist/report/snapshot.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 +16 -4
- package/dist/report/html.d.ts +0 -9
- package/dist/report/html.d.ts.map +0 -1
- package/dist/report/html.js +0 -76
- 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,26 +11,31 @@ 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';
|
|
22
22
|
import { durableWorkspaceId } from '../workspace-id.js';
|
|
23
23
|
import { desyncFindings } from './desync.js';
|
|
24
24
|
const USAGE = `usage: track <command>
|
|
25
|
+
--help | help
|
|
26
|
+
global read/store override: --track-dir <directory-containing-events.jsonl> (or TRACK_DIR)
|
|
27
|
+
may appear before or after the command; it redirects reads AND writes to that directory
|
|
25
28
|
--version | -v
|
|
26
29
|
init
|
|
27
|
-
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)
|
|
28
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)
|
|
29
33
|
item set-role <itemId> <workpackage|stream>
|
|
30
34
|
item scope-declare <itemId> [--allowed <glob,glob>] [--forbidden <...>] [--conditional <...>] [--scope <json>]
|
|
31
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>]
|
|
32
36
|
item spec <itemId> <to-specify|specified>
|
|
33
37
|
item realize <itemId> <in-progress|done|cancelled>
|
|
38
|
+
item reopen <itemId> --motive <closed-without-owner-uat|regression-observed> --reason <r> [--client-token <t>]
|
|
34
39
|
item assign-code <itemId> --code <c> [--client-token <t>]
|
|
35
40
|
item show <itemId>
|
|
36
41
|
item ls [--workspace <w>] [--kind <feature|bug|chore>] [--format json|text|md]
|
|
@@ -41,7 +46,7 @@ const USAGE = `usage: track <command>
|
|
|
41
46
|
decision dossier <decisionId> [--context <c>] [--options-json <json> --recommendation <optionId> --rationale <r>]
|
|
42
47
|
decision disposition <itemId> <orientation|commitment> <required|skipped|not-applicable>
|
|
43
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>]
|
|
44
|
-
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>]
|
|
45
50
|
blocker resolve <blockerId>
|
|
46
51
|
blocker resolve-external --engagement-ref <e>
|
|
47
52
|
accept criterion <itemId> --statement <s>
|
|
@@ -51,7 +56,7 @@ const USAGE = `usage: track <command>
|
|
|
51
56
|
accept waive <criterionId> --reason <r>
|
|
52
57
|
consolidate --items <id,id> --commit <mergeCommit> [--client-token <t>]
|
|
53
58
|
priority assess <itemId> --ubv <n> --tc <n> --rr <n> --js <n>
|
|
54
|
-
report [--decisions] [--require-accepted] [--active-roster] [--wp|--flat] [--inline] [--width <n>] [--level <spec|plan|wp|lot|task>] [--raw] [--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>]
|
|
55
60
|
snapshot [--require-accepted] [--format json|text|md] [--commit <sha>]
|
|
56
61
|
export-graph [--repo-key <repo:key>] [--source-id <id>] [--observed-at <iso>]
|
|
57
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>]
|
|
@@ -127,6 +132,98 @@ function resolveCommit(cwd, c) {
|
|
|
127
132
|
return c;
|
|
128
133
|
}
|
|
129
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
|
+
}
|
|
130
227
|
function remoteKeyFromUrl(remoteUrl) {
|
|
131
228
|
const trimmed = remoteUrl.trim().replace(/\.git$/i, '');
|
|
132
229
|
const sshUrl = trimmed.match(/^ssh:\/\/(?:[^@]+@)?([^/\s]+)(\/.+)$/i);
|
|
@@ -305,10 +402,32 @@ function extractTrackDirFlag(argv) {
|
|
|
305
402
|
}
|
|
306
403
|
return trackDirFlag !== undefined ? { trackDirFlag, rest } : { rest };
|
|
307
404
|
}
|
|
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.
|
|
408
|
+
|
|
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.
|
|
410
|
+
|
|
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.
|
|
412
|
+
|
|
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.
|
|
416
|
+
|
|
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.
|
|
418
|
+
`;
|
|
308
419
|
export function runCli(rawArgv, io) {
|
|
309
420
|
const { trackDirFlag, rest: argv } = extractTrackDirFlag(rawArgv);
|
|
310
421
|
const cmd = argv[0];
|
|
311
422
|
const rest = argv.slice(1);
|
|
423
|
+
if (cmd === '--help' || cmd === 'help' || cmd === undefined) {
|
|
424
|
+
io.out(USAGE);
|
|
425
|
+
return 0;
|
|
426
|
+
}
|
|
427
|
+
if (cmd === 'report' && rest.length === 1 && rest[0] === '--help') {
|
|
428
|
+
io.out(REPORT_USAGE);
|
|
429
|
+
return 0;
|
|
430
|
+
}
|
|
312
431
|
const trackDirEnv = process.env['TRACK_DIR'];
|
|
313
432
|
const resolveOpts = {
|
|
314
433
|
cwd: io.cwd,
|
|
@@ -507,6 +626,26 @@ function cmdItem(args, ctx) {
|
|
|
507
626
|
io.out('ok\n');
|
|
508
627
|
return 0;
|
|
509
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
|
+
}
|
|
510
649
|
if (sub === 'scope-declare') {
|
|
511
650
|
// Scope §B(a) — declare INERT path-scope globs on a WP/spec-phase. A comma-separated glob list per
|
|
512
651
|
// axis (`--allowed`/`--forbidden`/`--conditional`), OR a `--scope <json>` object; the two are
|
|
@@ -598,6 +737,23 @@ function cmdItem(args, ctx) {
|
|
|
598
737
|
io.out('ok\n');
|
|
599
738
|
return 0;
|
|
600
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
|
+
}
|
|
601
757
|
if (sub === 'assign-code') {
|
|
602
758
|
// A1 (wp-codes) — assign a durable, re-assignable display `code` to a workpackage/spec-phase (the
|
|
603
759
|
// canonical write; `assignCode` enforces roster-global uniqueness, re-asserted under the lock).
|
|
@@ -625,7 +781,7 @@ function cmdItem(args, ctx) {
|
|
|
625
781
|
rowsOut(rows, fmt(flags), io);
|
|
626
782
|
return 0;
|
|
627
783
|
}
|
|
628
|
-
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');
|
|
629
785
|
return 2;
|
|
630
786
|
}
|
|
631
787
|
/**
|
|
@@ -782,6 +938,11 @@ function cmdBlocker(args, ctx) {
|
|
|
782
938
|
const { positional, flags } = parseFlags(args.slice(1));
|
|
783
939
|
const track = writeTrack(ctx);
|
|
784
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
|
+
}
|
|
785
946
|
const id = track.openBlocker({
|
|
786
947
|
targetId: req(flags, 'target'),
|
|
787
948
|
kind: oneOf(req(flags, 'kind'), BLOCKER_KINDS, '--kind'),
|
|
@@ -794,6 +955,7 @@ function cmdBlocker(args, ctx) {
|
|
|
794
955
|
? { scope: oneOf(req(flags, 'scope'), BLOCKER_SCOPES, '--scope') }
|
|
795
956
|
: {}),
|
|
796
957
|
...(opt(flags, 'engagement-ref') !== undefined ? { engagementRef: req(flags, 'engagement-ref') } : {}),
|
|
958
|
+
...(owner !== undefined ? { owner } : {}),
|
|
797
959
|
});
|
|
798
960
|
io.out(`${id}\n`);
|
|
799
961
|
return 0;
|
|
@@ -911,8 +1073,18 @@ function cmdReport(args, ctx) {
|
|
|
911
1073
|
const { positional, flags } = parseFlags(args);
|
|
912
1074
|
if (positional.length > 0)
|
|
913
1075
|
throw new DomainError(`unexpected report argument(s): ${positional.join(' ')}`);
|
|
914
|
-
for (const name of ['commit', 'format', 'level', 'width'])
|
|
1076
|
+
for (const name of ['commit', 'format', 'level', 'width', 'resolve', 'now', 'scope', 'since', 'until', 'period'])
|
|
915
1077
|
assertValueFlag(flags, name);
|
|
1078
|
+
const scope = opt(flags, 'scope');
|
|
1079
|
+
// Criterion 10b — the one documented command that turns a short report handle back into an item.
|
|
1080
|
+
if (opt(flags, 'resolve') !== undefined) {
|
|
1081
|
+
assertOnlyFlags(flags, ['resolve', 'scope', 'commit', 'require-accepted']);
|
|
1082
|
+
io.out(resolveHandle(new TrackReader(ctx.eventsPath), {
|
|
1083
|
+
baselineCommit: resolveCommit(io.cwd, opt(flags, 'commit')),
|
|
1084
|
+
requireAccepted: assertBooleanFlag(flags, 'require-accepted'),
|
|
1085
|
+
}, req(flags, 'resolve'), scope));
|
|
1086
|
+
return 0;
|
|
1087
|
+
}
|
|
916
1088
|
const raw = assertBooleanFlag(flags, 'raw');
|
|
917
1089
|
if (raw) {
|
|
918
1090
|
assertOnlyFlags(flags, ['raw', 'commit', 'require-accepted', 'format']);
|
|
@@ -930,16 +1102,17 @@ function cmdReport(args, ctx) {
|
|
|
930
1102
|
return 0;
|
|
931
1103
|
}
|
|
932
1104
|
assertOnlyFlags(flags, [
|
|
933
|
-
'commit', 'require-accepted', 'decisions', 'active-roster', 'wp', 'flat', 'inline', 'width', 'format',
|
|
1105
|
+
'commit', 'require-accepted', 'decisions', 'active-roster', 'wp', 'flat', 'inline', 'width', 'format', 'now',
|
|
1106
|
+
'sub-wp', 'scope', 'since', 'until', 'period',
|
|
934
1107
|
]);
|
|
935
1108
|
const rawFormat = opt(flags, 'format');
|
|
936
|
-
if (rawFormat !== undefined && !['json', 'text', 'md'
|
|
937
|
-
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');
|
|
938
1111
|
}
|
|
939
1112
|
const widthArg = opt(flags, 'width');
|
|
940
1113
|
const inlineFlag = assertBooleanFlag(flags, 'inline');
|
|
941
1114
|
const inline = inlineFlag || widthArg !== undefined;
|
|
942
|
-
const format = oneOf(rawFormat ?? 'text', ['json', 'text', 'md'
|
|
1115
|
+
const format = oneOf(rawFormat ?? 'text', ['json', 'text', 'md'], '--format');
|
|
943
1116
|
if (inline && format !== 'text')
|
|
944
1117
|
throw new DomainError('--inline/--width accepts no --format, or --format text');
|
|
945
1118
|
const requireAccepted = assertBooleanFlag(flags, 'require-accepted');
|
|
@@ -951,8 +1124,10 @@ function cmdReport(args, ctx) {
|
|
|
951
1124
|
throw new DomainError('--wp and --flat are mutually exclusive');
|
|
952
1125
|
if (format === 'json' && flat)
|
|
953
1126
|
throw new DomainError('--flat is only meaningful for text or md reports');
|
|
954
|
-
if (
|
|
955
|
-
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');
|
|
956
1131
|
let width;
|
|
957
1132
|
if (widthArg !== undefined) {
|
|
958
1133
|
if (!/^\d+$/u.test(widthArg))
|
|
@@ -961,6 +1136,11 @@ function cmdReport(args, ctx) {
|
|
|
961
1136
|
if (width < 40 || width > 240)
|
|
962
1137
|
throw new DomainError('--width must be an integer in [40,240]');
|
|
963
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
|
+
}
|
|
964
1144
|
// Every format is a deterministic read over the folded log. The default human view is
|
|
965
1145
|
// the conductor; --flat explicitly requests the legacy bucket projection. JSON preserves
|
|
966
1146
|
// its established flat machine contract unless --wp is explicit, so --flat is rejected there
|
|
@@ -971,19 +1151,28 @@ function cmdReport(args, ctx) {
|
|
|
971
1151
|
requireAccepted,
|
|
972
1152
|
// The owner-facing conductor always classifies decision dossiers. JSON keeps its
|
|
973
1153
|
// established opt-in decision payload, while --flat retains the legacy opt-in.
|
|
974
|
-
decisions: requestedDecisions || (!flat && format !== 'json'),
|
|
975
|
-
wpTree: wp || (!flat && format !== 'json'),
|
|
1154
|
+
decisions: scope !== undefined || requestedDecisions || (!flat && format !== 'json'),
|
|
1155
|
+
wpTree: scope !== undefined || wp || (!flat && format !== 'json'),
|
|
976
1156
|
activeRoster,
|
|
977
1157
|
};
|
|
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.
|
|
1161
|
+
const nowArg = opt(flags, 'now');
|
|
1162
|
+
if (nowArg !== undefined && Number.isNaN(new Date(nowArg).getTime())) {
|
|
1163
|
+
throw new DomainError('--now must be an ISO timestamp');
|
|
1164
|
+
}
|
|
1165
|
+
const periodClock = nowArg ?? new Date().toISOString();
|
|
1166
|
+
const period = resolveReportPeriod(flags, io.cwd, periodClock);
|
|
1167
|
+
// Criterion 25 — the EXPLICIT owner request for sub-WP rows. Without it the reading unit follows the
|
|
1168
|
+
// window: the WP on a long one, the sub-level on a short one.
|
|
1169
|
+
const subWp = assertBooleanFlag(flags, 'sub-wp');
|
|
978
1170
|
const reader = new TrackReader(ctx.eventsPath);
|
|
979
1171
|
if (inline) {
|
|
980
|
-
io.out(reportInline(reader, options, width === undefined ? {} : { width }));
|
|
981
|
-
}
|
|
982
|
-
else if (format === 'html') {
|
|
983
|
-
io.out(reportHtml(reader, options));
|
|
1172
|
+
io.out(reportInline(reader, options, width === undefined ? {} : { width }, nowArg, period));
|
|
984
1173
|
}
|
|
985
1174
|
else {
|
|
986
|
-
io.out(reportText(reader, options, format));
|
|
1175
|
+
io.out(reportText(reader, options, format, nowArg, subWp, scope, period));
|
|
987
1176
|
}
|
|
988
1177
|
return 0;
|
|
989
1178
|
}
|