@basou/cli 0.46.0 → 0.48.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/index.js +75 -36
- package/dist/index.js.map +1 -1
- package/dist/program.js +75 -36
- package/dist/program.js.map +1 -1
- package/package.json +2 -2
package/dist/program.js
CHANGED
|
@@ -1250,7 +1250,8 @@ Input format (a JSON array; one object per decision):
|
|
|
1250
1250
|
"rationale": "Workspace protocol and a content-addressed store fit our layout.",
|
|
1251
1251
|
"alternatives": ["npm workspaces", "yarn"],
|
|
1252
1252
|
"rejected_reason": "npm hoisting caused phantom-dependency bugs",
|
|
1253
|
-
"linked_files": ["pnpm-workspace.yaml"]
|
|
1253
|
+
"linked_files": ["pnpm-workspace.yaml"],
|
|
1254
|
+
"kind": "decision"
|
|
1254
1255
|
},
|
|
1255
1256
|
{
|
|
1256
1257
|
"title": "Form-based admin editing is the next track (only 6/19 sections done)",
|
|
@@ -1259,10 +1260,16 @@ Input format (a JSON array; one object per decision):
|
|
|
1259
1260
|
}
|
|
1260
1261
|
]
|
|
1261
1262
|
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1263
|
+
"title" and "kind" are both required. "kind" names the vessel: "track" records a
|
|
1264
|
+
strategic, UNFINISHED direction (+ why) and orientation/handoff resurface it
|
|
1265
|
+
every session until you close it with 'basou decision void <id>', while
|
|
1266
|
+
"decision" records a point-in-time call (surfaced as the latest). Every other
|
|
1267
|
+
field is optional.
|
|
1268
|
+
|
|
1269
|
+
"kind" has no default because getting it wrong fails silently: a track filed as
|
|
1270
|
+
a decision never enters the open-track list, so nothing holds it there -- the
|
|
1271
|
+
next decision simply takes its place. Omitting it on ANY item refuses the whole
|
|
1272
|
+
batch and writes nothing, so a partial write can never be reported as a success.
|
|
1266
1273
|
All decisions are written into one ad-hoc session timestamped now, so
|
|
1267
1274
|
orientation surfaces them as the latest decisions. Run from a workspace-view
|
|
1268
1275
|
directory and it resolves to the planning repo, like 'basou orient' /
|
|
@@ -1270,7 +1277,7 @@ directory and it resolves to the planning repo, like 'basou orient' /
|
|
|
1270
1277
|
|
|
1271
1278
|
Example (heredoc on stdin):
|
|
1272
1279
|
basou decision capture <<'JSON'
|
|
1273
|
-
[{ "title": "Ship the capture command", "rationale": "Close the why-capture gap" }]
|
|
1280
|
+
[{ "title": "Ship the capture command", "rationale": "Close the why-capture gap", "kind": "decision" }]
|
|
1274
1281
|
JSON
|
|
1275
1282
|
`;
|
|
1276
1283
|
async function runDecisionRecord(options, ctx = {}) {
|
|
@@ -1306,15 +1313,6 @@ async function warnLinkedFilesOutsideRoots(input) {
|
|
|
1306
1313
|
} catch {
|
|
1307
1314
|
}
|
|
1308
1315
|
}
|
|
1309
|
-
var TRACK_MARKER_IN_TITLE = /^\s*track\s*[:\uff1a]|[[(\uff08\uff3b\u3010]\s*track\s*[\])\uff09\uff3d\u3011]/i;
|
|
1310
|
-
function warnTrackMarkerWithoutKind(decisions, markerWithoutKind) {
|
|
1311
|
-
for (const index of markerWithoutKind) {
|
|
1312
|
-
const title = (decisions[index]?.title ?? "").trim();
|
|
1313
|
-
console.error(
|
|
1314
|
-
`basou: decision[${index}] title carries a track marker (${title.slice(0, 40)}) but "kind" is absent \u2014 it is recorded as a point-in-time decision and will NOT resurface in orient. Set "kind": "track" to open a track, or "kind": "decision" to say the marker is part of the title.`
|
|
1315
|
-
);
|
|
1316
|
-
}
|
|
1317
|
-
}
|
|
1318
1316
|
async function doRunDecisionRecord(options, ctx) {
|
|
1319
1317
|
const cwd = ctx.cwd ?? process.cwd();
|
|
1320
1318
|
const repositoryRoot = await resolveRepositoryRootForDecision(cwd);
|
|
@@ -1412,8 +1410,7 @@ async function doRunDecisionCapture(options, ctx) {
|
|
|
1412
1410
|
const paths = basouPaths4(repositoryRoot);
|
|
1413
1411
|
await assertWorkspaceInitialized2(paths.root);
|
|
1414
1412
|
const raw = await readCaptureInput(options, ctx);
|
|
1415
|
-
const { decisions
|
|
1416
|
-
warnTrackMarkerWithoutKind(decisions, markerWithoutKind);
|
|
1413
|
+
const { decisions } = parseCaptureInput(raw);
|
|
1417
1414
|
await warnLinkedFilesOutsideRoots({
|
|
1418
1415
|
linkedFiles: decisions.flatMap((d) => d.linked_files ?? []),
|
|
1419
1416
|
cwd,
|
|
@@ -1675,13 +1672,31 @@ function parseCaptureInput(raw) {
|
|
|
1675
1672
|
throw new Error("Input array must contain at least one decision.");
|
|
1676
1673
|
}
|
|
1677
1674
|
const decisions = [];
|
|
1678
|
-
const
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1675
|
+
const missingKind = [];
|
|
1676
|
+
try {
|
|
1677
|
+
parsed.forEach((item, index) => {
|
|
1678
|
+
const input = validateCaptureItem(item, index);
|
|
1679
|
+
decisions.push(input);
|
|
1680
|
+
if (input.kind === void 0) missingKind.push(index);
|
|
1681
|
+
});
|
|
1682
|
+
} catch (error) {
|
|
1683
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1684
|
+
throw new Error(`${detail} ${batchDisposition(parsed.length)}`, { cause: error });
|
|
1685
|
+
}
|
|
1686
|
+
if (missingKind.length > 0) {
|
|
1687
|
+
throw new Error(`${missingKindError(missingKind)} ${batchDisposition(parsed.length)}`);
|
|
1688
|
+
}
|
|
1689
|
+
return { decisions };
|
|
1690
|
+
}
|
|
1691
|
+
var MISSING_KIND_SAMPLE = 10;
|
|
1692
|
+
function batchDisposition(total) {
|
|
1693
|
+
return `Nothing was written: all ${total} item(s) in this batch were refused.`;
|
|
1694
|
+
}
|
|
1695
|
+
function missingKindError(indices) {
|
|
1696
|
+
const shown = indices.slice(0, MISSING_KIND_SAMPLE).map((i) => `decision[${i}]`);
|
|
1697
|
+
const overflow = indices.length - shown.length;
|
|
1698
|
+
const list = overflow > 0 ? `${shown.join(", ")} (... +${overflow} more)` : shown.join(", ");
|
|
1699
|
+
return `${list}: "kind" is required. It names the vessel: "track" for an unfinished direction, which orient keeps in its open-track list until you close it with 'basou decision void', or "decision" for a settled point-in-time call, which the next decision replaces. Add "kind" to every item and re-run.`;
|
|
1685
1700
|
}
|
|
1686
1701
|
function validateCaptureItem(item, index) {
|
|
1687
1702
|
if (typeof item !== "object" || item === null || Array.isArray(item)) {
|
|
@@ -1698,12 +1713,12 @@ function validateCaptureItem(item, index) {
|
|
|
1698
1713
|
if (typeof obj.title !== "string" || isBlank(obj.title)) {
|
|
1699
1714
|
throw new Error(`decision[${index}].title must be a non-empty string.`);
|
|
1700
1715
|
}
|
|
1716
|
+
if (obj.kind !== void 0 && obj.kind !== "decision" && obj.kind !== "track") {
|
|
1717
|
+
throw new Error(`decision[${index}].kind must be "decision" or "track", got '${obj.kind}'.`);
|
|
1718
|
+
}
|
|
1701
1719
|
const out = { title: obj.title };
|
|
1702
1720
|
if (obj.kind !== void 0) {
|
|
1703
|
-
|
|
1704
|
-
throw new Error(`decision[${index}].kind must be "decision" or "track", got '${obj.kind}'.`);
|
|
1705
|
-
}
|
|
1706
|
-
if (obj.kind === "track") out.kind = "track";
|
|
1721
|
+
out.kind = obj.kind;
|
|
1707
1722
|
}
|
|
1708
1723
|
if (obj.rationale !== void 0) {
|
|
1709
1724
|
out.rationale = requireNonEmptyString(obj.rationale, index, "rationale");
|
|
@@ -1742,7 +1757,7 @@ function validateCaptureItem(item, index) {
|
|
|
1742
1757
|
}
|
|
1743
1758
|
});
|
|
1744
1759
|
}
|
|
1745
|
-
return
|
|
1760
|
+
return out;
|
|
1746
1761
|
}
|
|
1747
1762
|
function requireNonEmptyString(value, index, field) {
|
|
1748
1763
|
if (typeof value !== "string" || isBlank(value)) {
|
|
@@ -1793,9 +1808,12 @@ function captureItemToPayload(item) {
|
|
|
1793
1808
|
if (item.input.kind !== void 0) payload.kind = item.input.kind;
|
|
1794
1809
|
return payload;
|
|
1795
1810
|
}
|
|
1796
|
-
function
|
|
1811
|
+
function previewKindMarker(kind) {
|
|
1797
1812
|
return kind === "track" ? " [TRACK]" : " [DECISION]";
|
|
1798
1813
|
}
|
|
1814
|
+
function recordedKindMarker(kind) {
|
|
1815
|
+
return kind === "track" ? " [TRACK]" : "";
|
|
1816
|
+
}
|
|
1799
1817
|
function printCapturePreview(options, decisions) {
|
|
1800
1818
|
if (options.json === true) {
|
|
1801
1819
|
console.log(JSON.stringify({ dry_run: true, count: decisions.length, decisions }));
|
|
@@ -1805,7 +1823,7 @@ function printCapturePreview(options, decisions) {
|
|
|
1805
1823
|
`Would capture ${decisions.length} decision${decisions.length === 1 ? "" : "s"} (dry run; nothing written):`
|
|
1806
1824
|
);
|
|
1807
1825
|
for (const decision of decisions) {
|
|
1808
|
-
console.log(`- ${decision.title}${
|
|
1826
|
+
console.log(`- ${decision.title}${previewKindMarker(decision.kind)}`);
|
|
1809
1827
|
}
|
|
1810
1828
|
}
|
|
1811
1829
|
function printCaptureResult(options, result) {
|
|
@@ -1826,7 +1844,7 @@ function printCaptureResult(options, result) {
|
|
|
1826
1844
|
`Captured ${result.items.length} decision${result.items.length === 1 ? "" : "s"} in ad-hoc session ${sid}:`
|
|
1827
1845
|
);
|
|
1828
1846
|
for (const item of result.items) {
|
|
1829
|
-
console.log(`- ${item.decisionId}: ${item.input.title}${
|
|
1847
|
+
console.log(`- ${item.decisionId}: ${item.input.title}${recordedKindMarker(item.input.kind)}`);
|
|
1830
1848
|
}
|
|
1831
1849
|
}
|
|
1832
1850
|
function pickRichFields(options) {
|
|
@@ -1933,13 +1951,14 @@ function printDecisionResult(options, result) {
|
|
|
1933
1951
|
}
|
|
1934
1952
|
const trackPrefix = result.rich.kind === "track" ? "track " : "";
|
|
1935
1953
|
const rationaleSuffix = result.rich.rationale !== void 0 ? ` (rationale: ${result.rich.rationale})` : "";
|
|
1954
|
+
const vesselSuffix = result.rich.kind === "track" ? "" : " \u2014 a point-in-time decision, not an open track (use --track for a direction that should keep coming back until you close it)";
|
|
1936
1955
|
if (result.mode === "ad-hoc") {
|
|
1937
1956
|
console.log(
|
|
1938
|
-
`Recorded ${trackPrefix}${result.decisionId} in ad-hoc session ${sid}${rationaleSuffix}`
|
|
1957
|
+
`Recorded ${trackPrefix}${result.decisionId} in ad-hoc session ${sid}${rationaleSuffix}${vesselSuffix}`
|
|
1939
1958
|
);
|
|
1940
1959
|
} else {
|
|
1941
1960
|
console.log(
|
|
1942
|
-
`Recorded ${trackPrefix}${result.decisionId} in session ${sid} (${result.sessionStatus})${rationaleSuffix}`
|
|
1961
|
+
`Recorded ${trackPrefix}${result.decisionId} in session ${sid} (${result.sessionStatus})${rationaleSuffix}${vesselSuffix}`
|
|
1943
1962
|
);
|
|
1944
1963
|
}
|
|
1945
1964
|
}
|
|
@@ -12705,6 +12724,18 @@ var VIEW_HTML = `<!doctype html>
|
|
|
12705
12724
|
return el('span', { class: 'badge ok', text: 'up to date' });
|
|
12706
12725
|
}
|
|
12707
12726
|
|
|
12727
|
+
// Three states hide behind a bare 'in-flight 0': nothing ever recorded,
|
|
12728
|
+
// everything finished, and a task store that cannot be read -- where the
|
|
12729
|
+
// count is not zero but UNKNOWN. Say which one it is, as orient does.
|
|
12730
|
+
// Kept as a named top-level function with no free variables so the test suite
|
|
12731
|
+
// can lift it out of this template and run it; inlined in the card it was
|
|
12732
|
+
// unreachable from any test.
|
|
12733
|
+
function taskFlightLabel(w) {
|
|
12734
|
+
if (w.unreadableTaskCount > 0) return 'in-flight unknown (' + w.unreadableTaskCount + ' unreadable)';
|
|
12735
|
+
if (w.anyTaskEverRecorded === false) return 'no tasks recorded';
|
|
12736
|
+
return 'in-flight ' + w.inFlightCount;
|
|
12737
|
+
}
|
|
12738
|
+
|
|
12708
12739
|
function portfolioCard(w, generatedAt) {
|
|
12709
12740
|
if (!w.initialized) {
|
|
12710
12741
|
return el('div', { class: 'card pcard muted' }, [
|
|
@@ -12720,6 +12751,7 @@ var VIEW_HTML = `<!doctype html>
|
|
|
12720
12751
|
}
|
|
12721
12752
|
var pend = w.pendingApprovals || [];
|
|
12722
12753
|
var pendText = 'pending ' + pend.length + (pend.length ? ' (' + highestRisk(pend) + ')' : '');
|
|
12754
|
+
var flightText = taskFlightLabel(w);
|
|
12723
12755
|
var now = w.latestSession ? ((w.latestSession.label || '(session)') + ' [' + w.latestSession.status + ']') : '(no live sessions)';
|
|
12724
12756
|
var dec = w.latestDecision ? w.latestDecision.title : '(no decisions yet)';
|
|
12725
12757
|
var newest = (w.freshness && w.freshness.newestStartedAt) ? w.freshness.newestStartedAt : null;
|
|
@@ -12732,7 +12764,7 @@ var VIEW_HTML = `<!doctype html>
|
|
|
12732
12764
|
]),
|
|
12733
12765
|
el('div', { class: 'f', text: 'now: ' + now }),
|
|
12734
12766
|
el('div', { class: 'f', text: 'latest: ' + dec }),
|
|
12735
|
-
el('div', { class: 'f', text:
|
|
12767
|
+
el('div', { class: 'f', text: flightText + ' | ' + pendText + ' | suspect ' + w.suspectCount }),
|
|
12736
12768
|
el('div', { class: 'f muted', text: 'sessions ' + w.sessionCount + ' | newest ' + relAge(newest, generatedAt) })
|
|
12737
12769
|
]);
|
|
12738
12770
|
}
|
|
@@ -13309,6 +13341,13 @@ async function portfolioCard(ws, nowIso) {
|
|
|
13309
13341
|
sessionCount: s.sessionCount,
|
|
13310
13342
|
suspectCount: s.suspects.length,
|
|
13311
13343
|
inFlightCount: s.inFlightTasks.length,
|
|
13344
|
+
// Three states hide behind `inFlightCount: 0`: nothing ever recorded,
|
|
13345
|
+
// everything finished, and a store whose task files cannot be read (where
|
|
13346
|
+
// the count is not 0 but UNKNOWN). `orient` branches all three ways; the
|
|
13347
|
+
// card carries the two extra facts so it can too, instead of reporting a
|
|
13348
|
+
// loader blind spot as an empty record.
|
|
13349
|
+
anyTaskEverRecorded: s.anyTaskEverRecorded,
|
|
13350
|
+
unreadableTaskCount: s.unreadableTaskCount,
|
|
13312
13351
|
pendingApprovals: s.pendingApprovals.map((a) => ({
|
|
13313
13352
|
risk: a.risk,
|
|
13314
13353
|
kind: a.kind,
|
|
@@ -13776,7 +13815,7 @@ async function assertWorkspaceInitialized15(basouRoot) {
|
|
|
13776
13815
|
function readBuildStamp() {
|
|
13777
13816
|
if (false) return void 0;
|
|
13778
13817
|
try {
|
|
13779
|
-
return JSON.parse('{"version":"0.
|
|
13818
|
+
return JSON.parse('{"version":"0.48.0","commit":"2e8fee7","committedAt":"2026-09-21T11:35:12+09:00"}');
|
|
13780
13819
|
} catch {
|
|
13781
13820
|
return void 0;
|
|
13782
13821
|
}
|