@basou/cli 0.46.0 → 0.47.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/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,14 @@ Input format (a JSON array; one object per decision):
1259
1260
  }
1260
1261
  ]
1261
1262
 
1262
- Only "title" is required; every other field is optional. Set "kind": "track" to
1263
- record a strategic, UNFINISHED direction (+ why): orientation/handoff resurface
1264
- open tracks every session until you close one with 'basou decision void <id>'.
1265
- Absent / "decision" is a point-in-time decision (surfaced only as the latest).
1263
+ "title" is required. "kind" names the vessel: "track" records a strategic,
1264
+ UNFINISHED direction (+ why) and orientation/handoff resurface it every session
1265
+ until you close it with 'basou decision void <id>', while "decision" records a
1266
+ point-in-time call (surfaced only as the latest). Every other field is optional.
1267
+
1268
+ DEPRECATED: omitting "kind" still writes the item, as a decision, but warns --
1269
+ and becomes an ERROR in the next release. It has no default because getting it
1270
+ wrong fails silently: a track filed as a decision simply never comes back.
1266
1271
  All decisions are written into one ad-hoc session timestamped now, so
1267
1272
  orientation surfaces them as the latest decisions. Run from a workspace-view
1268
1273
  directory and it resolves to the planning repo, like 'basou orient' /
@@ -1270,7 +1275,7 @@ directory and it resolves to the planning repo, like 'basou orient' /
1270
1275
 
1271
1276
  Example (heredoc on stdin):
1272
1277
  basou decision capture <<'JSON'
1273
- [{ "title": "Ship the capture command", "rationale": "Close the why-capture gap" }]
1278
+ [{ "title": "Ship the capture command", "rationale": "Close the why-capture gap", "kind": "decision" }]
1274
1279
  JSON
1275
1280
  `;
1276
1281
  async function runDecisionRecord(options, ctx = {}) {
@@ -1306,12 +1311,11 @@ async function warnLinkedFilesOutsideRoots(input) {
1306
1311
  } catch {
1307
1312
  }
1308
1313
  }
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) {
1314
+ function warnMissingKind(decisions, indices) {
1315
+ for (const index of indices) {
1312
1316
  const title = (decisions[index]?.title ?? "").trim();
1313
1317
  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.`
1318
+ `basou: decision[${index}] ("${title.slice(0, 40)}") declares no "kind" \u2014 it is recorded as a point-in-time decision and will NOT resurface in orient. Set "kind": "track" for an unfinished direction, or "kind": "decision" to say it is settled. Omitting it becomes an ERROR in the next release.`
1315
1319
  );
1316
1320
  }
1317
1321
  }
