@sentropic/track 0.86.0 → 0.87.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/bin.js +14 -1
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +162 -64
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/install-skills.js +10 -10
- package/dist/events/types.d.ts +1 -1
- package/dist/events/types.d.ts.map +1 -1
- package/dist/events/types.js +3 -0
- package/dist/events/types.js.map +1 -1
- package/dist/focus-vendor/cli/index.d.ts +1 -1
- package/dist/ingest/contract.d.ts +2 -2
- package/dist/ingest/contract.d.ts.map +1 -1
- package/dist/ingest/contract.js +13 -2
- package/dist/ingest/contract.js.map +1 -1
- package/dist/ingest/focus-l4.d.ts +1 -1
- package/dist/ingest/focus-l4.d.ts.map +1 -1
- package/dist/ingest/focus-l4.js +10 -2
- package/dist/ingest/focus-l4.js.map +1 -1
- package/dist/ingest/ingest.d.ts.map +1 -1
- package/dist/ingest/ingest.js +8 -0
- package/dist/ingest/ingest.js.map +1 -1
- package/dist/ingest/map.d.ts.map +1 -1
- package/dist/ingest/map.js +3 -0
- package/dist/ingest/map.js.map +1 -1
- package/dist/mcp/server.d.ts +2 -2
- package/dist/mcp/server.js +2 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/model/decision.d.ts +11 -0
- package/dist/model/decision.d.ts.map +1 -1
- package/dist/model/decision.js +66 -0
- package/dist/model/decision.js.map +1 -1
- package/dist/read/commands.d.ts +2 -2
- package/dist/read/commands.d.ts.map +1 -1
- package/dist/read/commands.js +14 -11
- package/dist/read/commands.js.map +1 -1
- package/dist/read/contract.d.ts +18 -6
- package/dist/read/contract.d.ts.map +1 -1
- package/dist/read/contract.js +30 -8
- package/dist/read/contract.js.map +1 -1
- package/dist/report/build.d.ts +20 -1
- package/dist/report/build.d.ts.map +1 -1
- package/dist/report/build.js +40 -14
- package/dist/report/build.js.map +1 -1
- package/dist/report/directive.d.ts.map +1 -1
- package/dist/report/directive.js +6 -4
- package/dist/report/directive.js.map +1 -1
- package/dist/report/format.d.ts +15 -6
- package/dist/report/format.d.ts.map +1 -1
- package/dist/report/format.js +160 -65
- package/dist/report/format.js.map +1 -1
- package/dist/report/html.d.ts +2 -2
- package/dist/report/html.d.ts.map +1 -1
- package/dist/report/html.js +2 -2
- package/dist/report/html.js.map +1 -1
- package/dist/report/snapshot.d.ts +2 -1
- package/dist/report/snapshot.d.ts.map +1 -1
- package/dist/report/snapshot.js +7 -1
- package/dist/report/snapshot.js.map +1 -1
- package/dist/report/status-by-level.d.ts +1 -1
- package/dist/report/status-by-level.d.ts.map +1 -1
- package/dist/report/status-by-level.js +13 -30
- package/dist/report/status-by-level.js.map +1 -1
- package/dist/state/fold.js +10 -0
- package/dist/state/fold.js.map +1 -1
- package/dist/track.d.ts +6 -4
- package/dist/track.d.ts.map +1 -1
- package/dist/track.js +92 -38
- package/dist/track.js.map +1 -1
- package/package.json +2 -2
- package/skills/track-operation/SKILL.md +60 -88
package/dist/cli/bin.js
CHANGED
|
@@ -5,6 +5,17 @@
|
|
|
5
5
|
// bin/ — so the installed `track` silently did nothing. A separate entry that just runs is the
|
|
6
6
|
// same posture as track-mcp's cli.ts and cannot regress that way. `index.ts` stays import-only.
|
|
7
7
|
import { runCli } from './index.js';
|
|
8
|
+
// Keep the natural drain that preserves large reports, while treating a downstream closed pipe (for
|
|
9
|
+
// example `track report | head`) as normal CLI termination rather than an uncaught stream error.
|
|
10
|
+
for (const stream of [process.stdout, process.stderr]) {
|
|
11
|
+
stream.on('error', (error) => {
|
|
12
|
+
if (error.code === 'EPIPE') {
|
|
13
|
+
process.exitCode ??= 0;
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
throw error;
|
|
17
|
+
});
|
|
18
|
+
}
|
|
8
19
|
// `runCli` returns `number | Promise<number>` — the `focus` command is async (it dynamically imports the
|
|
9
20
|
// integrated focus); every other command stays sync and returns a plain number. `Promise.resolve`
|
|
10
21
|
// normalizes both into one exit path, so a sync command still exits with no added microtask churn beyond a
|
|
@@ -13,5 +24,7 @@ Promise.resolve(runCli(process.argv.slice(2), {
|
|
|
13
24
|
cwd: process.cwd(),
|
|
14
25
|
out: (s) => process.stdout.write(s),
|
|
15
26
|
err: (s) => process.stderr.write(s),
|
|
16
|
-
})).then((rc) =>
|
|
27
|
+
})).then((rc) => {
|
|
28
|
+
process.exitCode = rc;
|
|
29
|
+
});
|
|
17
30
|
//# sourceMappingURL=bin.js.map
|
package/dist/cli/bin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bin.js","sourceRoot":"","sources":["../../src/cli/bin.ts"],"names":[],"mappings":";AACA,yFAAyF;AACzF,8FAA8F;AAC9F,+FAA+F;AAC/F,+FAA+F;AAC/F,gGAAgG;AAChG,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAEnC,yGAAyG;AACzG,kGAAkG;AAClG,2GAA2G;AAC3G,yBAAyB;AACzB,OAAO,CAAC,OAAO,CACb,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;IAC5B,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;IAClB,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACnC,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;CACpC,CAAC,
|
|
1
|
+
{"version":3,"file":"bin.js","sourceRoot":"","sources":["../../src/cli/bin.ts"],"names":[],"mappings":";AACA,yFAAyF;AACzF,8FAA8F;AAC9F,+FAA+F;AAC/F,+FAA+F;AAC/F,gGAAgG;AAChG,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAEnC,oGAAoG;AACpG,iGAAiG;AACjG,KAAK,MAAM,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;IACtD,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAA4B,EAAE,EAAE;QAClD,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC3B,OAAO,CAAC,QAAQ,KAAK,CAAC,CAAA;YACtB,OAAM;QACR,CAAC;QACD,MAAM,KAAK,CAAA;IACb,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,yGAAyG;AACzG,kGAAkG;AAClG,2GAA2G;AAC3G,yBAAyB;AACzB,OAAO,CAAC,OAAO,CACb,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;IAC5B,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;IAClB,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACnC,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;CACpC,CAAC,CAIH,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE;IACZ,OAAO,CAAC,QAAQ,GAAG,EAAE,CAAA;AACvB,CAAC,CAAC,CAAA"}
|
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":"AA6CA,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;AAuSD,wBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAsJ7E"}
|
package/dist/cli/index.js
CHANGED
|
@@ -9,16 +9,15 @@ import { cmdEventsContains } from './events-contains.js';
|
|
|
9
9
|
import { cmdInstallSkills } from './install-skills.js';
|
|
10
10
|
import { initTrackDir, resolveTrackDir, resolveTrackDirOrNull } from './resolve.js';
|
|
11
11
|
import { DomainError } from '../model/item.js';
|
|
12
|
-
import { formatRows } from '../report/format.js';
|
|
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,
|
|
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';
|
|
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, reportText, statusText } from '../read/commands.js';
|
|
18
|
+
import { queryText, reportHtml, reportInline, reportText, 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
|
-
import { generateAiReport } from '../report/ai-report.js';
|
|
22
21
|
import { VERSION } from '../version.js';
|
|
23
22
|
import { durableWorkspaceId } from '../workspace-id.js';
|
|
24
23
|
import { desyncFindings } from './desync.js';
|
|
@@ -35,9 +34,11 @@ const USAGE = `usage: track <command>
|
|
|
35
34
|
item assign-code <itemId> --code <c> [--client-token <t>]
|
|
36
35
|
item show <itemId>
|
|
37
36
|
item ls [--workspace <w>] [--kind <feature|bug|chore>] [--format json|text|md]
|
|
38
|
-
decision new --kind <orientation|commitment> --title <t> --workspace <w> --targets <id,id>
|
|
39
|
-
decision
|
|
40
|
-
decision
|
|
37
|
+
decision new --kind <orientation|commitment> --title <t> --workspace <w> --targets <id,id> --context <c> --options-json <json> --recommendation <optionId> --rationale <r> [--accountable <a>] [--engagement-ref <e>]
|
|
38
|
+
decision ls [--workspace <w>] [--outcome <pending|go|no-go|deferred>] [--format json|text|md] [--commit <sha>]
|
|
39
|
+
decision outcome <decisionId> deferred
|
|
40
|
+
decision select <decisionId> <optionId> [--outcome <go|no-go>]
|
|
41
|
+
decision dossier <decisionId> [--context <c>] [--options-json <json> --recommendation <optionId> --rationale <r>]
|
|
41
42
|
decision disposition <itemId> <orientation|commitment> <required|skipped|not-applicable>
|
|
42
43
|
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>]
|
|
43
44
|
blocker raise --target <id> --kind <decision|dependency> [--ref <id>] [--reason <r>] [--rule <linked-done|linked-accepted|manual>] [--scope <intra|extra>] [--engagement-ref <e>]
|
|
@@ -66,7 +67,7 @@ const USAGE = `usage: track <command>
|
|
|
66
67
|
install-skills --host <claude|codex|gemini|agy|all> [--scope user|project] [--force]
|
|
67
68
|
workspace-id [--cwd <path>]
|
|
68
69
|
`;
|
|
69
|
-
// Write enums (ITEM_KINDS, SPEC_TARGETS, REALIZE_TARGETS, DECISION_KINDS,
|
|
70
|
+
// Write enums (ITEM_KINDS, SPEC_TARGETS, REALIZE_TARGETS, DECISION_KINDS, GATES, DISPOSITIONS,
|
|
70
71
|
// BLOCKER_KINDS, RESOLUTION_RULES, EVIDENCE_KINDS, RESULTS) are sourced from the ingest contract — the
|
|
71
72
|
// SINGLE source, so the CLI's `oneOf` checks and the WorkEvent mapper cannot diverge on accepted values.
|
|
72
73
|
// (`linked-accepted` openness is DERIVED at report/query time vs `--commit`, v2.2a hybrid-A; see
|
|
@@ -74,6 +75,7 @@ const USAGE = `usage: track <command>
|
|
|
74
75
|
const REALIZATIONS = ['to-do', 'in-progress', 'done', 'cancelled', 'rejected'];
|
|
75
76
|
const FROM_FORMATS = ['junit', 'json'];
|
|
76
77
|
const BUCKETS_ARG = ['AWAITED', 'DROPPED', 'DONE', 'TO-DO'];
|
|
78
|
+
const DECISION_OUTCOMES = ['pending', 'go', 'no-go', 'deferred'];
|
|
77
79
|
// `n/a` is decision-only; `query` projects non-decision rows, so it would never match.
|
|
78
80
|
const ACCEPTANCES = ['fail', 'waived', 'unknown', 'stale', 'pass'];
|
|
79
81
|
// The DossierArtifact discriminator (M5 §3.1). CLI-local: the union SHAPE is validated fail-closed in the
|
|
@@ -241,6 +243,19 @@ function num(flags, key) {
|
|
|
241
243
|
throw new DomainError(`--${key} must be a number`);
|
|
242
244
|
return n;
|
|
243
245
|
}
|
|
246
|
+
function dossierOptions(raw) {
|
|
247
|
+
let parsed;
|
|
248
|
+
try {
|
|
249
|
+
parsed = JSON.parse(raw);
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
throw new DomainError('--options-json must be a JSON array of existing Option objects');
|
|
253
|
+
}
|
|
254
|
+
if (!Array.isArray(parsed)) {
|
|
255
|
+
throw new DomainError('--options-json must be a JSON array of existing Option objects');
|
|
256
|
+
}
|
|
257
|
+
return parsed;
|
|
258
|
+
}
|
|
244
259
|
/** Validate a positional/flag against an allowed enum (CLI-boundary input validation). */
|
|
245
260
|
function oneOf(value, allowed, name) {
|
|
246
261
|
if (value === undefined || !allowed.includes(value)) {
|
|
@@ -301,6 +316,19 @@ export function runCli(rawArgv, io) {
|
|
|
301
316
|
...(trackDirEnv !== undefined ? { env: trackDirEnv } : {}),
|
|
302
317
|
};
|
|
303
318
|
try {
|
|
319
|
+
// `decision ls` is an inspection surface, not a write verb. Route it through the same serve-empty
|
|
320
|
+
// read path as report/query so it never creates a sidecar and can be used by a fresh reporting agent.
|
|
321
|
+
if (cmd === 'decision' && rest[0] === 'ls') {
|
|
322
|
+
const trackDir = resolveTrackDirOrNull(resolveOpts);
|
|
323
|
+
if (trackDir === null) {
|
|
324
|
+
io.err(`track: no .track resolved from ${io.cwd}. Run \`track init\` to create one ` +
|
|
325
|
+
`(the ONLY command that does), or pass --track-dir / TRACK_DIR. Serving an empty view.\n`);
|
|
326
|
+
}
|
|
327
|
+
return cmdDecisionLs(rest, {
|
|
328
|
+
io,
|
|
329
|
+
eventsPath: trackDir !== null ? eventsPathOf(trackDir) : eventsPathOf(join(resolve(io.cwd), '.track')),
|
|
330
|
+
});
|
|
331
|
+
}
|
|
304
332
|
switch (cmd) {
|
|
305
333
|
case '--version':
|
|
306
334
|
case '-v':
|
|
@@ -659,7 +687,12 @@ function cmdDecision(args, ctx) {
|
|
|
659
687
|
title: req(flags, 'title'),
|
|
660
688
|
workspace: req(flags, 'workspace'),
|
|
661
689
|
targets: req(flags, 'targets').split(',').map((s) => s.trim()).filter(Boolean),
|
|
662
|
-
dossier: {
|
|
690
|
+
dossier: {
|
|
691
|
+
context: req(flags, 'context'),
|
|
692
|
+
options: dossierOptions(req(flags, 'options-json')),
|
|
693
|
+
qa: [],
|
|
694
|
+
recommendation: { optionId: req(flags, 'recommendation'), rationale: req(flags, 'rationale') },
|
|
695
|
+
},
|
|
663
696
|
...(opt(flags, 'accountable') !== undefined ? { accountable: req(flags, 'accountable') } : {}),
|
|
664
697
|
...(opt(flags, 'engagement-ref') !== undefined ? { engagementRef: req(flags, 'engagement-ref') } : {}),
|
|
665
698
|
});
|
|
@@ -667,16 +700,37 @@ function cmdDecision(args, ctx) {
|
|
|
667
700
|
return 0;
|
|
668
701
|
}
|
|
669
702
|
if (sub === 'outcome') {
|
|
670
|
-
track.setOutcome(positional[0], oneOf(positional[1],
|
|
703
|
+
track.setOutcome(positional[0], oneOf(positional[1], ['deferred'], 'outcome'));
|
|
671
704
|
io.out('ok\n');
|
|
672
705
|
return 0;
|
|
673
706
|
}
|
|
674
707
|
if (sub === 'dossier') {
|
|
675
|
-
//
|
|
708
|
+
// Merge: a context-only edit preserves a structured dossier. A legacy dossier must provide the
|
|
709
|
+
// full options/recommendation triplet here; the facade validates it fail-closed.
|
|
676
710
|
const current = track.state().decisions.get(positional[0])?.dossier;
|
|
677
711
|
if (current === undefined)
|
|
678
712
|
throw new DomainError(`unknown decision ${positional[0]}`);
|
|
679
|
-
|
|
713
|
+
const optionJson = opt(flags, 'options-json');
|
|
714
|
+
const recommendation = opt(flags, 'recommendation');
|
|
715
|
+
const rationale = opt(flags, 'rationale');
|
|
716
|
+
if ((recommendation === undefined) !== (rationale === undefined)) {
|
|
717
|
+
throw new DomainError('--recommendation and --rationale must be supplied together');
|
|
718
|
+
}
|
|
719
|
+
if (optionJson !== undefined && recommendation === undefined && current.recommendation === undefined) {
|
|
720
|
+
throw new DomainError('migrating a legacy dossier requires --recommendation and --rationale');
|
|
721
|
+
}
|
|
722
|
+
track.reviseDossier(positional[0], {
|
|
723
|
+
...current,
|
|
724
|
+
context: opt(flags, 'context') ?? current.context,
|
|
725
|
+
...(optionJson !== undefined ? { options: dossierOptions(optionJson) } : {}),
|
|
726
|
+
...(recommendation !== undefined ? { recommendation: { optionId: recommendation, rationale: rationale } } : {}),
|
|
727
|
+
});
|
|
728
|
+
io.out('ok\n');
|
|
729
|
+
return 0;
|
|
730
|
+
}
|
|
731
|
+
if (sub === 'select') {
|
|
732
|
+
const outcome = opt(flags, 'outcome');
|
|
733
|
+
track.selectDecisionOption(positional[0], positional[1], outcome === undefined ? 'go' : oneOf(outcome, ['go', 'no-go'], '--outcome'));
|
|
680
734
|
io.out('ok\n');
|
|
681
735
|
return 0;
|
|
682
736
|
}
|
|
@@ -719,7 +773,7 @@ function cmdDecision(args, ctx) {
|
|
|
719
773
|
io.out('ok\n');
|
|
720
774
|
return 0;
|
|
721
775
|
}
|
|
722
|
-
io.err('usage: track decision <new|outcome|dossier|disposition|add-artifact>\n');
|
|
776
|
+
io.err('usage: track decision <new|outcome|select|dossier|disposition|add-artifact>\n');
|
|
723
777
|
return 2;
|
|
724
778
|
}
|
|
725
779
|
function cmdBlocker(args, ctx) {
|
|
@@ -864,54 +918,41 @@ function cmdReport(args, ctx) {
|
|
|
864
918
|
assertOnlyFlags(flags, ['raw', 'commit', 'require-accepted', 'format']);
|
|
865
919
|
return emitSnapshot(flags, ctx, true);
|
|
866
920
|
}
|
|
867
|
-
// Reads go through the shared TrackReader command layer (same path the MCP server uses).
|
|
868
|
-
const reader = new TrackReader(ctx.eventsPath);
|
|
869
921
|
// Scope §A/§B — `--level <spec|plan|wp|lot|task>` switches to the status(level) projection.
|
|
870
|
-
// Otherwise
|
|
922
|
+
// Otherwise the WP/table conductor is the default; `--flat` is the legacy opt-out.
|
|
871
923
|
if (opt(flags, 'level') !== undefined) {
|
|
872
924
|
assertOnlyFlags(flags, ['level', 'commit', 'require-accepted', 'format']);
|
|
873
925
|
assertBooleanFlag(flags, 'require-accepted');
|
|
874
|
-
io.out(statusText(
|
|
926
|
+
io.out(statusText(new TrackReader(ctx.eventsPath), oneOf(req(flags, 'level'), STATUS_LEVELS, '--level'), {
|
|
875
927
|
baselineCommit: resolveCommit(io.cwd, opt(flags, 'commit')),
|
|
876
928
|
requireAccepted: assertBooleanFlag(flags, 'require-accepted'),
|
|
877
929
|
}, fmt(flags)));
|
|
878
930
|
return 0;
|
|
879
931
|
}
|
|
880
|
-
const rawFormat = opt(flags, 'format');
|
|
881
|
-
const widthArg = opt(flags, 'width');
|
|
882
|
-
const inlineFlag = assertBooleanFlag(flags, 'inline');
|
|
883
|
-
const inline = inlineFlag || widthArg !== undefined;
|
|
884
|
-
if (inline && rawFormat !== undefined && rawFormat !== 'text') {
|
|
885
|
-
throw new DomainError('--inline/--width accepts no --format, or --format text');
|
|
886
|
-
}
|
|
887
|
-
if (rawFormat === 'html' && inline)
|
|
888
|
-
throw new DomainError('--format html rejects --inline/--width');
|
|
889
|
-
// Frozen legacy path. Keep this call and option derivation byte-for-byte equivalent to the former JSON
|
|
890
|
-
// branch: it never enters context collection or the adapter.
|
|
891
|
-
if (rawFormat === 'json') {
|
|
892
|
-
assertOnlyFlags(flags, ['commit', 'require-accepted', 'decisions', 'active-roster', 'wp', 'flat', 'format']);
|
|
893
|
-
io.out(reportText(reader, {
|
|
894
|
-
baselineCommit: resolveCommit(io.cwd, opt(flags, 'commit')),
|
|
895
|
-
requireAccepted: flags['require-accepted'] === true,
|
|
896
|
-
decisions: flags['decisions'] === true,
|
|
897
|
-
wpTree: flags['wp'] === true,
|
|
898
|
-
activeRoster: flags['active-roster'] === true,
|
|
899
|
-
}, 'json'));
|
|
900
|
-
return 0;
|
|
901
|
-
}
|
|
902
932
|
assertOnlyFlags(flags, [
|
|
903
933
|
'commit', 'require-accepted', 'decisions', 'active-roster', 'wp', 'flat', 'inline', 'width', 'format',
|
|
904
934
|
]);
|
|
905
|
-
|
|
935
|
+
const rawFormat = opt(flags, 'format');
|
|
936
|
+
if (rawFormat !== undefined && !['json', 'text', 'md', 'html'].includes(rawFormat)) {
|
|
906
937
|
throw new DomainError('--format must be one of: json|text|md|html');
|
|
907
938
|
}
|
|
939
|
+
const widthArg = opt(flags, 'width');
|
|
940
|
+
const inlineFlag = assertBooleanFlag(flags, 'inline');
|
|
941
|
+
const inline = inlineFlag || widthArg !== undefined;
|
|
942
|
+
const format = oneOf(rawFormat ?? 'text', ['json', 'text', 'md', 'html'], '--format');
|
|
943
|
+
if (inline && format !== 'text')
|
|
944
|
+
throw new DomainError('--inline/--width accepts no --format, or --format text');
|
|
908
945
|
const requireAccepted = assertBooleanFlag(flags, 'require-accepted');
|
|
909
|
-
const
|
|
946
|
+
const requestedDecisions = assertBooleanFlag(flags, 'decisions');
|
|
910
947
|
const activeRoster = assertBooleanFlag(flags, 'active-roster');
|
|
911
948
|
const wp = assertBooleanFlag(flags, 'wp');
|
|
912
949
|
const flat = assertBooleanFlag(flags, 'flat');
|
|
913
950
|
if (wp && flat)
|
|
914
951
|
throw new DomainError('--wp and --flat are mutually exclusive');
|
|
952
|
+
if (format === 'json' && flat)
|
|
953
|
+
throw new DomainError('--flat is only meaningful for text or md reports');
|
|
954
|
+
if (format === 'html' && flat)
|
|
955
|
+
throw new DomainError('--format html is always the deterministic conductor and rejects --flat');
|
|
915
956
|
let width;
|
|
916
957
|
if (widthArg !== undefined) {
|
|
917
958
|
if (!/^\d+$/u.test(widthArg))
|
|
@@ -920,28 +961,30 @@ function cmdReport(args, ctx) {
|
|
|
920
961
|
if (width < 40 || width > 240)
|
|
921
962
|
throw new DomainError('--width must be an integer in [40,240]');
|
|
922
963
|
}
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
964
|
+
// Every format is a deterministic read over the folded log. The default human view is
|
|
965
|
+
// the conductor; --flat explicitly requests the legacy bucket projection. JSON preserves
|
|
966
|
+
// its established flat machine contract unless --wp is explicit, so --flat is rejected there
|
|
967
|
+
// instead of being silently accepted as a no-op. Contextual prose remains advisory agent work;
|
|
968
|
+
// no report format invokes an adapter, gateway, subprocess, or model.
|
|
969
|
+
const options = {
|
|
970
|
+
baselineCommit: resolveCommit(io.cwd, opt(flags, 'commit')),
|
|
971
|
+
requireAccepted,
|
|
972
|
+
// The owner-facing conductor always classifies decision dossiers. JSON keeps its
|
|
973
|
+
// established opt-in decision payload, while --flat retains the legacy opt-in.
|
|
974
|
+
decisions: requestedDecisions || (!flat && format !== 'json'),
|
|
975
|
+
wpTree: wp || (!flat && format !== 'json'),
|
|
976
|
+
activeRoster,
|
|
977
|
+
};
|
|
978
|
+
const reader = new TrackReader(ctx.eventsPath);
|
|
979
|
+
if (inline) {
|
|
980
|
+
io.out(reportInline(reader, options, width === undefined ? {} : { width }));
|
|
981
|
+
}
|
|
982
|
+
else if (format === 'html') {
|
|
983
|
+
io.out(reportHtml(reader, options));
|
|
984
|
+
}
|
|
985
|
+
else {
|
|
986
|
+
io.out(reportText(reader, options, format));
|
|
987
|
+
}
|
|
945
988
|
return 0;
|
|
946
989
|
}
|
|
947
990
|
function emitSnapshot(flags, ctx, allowRaw) {
|
|
@@ -996,6 +1039,43 @@ function cmdQuery(args, ctx) {
|
|
|
996
1039
|
}, { baselineCommit: resolveCommit(io.cwd, opt(flags, 'commit')) }, fmt(flags)));
|
|
997
1040
|
return 0;
|
|
998
1041
|
}
|
|
1042
|
+
/**
|
|
1043
|
+
* List decision dossiers without a renderer cap. `structure` makes the prose-only migration state explicit;
|
|
1044
|
+
* only a structured dossier has durable alternatives + a durable recommendation to render as a choice.
|
|
1045
|
+
*/
|
|
1046
|
+
function cmdDecisionLs(args, ctx) {
|
|
1047
|
+
const { io } = ctx;
|
|
1048
|
+
const { positional, flags } = parseFlags(args.slice(1));
|
|
1049
|
+
if (positional.length > 0)
|
|
1050
|
+
throw new DomainError(`unexpected decision ls argument(s): ${positional.join(' ')}`);
|
|
1051
|
+
assertOnlyFlags(flags, ['workspace', 'outcome', 'format', 'commit']);
|
|
1052
|
+
for (const name of ['workspace', 'outcome', 'format', 'commit'])
|
|
1053
|
+
assertValueFlag(flags, name);
|
|
1054
|
+
const format = fmt(flags);
|
|
1055
|
+
const baselineCommit = resolveCommit(io.cwd, opt(flags, 'commit'));
|
|
1056
|
+
const reader = new TrackReader(ctx.eventsPath);
|
|
1057
|
+
const workspace = opt(flags, 'workspace');
|
|
1058
|
+
const outcome = opt(flags, 'outcome') !== undefined ? oneOf(req(flags, 'outcome'), DECISION_OUTCOMES, '--outcome') : undefined;
|
|
1059
|
+
const rows = reader.decisionDossiers({ baselineCommit })
|
|
1060
|
+
.filter((decision) => (workspace === undefined || decision.workspace === workspace) && (outcome === undefined || decision.outcome === outcome))
|
|
1061
|
+
.map(({ dossier, ...decision }) => ({
|
|
1062
|
+
...decision,
|
|
1063
|
+
options: dossier.options,
|
|
1064
|
+
...(dossier.recommendation !== undefined ? { recommendation: dossier.recommendation } : {}),
|
|
1065
|
+
}));
|
|
1066
|
+
if (format === 'json') {
|
|
1067
|
+
io.out(`${JSON.stringify(rows, null, 2)}\n`);
|
|
1068
|
+
return 0;
|
|
1069
|
+
}
|
|
1070
|
+
const lines = rows.map((decision) => {
|
|
1071
|
+
const safe = (value) => displayText(value, format);
|
|
1072
|
+
const recommendation = safe(decision.recommendation?.optionId ?? '-');
|
|
1073
|
+
const line = `${safe(decision.id)} · ${safe(decision.workspace)} · ${safe(decision.outcome)} · ${safe(decision.structure ?? 'unstructured')} · options:${decision.options.length} · recommendation:${recommendation} — ${safe(decision.title)}`;
|
|
1074
|
+
return format === 'md' ? `- ${line}` : line;
|
|
1075
|
+
});
|
|
1076
|
+
io.out(lines.length > 0 ? `${lines.join('\n')}\n` : '');
|
|
1077
|
+
return 0;
|
|
1078
|
+
}
|
|
999
1079
|
/**
|
|
1000
1080
|
* `track workspace-activity --workspace <id> [--baseline-commit <sha>] [--now <iso>] [--idle-ms <ms>]
|
|
1001
1081
|
* [--format json|text]` — a poll surface over the shipped, CLOCKLESS `TrackReader.workspaceActivity`
|
|
@@ -1079,6 +1159,15 @@ async function cmdFocus(args, ctx, _noStore) {
|
|
|
1079
1159
|
const { io } = ctx;
|
|
1080
1160
|
const FOCUS_USAGE = 'usage: track focus <decision-id> --workspace <w> [--format terminal|md|html] [--baseline-commit <sha>]\n';
|
|
1081
1161
|
const { positional, flags } = parseFlags(args);
|
|
1162
|
+
try {
|
|
1163
|
+
assertOnlyFlags(flags, ['workspace', 'format', 'baseline-commit']);
|
|
1164
|
+
for (const name of ['workspace', 'format', 'baseline-commit'])
|
|
1165
|
+
assertValueFlag(flags, name);
|
|
1166
|
+
}
|
|
1167
|
+
catch (error) {
|
|
1168
|
+
io.err(`error: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
1169
|
+
return 2;
|
|
1170
|
+
}
|
|
1082
1171
|
// decision-id is positional + REQUIRED; --workspace is a REQUIRED flag (both gate at the CLI boundary
|
|
1083
1172
|
// with rc=2 + usage, never reaching focus). Validate BEFORE the dynamic import so a usage error is
|
|
1084
1173
|
// independent of whether focus is installed.
|
|
@@ -1098,6 +1187,15 @@ async function cmdFocus(args, ctx, _noStore) {
|
|
|
1098
1187
|
io.err(`error: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
1099
1188
|
return 2;
|
|
1100
1189
|
}
|
|
1190
|
+
const baselineCommit = resolveCommit(io.cwd, opt(flags, 'baseline-commit'));
|
|
1191
|
+
const decision = new TrackReader(ctx.eventsPath)
|
|
1192
|
+
.report({ baselineCommit, decisions: true })
|
|
1193
|
+
.decisions
|
|
1194
|
+
?.find((candidate) => candidate.id === decisionId);
|
|
1195
|
+
if (decision !== undefined && decision.workspace !== workspace) {
|
|
1196
|
+
io.err(`error: decision ${decisionId} belongs to workspace ${decision.workspace}, not ${workspace}\n`);
|
|
1197
|
+
return 3;
|
|
1198
|
+
}
|
|
1101
1199
|
// Load the focus render binding + core. focus is an integrated dependency → a MODULE_NOT_FOUND means the
|
|
1102
1200
|
// integrated focus renderer is unavailable: map it to rc=1 + a helpful hint (NOT a stack trace).
|
|
1103
1201
|
let focusTrack;
|
|
@@ -1118,7 +1216,7 @@ async function cmdFocus(args, ctx, _noStore) {
|
|
|
1118
1216
|
try {
|
|
1119
1217
|
// PURE / read-only / clockless: `readAt` is supplied at the CLI boundary (track holds no clock), the
|
|
1120
1218
|
// baseline commit resolves HEAD/refs/short-SHA → 40-char, and `ctx.eventsPath` is the single store.
|
|
1121
|
-
const doc = focusTrack.readDecisionDossier(ctx.eventsPath, { workspace, baselineCommit
|
|
1219
|
+
const doc = focusTrack.readDecisionDossier(ctx.eventsPath, { workspace, baselineCommit, decisionId }, new Date().toISOString());
|
|
1122
1220
|
const rendered = format === 'md' ? core.renderMd(doc) : format === 'html' ? core.renderHtml(doc, HTML_HOOKS) : core.renderTerminal(doc);
|
|
1123
1221
|
io.out(rendered.endsWith('\n') ? rendered : `${rendered}\n`);
|
|
1124
1222
|
return 0;
|