@@ -1412,8 +1416,8 @@ async function doRunDecisionCapture(options, ctx) {
1412
1416
  const paths = basouPaths4(repositoryRoot);
1413
1417
  await assertWorkspaceInitialized2(paths.root);
1414
1418
  const raw = await readCaptureInput(options, ctx);
1415
- const { decisions, markerWithoutKind } = parseCaptureInput(raw);
1416
- warnTrackMarkerWithoutKind(decisions, markerWithoutKind);
1419
+ const { decisions, missingKind } = parseCaptureInput(raw);
1420
+ warnMissingKind(decisions, missingKind);
1417
1421
  await warnLinkedFilesOutsideRoots({
1418
1422
  linkedFiles: decisions.flatMap((d) => d.linked_files ?? []),
1419
1423
  cwd,
@@ -1675,13 +1679,13 @@ function parseCaptureInput(raw) {
1675
1679
  throw new Error("Input array must contain at least one decision.");
1676
1680
  }
1677
1681
  const decisions = [];
1678
- const markerWithoutKind = [];
1682
+ const missingKind = [];
1679
1683
  parsed.forEach((item, index) => {
1680
- const { input, kindWasPresent } = validateCaptureItem(item, index);
1684
+ const input = validateCaptureItem(item, index);
1681
1685
  decisions.push(input);
1682
- if (!kindWasPresent && TRACK_MARKER_IN_TITLE.test(input.title)) markerWithoutKind.push(index);
1686
+ if (input.kind === void 0) missingKind.push(index);
1683
1687
  });
1684
- return { decisions, markerWithoutKind };
1688
+ return { decisions, missingKind };
1685
1689
  }
1686
1690
  function validateCaptureItem(item, index) {
1687
1691
  if (typeof item !== "object" || item === null || Array.isArray(item)) {
@@ -1703,7 +1707,7 @@ function validateCaptureItem(item, index) {
1703
1707
  if (obj.kind !== "decision" && obj.kind !== "track") {
1704
1708
  throw new Error(`decision[${index}].kind must be "decision" or "track", got '${obj.kind}'.`);
1705
1709
  }
1706
- if (obj.kind === "track") out.kind = "track";
1710
+ out.kind = obj.kind;
1707
1711
  }
1708
1712
  if (obj.rationale !== void 0) {
1709
1713
  out.rationale = requireNonEmptyString(obj.rationale, index, "rationale");
@@ -1742,7 +1746,7 @@ function validateCaptureItem(item, index) {
1742
1746
  }
1743
1747
  });
1744
1748
  }
1745
- return { input: out, kindWasPresent: obj.kind !== void 0 };
1749
+ return out;
1746
1750
  }
1747
1751
  function requireNonEmptyString(value, index, field) {
1748
1752
  if (typeof value !== "string" || isBlank(value)) {
@@ -1793,8 +1797,13 @@ function captureItemToPayload(item) {
1793
1797
  if (item.input.kind !== void 0) payload.kind = item.input.kind;
1794
1798
  return payload;
1795
1799
  }
1796
- function kindMarker(kind) {
1797
- return kind === "track" ? " [TRACK]" : " [DECISION]";
1800
+ function previewKindMarker(kind) {
1801
+ if (kind === "track") return " [TRACK]";
1802
+ return kind === "decision" ? " [DECISION]" : " [NO KIND]";
1803
+ }
1804
+ function recordedKindMarker(kind) {
1805
+ if (kind === "track") return " [TRACK]";
1806
+ return kind === "decision" ? "" : " [NO KIND]";
1798
1807
  }
1799
1808
  function printCapturePreview(options, decisions) {
1800
1809
  if (options.json === true) {
@@ -1805,7 +1814,7 @@ function printCapturePreview(options, decisions) {
1805
1814
  `Would capture ${decisions.length} decision${decisions.length === 1 ? "" : "s"} (dry run; nothing written):`
1806
1815
  );
1807
1816
  for (const decision of decisions) {
1808
- console.log(`- ${decision.title}${kindMarker(decision.kind)}`);
1817
+ console.log(`- ${decision.title}${previewKindMarker(decision.kind)}`);
1809
1818
  }
1810
1819
  }
1811
1820
  function printCaptureResult(options, result) {
@@ -1826,7 +1835,7 @@ function printCaptureResult(options, result) {
1826
1835
  `Captured ${result.items.length} decision${result.items.length === 1 ? "" : "s"} in ad-hoc session ${sid}:`
1827
1836
  );
1828
1837
  for (const item of result.items) {
1829
- console.log(`- ${item.decisionId}: ${item.input.title}${kindMarker(item.input.kind)}`);
1838
+ console.log(`- ${item.decisionId}: ${item.input.title}${recordedKindMarker(item.input.kind)}`);
1830
1839
  }
1831
1840
  }
1832
1841
  function pickRichFields(options) {
@@ -12705,6 +12714,18 @@ var VIEW_HTML = `<!doctype html>
12705
12714
  return el('span', { class: 'badge ok', text: 'up to date' });
12706
12715
  }
12707
12716
 
12717
+ // Three states hide behind a bare 'in-flight 0': nothing ever recorded,
12718
+ // everything finished, and a task store that cannot be read -- where the
12719
+ // count is not zero but UNKNOWN. Say which one it is, as orient does.
12720
+ // Kept as a named top-level function with no free variables so the test suite
12721
+ // can lift it out of this template and run it; inlined in the card it was
12722
+ // unreachable from any test.
12723
+ function taskFlightLabel(w) {
12724
+ if (w.unreadableTaskCount > 0) return 'in-flight unknown (' + w.unreadableTaskCount + ' unreadable)';
12725
+ if (w.anyTaskEverRecorded === false) return 'no tasks recorded';
12726
+ return 'in-flight ' + w.inFlightCount;
12727
+ }
12728
+
12708
12729
  function portfolioCard(w, generatedAt) {
12709
12730
  if (!w.initialized) {
12710
12731
  return el('div', { class: 'card pcard muted' }, [
@@ -12720,6 +12741,7 @@ var VIEW_HTML = `<!doctype html>
12720
12741
  }
12721
12742
  var pend = w.pendingApprovals || [];
12722
12743
  var pendText = 'pending ' + pend.length + (pend.length ? ' (' + highestRisk(pend) + ')' : '');
12744
+ var flightText = taskFlightLabel(w);
12723
12745
  var now = w.latestSession ? ((w.latestSession.label || '(session)') + ' [' + w.latestSession.status + ']') : '(no live sessions)';
12724
12746
  var dec = w.latestDecision ? w.latestDecision.title : '(no decisions yet)';
12725
12747
  var newest = (w.freshness && w.freshness.newestStartedAt) ? w.freshness.newestStartedAt : null;
@@ -12732,7 +12754,7 @@ var VIEW_HTML = `<!doctype html>
12732
12754
  ]),
12733
12755
  el('div', { class: 'f', text: 'now: ' + now }),
12734
12756
  el('div', { class: 'f', text: 'latest: ' + dec }),
12735
- el('div', { class: 'f', text: 'in-flight ' + w.inFlightCount + ' | ' + pendText + ' | suspect ' + w.suspectCount }),
12757
+ el('div', { class: 'f', text: flightText + ' | ' + pendText + ' | suspect ' + w.suspectCount }),
12736
12758
  el('div', { class: 'f muted', text: 'sessions ' + w.sessionCount + ' | newest ' + relAge(newest, generatedAt) })
12737
12759
  ]);
12738
12760
  }
@@ -13309,6 +13331,13 @@ async function portfolioCard(ws, nowIso) {
13309
13331
  sessionCount: s.sessionCount,
13310
13332
  suspectCount: s.suspects.length,
13311
13333
  inFlightCount: s.inFlightTasks.length,
13334
+ // Three states hide behind `inFlightCount: 0`: nothing ever recorded,
13335
+ // everything finished, and a store whose task files cannot be read (where
13336
+ // the count is not 0 but UNKNOWN). `orient` branches all three ways; the
13337
+ // card carries the two extra facts so it can too, instead of reporting a
13338
+ // loader blind spot as an empty record.
13339
+ anyTaskEverRecorded: s.anyTaskEverRecorded,
13340
+ unreadableTaskCount: s.unreadableTaskCount,
13312
13341
  pendingApprovals: s.pendingApprovals.map((a) => ({
13313
13342
  risk: a.risk,
13314
13343
  kind: a.kind,
@@ -13776,7 +13805,7 @@ async function assertWorkspaceInitialized15(basouRoot) {
13776
13805
  function readBuildStamp() {
13777
13806
  if (false) return void 0;
13778
13807
  try {
13779
- return JSON.parse('{"version":"0.46.0","commit":"dbc9c57","committedAt":"2026-09-19T15:43:35+09:00"}');
13808
+ return JSON.parse('{"version":"0.47.0","commit":"831e0cb","committedAt":"2026-09-20T22:28:13+09:00"}');
13780
13809
  } catch {
13781
13810
  return void 0;
13782
13811
  }