@basou/core 0.32.0 → 0.33.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 CHANGED
@@ -1080,7 +1080,7 @@ function isLazyExpired(approval, now) {
1080
1080
  }
1081
1081
 
1082
1082
  // src/decisions/decisions-renderer.ts
1083
- import { lstat } from "fs/promises";
1083
+ import { lstat as lstat2 } from "fs/promises";
1084
1084
  import { dirname as dirname2, join as join6, resolve } from "path";
1085
1085
 
1086
1086
  // src/events/event-replay.ts
@@ -1367,6 +1367,411 @@ async function readAllEvents(sessionDir, options = {}) {
1367
1367
  return out;
1368
1368
  }
1369
1369
 
1370
+ // src/project/relative-path.ts
1371
+ function normalizeRelativePath(p) {
1372
+ const trimmed = p.trim();
1373
+ const absolute = trimmed.startsWith("/");
1374
+ const out = [];
1375
+ for (const seg of trimmed.split("/")) {
1376
+ if (seg === "" || seg === ".") continue;
1377
+ if (seg === "..") {
1378
+ const top = out[out.length - 1];
1379
+ if (top !== void 0 && top !== "..") {
1380
+ out.pop();
1381
+ } else if (!absolute) {
1382
+ out.push("..");
1383
+ }
1384
+ continue;
1385
+ }
1386
+ out.push(seg);
1387
+ }
1388
+ const joined = out.join("/");
1389
+ if (absolute) return `/${joined}`;
1390
+ return joined.length === 0 ? "." : joined;
1391
+ }
1392
+
1393
+ // src/storage/manifest.ts
1394
+ import { lstat } from "fs/promises";
1395
+
1396
+ // src/schemas/manifest.schema.ts
1397
+ import { z as z4 } from "zod";
1398
+ var ProjectSchema = z4.looseObject({
1399
+ name: z4.string().optional(),
1400
+ description: z4.string().optional()
1401
+ });
1402
+ var CapabilitiesSchema = z4.looseObject({
1403
+ enabled: z4.array(z4.string())
1404
+ });
1405
+ var ApprovalConfigSchema = z4.looseObject({
1406
+ required_for: z4.array(z4.string()).optional(),
1407
+ default_risk_level: z4.enum(["low", "medium", "high", "critical"])
1408
+ });
1409
+ var ClaudeCodeAdapterConfigSchema = z4.looseObject({
1410
+ enabled: z4.boolean(),
1411
+ config_path: z4.string().optional()
1412
+ });
1413
+ var AdaptersSchema = z4.looseObject({
1414
+ "claude-code": ClaudeCodeAdapterConfigSchema
1415
+ });
1416
+ var GitConfigSchema = z4.looseObject({
1417
+ events_log: z4.enum(["ignore", "commit"]).default("ignore")
1418
+ });
1419
+ var SOURCE_ROOT_PATTERN = /^(?![~/\\])(?![A-Za-z]:)(?!\s)[^\0\\]*[^\0\\\s]$/;
1420
+ var SourceRootSchema = z4.string().min(1).regex(SOURCE_ROOT_PATTERN, {
1421
+ message: "source_roots entries must be relative paths (no absolute path, '~', '\\', or null byte)"
1422
+ });
1423
+ var ImportConfigSchema = z4.looseObject({
1424
+ source_roots: z4.array(SourceRootSchema).min(1).optional()
1425
+ });
1426
+ var RepoVisibilitySchema = z4.enum(["public", "private", "future-public"]);
1427
+ var RepoLanguageSchema = z4.enum(["en", "ja", "en+ja"]);
1428
+ var PublishKindSchema = z4.enum(["web", "npm"]);
1429
+ var RepoInstructionsSchema = z4.enum(["hub", "self"]);
1430
+ var PublishTargetSchema = z4.looseObject({
1431
+ kind: PublishKindSchema,
1432
+ visibility: RepoVisibilitySchema.optional(),
1433
+ language: RepoLanguageSchema.optional()
1434
+ });
1435
+ var RepoEntrySchema = z4.looseObject({
1436
+ path: SourceRootSchema,
1437
+ visibility: RepoVisibilitySchema.optional(),
1438
+ language: RepoLanguageSchema.optional(),
1439
+ publishes: z4.array(PublishTargetSchema).optional(),
1440
+ instructions: RepoInstructionsSchema.optional()
1441
+ });
1442
+ var WorkspaceMetaSchema = z4.looseObject({
1443
+ id: WorkspaceIdSchema,
1444
+ name: z4.string().min(1),
1445
+ created_at: IsoTimestampSchema,
1446
+ updated_at: IsoTimestampSchema,
1447
+ /**
1448
+ * The generated workspace view: a throwaway directory that aggregates the
1449
+ * roster repos via symlinks (one `<repo-basename>` symlink per repo). A path
1450
+ * relative to the manifest root, reusing the machine-portable source-root
1451
+ * constraint. Absent for a solo project (no view needed); `basou project
1452
+ * workspace` reconciles the view's symlinks to the declared roster.
1453
+ */
1454
+ view: SourceRootSchema.optional()
1455
+ });
1456
+ var ManifestSchema = z4.looseObject({
1457
+ schema_version: SchemaVersionSchema,
1458
+ // Same forward-compatible format gate as schema_version (accept 0.x.y, gate a
1459
+ // higher major with an upgrade error) rather than a hard literal. `basou_version`
1460
+ // is a format stamp, not the npm/product version; consolidating it with
1461
+ // schema_version is a candidate cleanup for the M4 freeze pass.
1462
+ basou_version: SchemaVersionSchema,
1463
+ workspace: WorkspaceMetaSchema,
1464
+ project: ProjectSchema,
1465
+ capabilities: CapabilitiesSchema,
1466
+ approval: ApprovalConfigSchema,
1467
+ adapters: AdaptersSchema,
1468
+ git: GitConfigSchema,
1469
+ import: ImportConfigSchema.optional(),
1470
+ repos: z4.array(RepoEntrySchema).min(1).optional()
1471
+ });
1472
+ var KNOWN_TOP_LEVEL_KEYS = new Set(Object.keys(ManifestSchema.shape));
1473
+ function unknownManifestKeys(manifest) {
1474
+ return Object.keys(manifest).filter((k) => !KNOWN_TOP_LEVEL_KEYS.has(k)).sort();
1475
+ }
1476
+
1477
+ // src/storage/manifest.ts
1478
+ function createManifest(input) {
1479
+ if (input.workspaceName.length === 0) {
1480
+ throw new Error("Workspace name is empty. Pass --name explicitly.");
1481
+ }
1482
+ const now = (input.now ?? /* @__PURE__ */ new Date()).toISOString();
1483
+ const workspaceId = input.workspaceId ?? prefixedUlid("ws");
1484
+ const project = {
1485
+ ...input.projectName !== void 0 ? { name: input.projectName } : {},
1486
+ ...input.projectDescription !== void 0 ? { description: input.projectDescription } : {}
1487
+ };
1488
+ const manifest = {
1489
+ schema_version: "0.1.0",
1490
+ basou_version: "0.1.0",
1491
+ workspace: {
1492
+ id: workspaceId,
1493
+ name: input.workspaceName,
1494
+ created_at: now,
1495
+ updated_at: now
1496
+ },
1497
+ project,
1498
+ capabilities: {
1499
+ enabled: ["core", "claude-code-adapter", "terminal-recording", "git-capability", "approval"]
1500
+ },
1501
+ approval: {
1502
+ required_for: ["destructive_command", "external_send"],
1503
+ default_risk_level: "medium"
1504
+ },
1505
+ adapters: {
1506
+ "claude-code": { enabled: true }
1507
+ },
1508
+ git: { events_log: "ignore" },
1509
+ ...input.sourceRoots !== void 0 && input.sourceRoots.length > 0 ? { import: { source_roots: input.sourceRoots } } : {}
1510
+ };
1511
+ return ManifestSchema.parse(manifest);
1512
+ }
1513
+ async function writeManifest(paths, manifest, options) {
1514
+ const force = options?.force === true;
1515
+ const validated = ManifestSchema.parse(manifest);
1516
+ delete validated.project.repository_url;
1517
+ if (!force) {
1518
+ let existed = false;
1519
+ try {
1520
+ await lstat(paths.files.manifest);
1521
+ existed = true;
1522
+ } catch (error) {
1523
+ if (!hasErrorCode2(error) || error.code !== "ENOENT") {
1524
+ throw new Error("Failed to inspect existing manifest", { cause: error });
1525
+ }
1526
+ }
1527
+ if (existed) {
1528
+ throw new Error("Already initialized. Use --force to overwrite.");
1529
+ }
1530
+ }
1531
+ await writeYamlFile(paths.files.manifest, validated);
1532
+ }
1533
+ async function readManifest(paths) {
1534
+ const raw = await readYamlFile(paths.files.manifest);
1535
+ return ManifestSchema.parse(raw);
1536
+ }
1537
+ function hasErrorCode2(error) {
1538
+ if (!(error instanceof Error)) return false;
1539
+ return typeof error.code === "string";
1540
+ }
1541
+
1542
+ // src/lib/view-strings.ts
1543
+ function resolveViewLanguage(manifest) {
1544
+ if (manifest === null) return "en";
1545
+ const anchor = manifest.repos?.find((r) => normalizeRelativePath(r.path) === ".");
1546
+ return anchor?.language === "ja" ? "ja" : "en";
1547
+ }
1548
+ async function resolveViewLanguageFromPaths(paths) {
1549
+ try {
1550
+ return resolveViewLanguage(await readManifest(paths));
1551
+ } catch {
1552
+ return "en";
1553
+ }
1554
+ }
1555
+ function viewStrings(language) {
1556
+ return language === "ja" ? JA : EN;
1557
+ }
1558
+ function relativeAgeEn(startedAt, now) {
1559
+ if (startedAt === null) return "(unknown)";
1560
+ const ms = now.getTime() - Date.parse(startedAt);
1561
+ if (!Number.isFinite(ms) || ms < 0) return "just now";
1562
+ if (ms < 6e4) return "just now";
1563
+ const totalMin = Math.floor(ms / 6e4);
1564
+ const days = Math.floor(totalMin / 1440);
1565
+ const hours = Math.floor(totalMin % 1440 / 60);
1566
+ const mins = totalMin % 60;
1567
+ if (days > 0) return hours > 0 ? `${days}d ${hours}h ago` : `${days}d ago`;
1568
+ if (hours > 0) return mins > 0 ? `${hours}h ${mins}m ago` : `${hours}h ago`;
1569
+ return `${mins}m ago`;
1570
+ }
1571
+ function relativeAgeJa(startedAt, now) {
1572
+ if (startedAt === null) return "(\u4E0D\u660E)";
1573
+ const ms = now.getTime() - Date.parse(startedAt);
1574
+ if (!Number.isFinite(ms) || ms < 0) return "\u305F\u3063\u305F\u4ECA";
1575
+ if (ms < 6e4) return "\u305F\u3063\u305F\u4ECA";
1576
+ const totalMin = Math.floor(ms / 6e4);
1577
+ const days = Math.floor(totalMin / 1440);
1578
+ const hours = Math.floor(totalMin % 1440 / 60);
1579
+ const mins = totalMin % 60;
1580
+ if (days > 0) return hours > 0 ? `${days}\u65E5${hours}\u6642\u9593\u524D` : `${days}\u65E5\u524D`;
1581
+ if (hours > 0) return mins > 0 ? `${hours}\u6642\u9593${mins}\u5206\u524D` : `${hours}\u6642\u9593\u524D`;
1582
+ return `${mins}\u5206\u524D`;
1583
+ }
1584
+ var EN = {
1585
+ relativeAge: relativeAgeEn,
1586
+ common: {
1587
+ lastSessionLabel: "Last session",
1588
+ latestDecisionLabel: "Latest decision",
1589
+ recentFilesLabel: "Recently changed files",
1590
+ trackWhyLabel: "Why",
1591
+ decisionOtherSessionNote: (sid) => `Note: this decision comes from a different session [${sid}] than the last session.`
1592
+ },
1593
+ orientation: {
1594
+ headingWhere: "## Where you are now",
1595
+ headingRecent: (n) => `## Recent direction (last ${n} sessions)`,
1596
+ headingInFlight: "## What is in flight",
1597
+ headingForward: "## Where you are heading",
1598
+ headingCurrency: "## Is this current",
1599
+ inFlightTasksHeading: (n) => `### In-flight tasks (${n})`,
1600
+ pendingApprovalsHeading: (n) => `### Pending approvals (${n})`,
1601
+ suspectSessionsHeading: (n) => `### Suspect sessions (${n})`,
1602
+ openTracksHeading: (n) => `### Open tracks (shown until closed) (${n})`,
1603
+ decisionStaleNote: (age) => `Note: this is the latest *recorded* decision. The latest activity (${age}) is more recent, so the current direction may not be reflected here (conversational decisions are not captured automatically; record this session's decisions with \`basou decision capture\`).`,
1604
+ outOfRootWarning: (count, files) => `\u26A0 ${count} outside source_roots (possibly another project): ${files}`,
1605
+ recentEmpty: "(no records yet)",
1606
+ recentDecisionsLabel: "Decisions",
1607
+ recentNextStepLabel: "Next step",
1608
+ recentChangedLabel: "Changed",
1609
+ trackCloseInstruction: "When finished, close it with `basou decision void <decision_id>`. It stays listed here every time until closed.",
1610
+ nextStepRecordedLabel: (age) => `Next step (recorded, ${age})`,
1611
+ noteStaleNote: (age) => `Note: work continued after this was recorded (latest activity ${age}), so this starting point may be stale.`,
1612
+ fallbackStaleDirection: "- (no planned tasks or recorded next step \u2014 the latest activity postdates the latest decision; ask the user for the continuation point)",
1613
+ fallbackStaleReferenceLabel: "Reference (possibly stale \u2014 not the current direction)",
1614
+ trackNudge: 'Once the next essential direction is settled, record it as a track with `basou decision capture` (`"kind":"track"`) / `basou decision record --track` \u2014 it stays surfaced here every session until closed.',
1615
+ federatedFreshnessNote: "Note: the freshness verdict covers only this machine's local store. Missed work on other hosts cannot be assessed here (run `basou refresh` on each host to sync).",
1616
+ bannerUnverifiable: (n) => `> \u26A0\uFE0F **Re-import needed** \u2014 ${n} session(s) changed in the native logs but cannot be imported by a plain refresh. Re-import with \`basou refresh --force\` (details under "Is this current" at the bottom).`,
1617
+ bannerStale: (parts) => `> \u26A0\uFE0F **Stale (uncaptured: ${parts})** \u2014 run \`basou refresh\` before starting work (details under "Is this current" at the bottom).`,
1618
+ partNew: (n) => `${n} new`,
1619
+ partUpdated: (n) => `${n} updated`,
1620
+ partsJoiner: ", ",
1621
+ verdictUnverifiable: (n) => [
1622
+ `\u26A0\uFE0F The native logs changed, but ${n} session(s) cannot be safely re-imported by a plain \`basou refresh\` (non-append changes, prior-chain mismatch, etc.).`,
1623
+ "Re-import with `basou refresh --force`. (`basou verify` is a different check \u2014 it inspects already-imported data for tampering/corruption, a separate axis from the suspect count in the header. A clean verify can still leave uncaptured work.)"
1624
+ ],
1625
+ verdictStale: (parts) => [
1626
+ `\u26A0\uFE0F Stale. There is uncaptured work since the last import (${parts}).`,
1627
+ "Run `basou refresh` before starting work."
1628
+ ],
1629
+ verdictUpdatedOnly: (n) => [
1630
+ `\u26A0\uFE0F ${n} session(s) have been updated. \`basou refresh\` can import them.`,
1631
+ "(A session still in progress keeps growing after each import, so it will keep appearing here \u2014 that is normal.)"
1632
+ ],
1633
+ verdictSuspectsAlso: (n) => `There are also ${n} suspect session(s) (see "Suspect sessions" above).`,
1634
+ verdictEmpty: [
1635
+ "\u2139\uFE0F No records yet.",
1636
+ "Work in this workspace and your current position will appear here."
1637
+ ],
1638
+ verdictUnprobed: (rel, tool) => [
1639
+ `\u2139\uFE0F Showing the last imported state. Last work: ${rel} (${tool}).`,
1640
+ "Run `basou refresh` to confirm this is current."
1641
+ ],
1642
+ verdictCurrent: (rel, tool, hasHosts) => hasHosts ? `\u2705 The capture on this host (local) is current. Last work: ${rel} (${tool}). No uncaptured native sessions.` : `\u2705 The capture is current. Last work: ${rel} (${tool}). No uncaptured native sessions.`,
1643
+ verdictSuspectsCaveat: (n) => `However, ${n} suspect session(s) need attention (see "Suspect sessions" above).`,
1644
+ verdictScopeDisclaimer: "Note: this verdict only checks whether captured native sessions are current and whether any are suspect. It does not detect planning-implementation drift or unrecorded decisions.",
1645
+ toolTerminal: "terminal",
1646
+ toolHuman: "manual note",
1647
+ toolImport: "another workspace",
1648
+ toolUnknown: "unknown"
1649
+ },
1650
+ handoff: {
1651
+ headingCurrentState: "## Current state",
1652
+ headingRecentFiles: "## Recently changed files",
1653
+ headingLatestDecision: "## Latest decision",
1654
+ headingOpenTracks: "## Open tracks (shown until closed)",
1655
+ headingUnresolved: "## Unresolved items",
1656
+ headingReadNext: "## Files to read next",
1657
+ headingNextWork: "## Work to do next",
1658
+ headingSessions: "## Sessions",
1659
+ lastTaskLabel: "Last task",
1660
+ decisionStaleNote: "Note: the latest activity postdates this decision. It may already be resolved in conversation \u2014 confirm the continuation point before resuming (conversational decisions are not captured automatically; record them with `basou decision capture`).",
1661
+ trackCloseInstruction: "When finished, close it with `basou decision void <decision_id>`."
1662
+ },
1663
+ decisions: {
1664
+ dateLabel: "date",
1665
+ trackKindLine: "- kind: track (stays in orient/handoff until closed)",
1666
+ decisionLabel: "decision"
1667
+ },
1668
+ report: {
1669
+ headingSummary: "## Summary",
1670
+ headingVolume: "## Work volume",
1671
+ headingDecisions: "## Decisions",
1672
+ headingApprovals: "## Approvals",
1673
+ headingTasks: "## Tasks",
1674
+ headingChangedFiles: "## Changed files",
1675
+ headingSessions: "## Sessions",
1676
+ headingIntegrity: "## Integrity"
1677
+ }
1678
+ };
1679
+ var JA = {
1680
+ relativeAge: relativeAgeJa,
1681
+ common: {
1682
+ lastSessionLabel: "\u6700\u7D42 session",
1683
+ latestDecisionLabel: "\u76F4\u8FD1\u306E\u5224\u65AD",
1684
+ recentFilesLabel: "\u76F4\u8FD1\u306E\u5909\u66F4\u30D5\u30A1\u30A4\u30EB",
1685
+ trackWhyLabel: "\u7406\u7531",
1686
+ decisionOtherSessionNote: (sid) => `\u6CE8: \u3053\u306E\u5224\u65AD\u306F\u6700\u7D42 session \u3068\u306F\u5225\u306E session [${sid}] \u306E\u3082\u306E\u3067\u3059\u3002`
1687
+ },
1688
+ orientation: {
1689
+ headingWhere: "## \u4ECA\u3069\u3053\u306B\u3044\u308B",
1690
+ headingRecent: (n) => `## \u6700\u8FD1\u306E\u6D41\u308C (\u76F4\u8FD1 ${n} session)`,
1691
+ headingInFlight: "## \u4F55\u304C\u52D5\u304F",
1692
+ headingForward: "## \u3069\u3053\u3078\u5411\u304B\u3046",
1693
+ headingCurrency: "## \u3053\u308C\u306F\u6700\u65B0\u304B",
1694
+ inFlightTasksHeading: (n) => `### \u9032\u884C\u4E2D task (${n})`,
1695
+ pendingApprovalsHeading: (n) => `### \u627F\u8A8D\u5F85\u3061 (${n})`,
1696
+ suspectSessionsHeading: (n) => `### \u8981\u6CE8\u610F session (${n})`,
1697
+ openTracksHeading: (n) => `### \u672A\u5B8C\u30C8\u30E9\u30C3\u30AF (close \u307E\u3067\u7D99\u7D9A\u8868\u793A) (${n})`,
1698
+ decisionStaleNote: (age) => `\u6CE8: \u3053\u308C\u306F\u6700\u5F8C\u306B\u300C\u8A18\u9332\u3055\u308C\u305F\u300D\u5224\u65AD\u3067\u3059\u3002\u6700\u7D42\u6D3B\u52D5 (${age}) \u306F\u3053\u308C\u3088\u308A\u5F8C\u306E\u305F\u3081\u3001\u73FE\u5728\u306E\u65B9\u91DD\u304C\u53CD\u6620\u3055\u308C\u3066\u3044\u306A\u3044\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059(\u4F1A\u8A71\u3067\u306E\u610F\u601D\u6C7A\u5B9A\u306F\u81EA\u52D5\u8A18\u9332\u3055\u308C\u307E\u305B\u3093\u3002\`basou decision capture\` \u3067\u3053\u306E session \u306E\u5224\u65AD\u3092\u8A18\u9332\u3067\u304D\u307E\u3059)\u3002`,
1699
+ outOfRootWarning: (count, files) => `\u26A0 source_roots \u5916 ${count} \u4EF6 (\u5225\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u53EF\u80FD\u6027): ${files}`,
1700
+ recentEmpty: "(\u307E\u3060\u8A18\u9332\u304C\u3042\u308A\u307E\u305B\u3093)",
1701
+ recentDecisionsLabel: "\u5224\u65AD",
1702
+ recentNextStepLabel: "\u6B21\u306E\u8D77\u70B9",
1703
+ recentChangedLabel: "\u5909\u66F4",
1704
+ trackCloseInstruction: "\u5B8C\u4E86\u3057\u305F\u3089 `basou decision void <decision_id>` \u3067\u9589\u3058\u3066\u304F\u3060\u3055\u3044\u3002\u9589\u3058\u308B\u307E\u3067\u6BCE\u56DE\u3053\u3053\u306B\u8868\u793A\u3055\u308C\u307E\u3059\u3002",
1705
+ nextStepRecordedLabel: (age) => `\u6B21\u306E\u8D77\u70B9 (\u8A18\u9332\u6E08\u307F, ${age})`,
1706
+ noteStaleNote: (age) => `\u6CE8: \u3053\u306E\u8D77\u70B9\u306E\u8A18\u9332\u5F8C (\u6700\u7D42\u6D3B\u52D5 ${age}) \u3082\u4F5C\u696D\u304C\u7D9A\u3044\u3066\u3044\u307E\u3059\u3002\u518D\u958B\u70B9\u304C\u53E4\u3044\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002`,
1707
+ fallbackStaleDirection: "- (no planned tasks or recorded next step \u2014 \u6700\u7D42\u6D3B\u52D5\u306F\u76F4\u8FD1\u306E\u5224\u65AD\u3088\u308A\u5F8C\u3067\u3059\u3002\u7D99\u7D9A\u70B9\u3092\u30E6\u30FC\u30B6\u306B\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044)",
1708
+ fallbackStaleReferenceLabel: "\u53C2\u8003 (\u53E4\u3044\u53EF\u80FD\u6027\u30FB\u65B9\u91DD\u3067\u306F\u306A\u3044)",
1709
+ trackNudge: '\u6B21\u306B\u4F5C\u308B\u3079\u304D\u672C\u8CEA\u7684\u306A\u65B9\u5411\u6027\u304C\u5B9A\u307E\u3063\u305F\u3089 `basou decision capture` (`"kind":"track"`) / `basou decision record --track` \u3067 track \u5316\u3059\u308B\u3068\u3001close \u307E\u3067\u6BCE session \u3053\u3053\u306B\u7D99\u7D9A\u8868\u793A\u3055\u308C\u307E\u3059\u3002',
1710
+ federatedFreshnessNote: "\u6CE8: \u9BAE\u5EA6\u5224\u5B9A\u306F\u3053\u306E\u30DE\u30B7\u30F3\u306E\u30ED\u30FC\u30AB\u30EB\u30B9\u30C8\u30A2\u306E\u307F\u304C\u5BFE\u8C61\u3067\u3059\u3002\u4ED6\u30DB\u30B9\u30C8\u306E\u53D6\u308A\u3053\u307C\u3057\u306F\u5224\u5B9A\u3067\u304D\u307E\u305B\u3093(\u5404\u30DB\u30B9\u30C8\u3067 basou refresh \u3092\u5B9F\u884C\u3057\u540C\u671F\u3057\u3066\u304F\u3060\u3055\u3044)\u3002",
1711
+ bannerUnverifiable: (n) => `> \u26A0\uFE0F **\u518D\u53D6\u308A\u8FBC\u307F\u304C\u5FC5\u8981** \u2014 native \u30ED\u30B0\u304C\u5909\u5316\u3057\u305F\u304C\u901A\u5E38\u306E refresh \u3067\u306F\u53D6\u308A\u8FBC\u3081\u306A\u3044\u30BB\u30C3\u30B7\u30E7\u30F3\u304C ${n} \u4EF6\u3042\u308A\u307E\u3059\u3002\`basou refresh --force\` \u3067\u518D\u53D6\u308A\u8FBC\u307F\u3057\u3066\u304F\u3060\u3055\u3044(\u8A73\u7D30\u306F\u672B\u5C3E\u300C\u3053\u308C\u306F\u6700\u65B0\u304B\u300D)\u3002`,
1712
+ bannerStale: (parts) => `> \u26A0\uFE0F **\u53E4\u3044\u3067\u3059\uFF08\u672A\u53D6\u308A\u8FBC\u307F ${parts}\uFF09** \u2014 \u7740\u624B\u524D\u306B\u5FC5\u305A \`basou refresh\` \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044(\u8A73\u7D30\u306F\u672B\u5C3E\u300C\u3053\u308C\u306F\u6700\u65B0\u304B\u300D)\u3002`,
1713
+ partNew: (n) => `\u65B0\u898F ${n} \u4EF6`,
1714
+ partUpdated: (n) => `\u66F4\u65B0 ${n} \u4EF6`,
1715
+ partsJoiner: "\u30FB",
1716
+ verdictUnverifiable: (n) => [
1717
+ `\u26A0\uFE0F native \u30ED\u30B0\u304C\u5909\u5316\u3057\u307E\u3057\u305F\u304C\u3001\u901A\u5E38\u306E \`basou refresh\` \u3067\u306F\u5B89\u5168\u306B\u518D\u53D6\u308A\u8FBC\u307F\u3067\u304D\u306A\u3044\u30BB\u30C3\u30B7\u30E7\u30F3\u304C ${n} \u4EF6\u3042\u308A\u307E\u3059(\u975E\u8FFD\u8A18\u5909\u66F4\u30FB\u524D\u30C1\u30A7\u30FC\u30F3\u4E0D\u6574\u5408\u306A\u3069)\u3002`,
1718
+ "`basou refresh --force` \u3067\u518D\u53D6\u308A\u8FBC\u307F\u3057\u3066\u304F\u3060\u3055\u3044\u3002(`basou verify` \u306F\u5225\u7269=\u53D6\u308A\u8FBC\u307F\u6E08\u307F\u30C7\u30FC\u30BF\u306E\u6539\u7AC4/\u7834\u640D\u691C\u67FB\u3067\u3001\u30D8\u30C3\u30C0\u306E suspect \u3068\u306F\u5225\u8EF8\u3067\u3059\u3002verify \u304C clean \u3067\u3082\u672A\u53D6\u308A\u8FBC\u307F\u306F\u6B8B\u308A\u5F97\u307E\u3059\u3002)"
1719
+ ],
1720
+ verdictStale: (parts) => [
1721
+ `\u26A0\uFE0F \u53E4\u3044\u3067\u3059\u3002\u6700\u5F8C\u306E\u53D6\u308A\u8FBC\u307F\u4EE5\u964D\u306B\u672A\u53D6\u308A\u8FBC\u307F\u306E\u4F5C\u696D\u304C\u3042\u308A\u307E\u3059(${parts})\u3002`,
1722
+ "\u7740\u624B\u524D\u306B\u5FC5\u305A `basou refresh` \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
1723
+ ],
1724
+ verdictUpdatedOnly: (n) => [
1725
+ `\u26A0\uFE0F \u66F4\u65B0\u3055\u308C\u305F\u30BB\u30C3\u30B7\u30E7\u30F3\u304C ${n} \u4EF6\u3042\u308A\u307E\u3059\u3002\`basou refresh\` \u3067\u53D6\u308A\u8FBC\u3081\u307E\u3059\u3002`,
1726
+ "(\u9032\u884C\u4E2D\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u304C\u3042\u308B\u5834\u5408\u3001\u305D\u308C\u81EA\u8EAB\u306F\u53D6\u308A\u8FBC\u307F\u5F8C\u3082\u5897\u3048\u7D9A\u3051\u308B\u305F\u3081\u6B8B\u308A\u307E\u3059\uFF1D\u6B63\u5E38\u3067\u3059\u3002)"
1727
+ ],
1728
+ verdictSuspectsAlso: (n) => `\u307E\u305F\u8981\u6CE8\u610F\u30BB\u30C3\u30B7\u30E7\u30F3\u304C ${n} \u4EF6\u3042\u308A\u307E\u3059(\u4E0A\u8A18\u300C\u8981\u6CE8\u610F session\u300D\u53C2\u7167)\u3002`,
1729
+ verdictEmpty: [
1730
+ "\u2139\uFE0F \u307E\u3060\u8A18\u9332\u304C\u3042\u308A\u307E\u305B\u3093\u3002",
1731
+ "\u3053\u306E\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u3067\u4F5C\u696D\u3059\u308B\u3068\u3001\u3053\u3053\u306B\u73FE\u5728\u5730\u304C\u8868\u793A\u3055\u308C\u307E\u3059\u3002"
1732
+ ],
1733
+ verdictUnprobed: (rel, tool) => [
1734
+ `\u2139\uFE0F \u53D6\u308A\u8FBC\u307F\u6E08\u307F\u306E\u72B6\u614B\u3092\u8868\u793A\u3057\u3066\u3044\u307E\u3059\u3002\u6700\u5F8C\u306E\u4F5C\u696D\u306F ${rel}(${tool})\u3002`,
1735
+ "\u6700\u65B0\u304B\u78BA\u8A8D\u3059\u308B\u306B\u306F `basou refresh` \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
1736
+ ],
1737
+ verdictCurrent: (rel, tool, hasHosts) => `\u2705 ${hasHosts ? "\u3053\u306E\u30DB\u30B9\u30C8(\u30ED\u30FC\u30AB\u30EB)\u306E" : ""}\u53D6\u308A\u8FBC\u307F\u306F\u6700\u65B0\u3067\u3059\u3002\u6700\u5F8C\u306E\u4F5C\u696D\u306F ${rel}(${tool})\u3002\u672A\u53D6\u308A\u8FBC\u307F\u306E native \u30BB\u30C3\u30B7\u30E7\u30F3\u306F\u3042\u308A\u307E\u305B\u3093\u3002`,
1738
+ verdictSuspectsCaveat: (n) => `\u305F\u3060\u3057\u8981\u6CE8\u610F\u30BB\u30C3\u30B7\u30E7\u30F3\u304C ${n} \u4EF6\u3042\u308A\u307E\u3059(\u4E0A\u8A18\u300C\u8981\u6CE8\u610F session\u300D\u53C2\u7167)\u3002`,
1739
+ verdictScopeDisclaimer: "\u6CE8: \u3053\u306E\u5224\u5B9A\u306F\u53D6\u308A\u8FBC\u307F\u6E08\u307F native \u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u9BAE\u5EA6\u3068 suspect \u306E\u6709\u7121\u3060\u3051\u3092\u898B\u307E\u3059\u3002\u8A08\u753B\u2194\u5B9F\u88C5\u306E\u30C9\u30EA\u30D5\u30C8\u3084\u672A\u8A18\u9332\u306E\u610F\u601D\u6C7A\u5B9A\u307E\u3067\u306F\u691C\u77E5\u3057\u307E\u305B\u3093\u3002",
1740
+ toolTerminal: "\u30BF\u30FC\u30DF\u30CA\u30EB",
1741
+ toolHuman: "\u624B\u52D5\u30E1\u30E2",
1742
+ toolImport: "\u4ED6\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9",
1743
+ toolUnknown: "\u4E0D\u660E"
1744
+ },
1745
+ handoff: {
1746
+ headingCurrentState: "## \u73FE\u5728\u306E\u72B6\u614B",
1747
+ headingRecentFiles: "## \u76F4\u8FD1\u306E\u5909\u66F4\u30D5\u30A1\u30A4\u30EB",
1748
+ headingLatestDecision: "## \u76F4\u8FD1\u306E\u5224\u65AD",
1749
+ headingOpenTracks: "## \u672A\u5B8C\u30C8\u30E9\u30C3\u30AF (close \u307E\u3067\u7D99\u7D9A\u8868\u793A)",
1750
+ headingUnresolved: "## \u672A\u6C7A\u4E8B\u9805",
1751
+ headingReadNext: "## \u6B21\u306B\u8AAD\u3080\u3079\u304D\u30D5\u30A1\u30A4\u30EB",
1752
+ headingNextWork: "## \u6B21\u306B\u5B9F\u884C\u3059\u3079\u304D\u4F5C\u696D",
1753
+ headingSessions: "## \u30BB\u30C3\u30B7\u30E7\u30F3\u4E00\u89A7",
1754
+ lastTaskLabel: "\u6700\u7D42 task",
1755
+ decisionStaleNote: "\u6CE8: \u6700\u7D42\u6D3B\u52D5\u306F\u3053\u306E\u5224\u65AD\u3088\u308A\u5F8C\u3067\u3059\u3002\u4F1A\u8A71\u3067\u65E2\u306B\u89E3\u6C7A\u6E08\u307F\u306E\u53EF\u80FD\u6027\u304C\u3042\u308B\u305F\u3081\u3001\u518D\u958B\u524D\u306B\u7D99\u7D9A\u70B9\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044(\u4F1A\u8A71\u3067\u306E\u610F\u601D\u6C7A\u5B9A\u306F\u81EA\u52D5\u8A18\u9332\u3055\u308C\u307E\u305B\u3093\u3002`basou decision capture` \u3067\u8A18\u9332\u3067\u304D\u307E\u3059)\u3002",
1756
+ trackCloseInstruction: "\u5B8C\u4E86\u3057\u305F\u3089 `basou decision void <decision_id>` \u3067\u9589\u3058\u3066\u304F\u3060\u3055\u3044\u3002"
1757
+ },
1758
+ decisions: {
1759
+ dateLabel: "\u6C7A\u5B9A\u65E5",
1760
+ trackKindLine: "- \u7A2E\u5225: track (close \u307E\u3067 orient/handoff \u306B\u7D99\u7D9A\u8868\u793A)",
1761
+ decisionLabel: "\u5224\u65AD"
1762
+ },
1763
+ report: {
1764
+ headingSummary: "## \u6982\u8981",
1765
+ headingVolume: "## \u4F5C\u696D\u91CF",
1766
+ headingDecisions: "## \u5224\u65AD",
1767
+ headingApprovals: "## \u627F\u8A8D",
1768
+ headingTasks: "## \u30BF\u30B9\u30AF",
1769
+ headingChangedFiles: "## \u5909\u66F4\u30D5\u30A1\u30A4\u30EB",
1770
+ headingSessions: "## \u30BB\u30C3\u30B7\u30E7\u30F3\u4E00\u89A7",
1771
+ headingIntegrity: "## \u6574\u5408\u6027"
1772
+ }
1773
+ };
1774
+
1370
1775
  // src/storage/sessions.ts
1371
1776
  import { readdir as readdir2 } from "fs/promises";
1372
1777
  import { join as join5 } from "path";
@@ -1573,8 +1978,8 @@ async function appendChainedEvent(paths, sessionId, event) {
1573
1978
  }
1574
1979
 
1575
1980
  // src/schemas/session.schema.ts
1576
- import { z as z4 } from "zod";
1577
- var SessionStatusSchema = z4.enum([
1981
+ import { z as z5 } from "zod";
1982
+ var SessionStatusSchema = z5.enum([
1578
1983
  "initialized",
1579
1984
  "running",
1580
1985
  "waiting_approval",
@@ -1584,7 +1989,7 @@ var SessionStatusSchema = z4.enum([
1584
1989
  "imported",
1585
1990
  "archived"
1586
1991
  ]);
1587
- var SessionSourceKindSchema = z4.enum([
1992
+ var SessionSourceKindSchema = z5.enum([
1588
1993
  "claude-code-adapter",
1589
1994
  "claude-code-import",
1590
1995
  "codex-adapter",
@@ -1593,13 +1998,13 @@ var SessionSourceKindSchema = z4.enum([
1593
1998
  "import",
1594
1999
  "terminal"
1595
2000
  ]);
1596
- var SessionSourceSchema = z4.looseObject({
2001
+ var SessionSourceSchema = z5.looseObject({
1597
2002
  kind: SessionSourceKindSchema,
1598
- version: z4.literal("0.1.0"),
2003
+ version: z5.literal("0.1.0"),
1599
2004
  // Optional id of the originating session in the SOURCE tool's own
1600
2005
  // namespace (e.g. the Claude Code session UUID for a `claude-code-import`).
1601
2006
  // Lets re-imports of the same source be deduplicated; absent for live runs.
1602
- external_id: z4.string().optional(),
2007
+ external_id: z5.string().optional(),
1603
2008
  // Byte size of the source native log at import time, recorded so a later
1604
2009
  // import can detect that an append-only transcript GREW and re-import it
1605
2010
  // (scoped, preserving the session id) instead of skipping it as already
@@ -1607,33 +2012,33 @@ var SessionSourceSchema = z4.looseObject({
1607
2012
  // external_id, metrics). Absent on sessions imported before this field
1608
2013
  // existed (treated as legacy: never auto-re-imported, populated on the next
1609
2014
  // fresh import or `--force`).
1610
- source_size_bytes: z4.number().int().nonnegative().optional()
2015
+ source_size_bytes: z5.number().int().nonnegative().optional()
1611
2016
  });
1612
- var InvocationSchema = z4.looseObject({
1613
- command: z4.string().min(1),
1614
- args: z4.array(z4.string()).default([]),
2017
+ var InvocationSchema = z5.looseObject({
2018
+ command: z5.string().min(1),
2019
+ args: z5.array(z5.string()).default([]),
1615
2020
  // Nullable to record signal-terminated runs where the child has no exit
1616
2021
  // code; the same nullability is mirrored in CommandExecutedEventSchema.
1617
- exit_code: z4.number().int().nullable()
2022
+ exit_code: z5.number().int().nullable()
1618
2023
  });
1619
- var SessionMetricsSchema = z4.looseObject({
1620
- output_tokens: z4.number().int().nonnegative().optional(),
1621
- input_tokens: z4.number().int().nonnegative().optional(),
1622
- cached_input_tokens: z4.number().int().nonnegative().optional(),
1623
- reasoning_output_tokens: z4.number().int().nonnegative().optional(),
1624
- active_time_ms: z4.number().int().nonnegative().optional(),
1625
- active_intervals: z4.array(z4.looseObject({ start: IsoTimestampSchema, end: IsoTimestampSchema })).optional(),
1626
- active_gap_cap_ms: z4.number().int().nonnegative().optional(),
1627
- active_time_method: z4.string().optional(),
1628
- machine_active_time_ms: z4.number().int().nonnegative().optional()
2024
+ var SessionMetricsSchema = z5.looseObject({
2025
+ output_tokens: z5.number().int().nonnegative().optional(),
2026
+ input_tokens: z5.number().int().nonnegative().optional(),
2027
+ cached_input_tokens: z5.number().int().nonnegative().optional(),
2028
+ reasoning_output_tokens: z5.number().int().nonnegative().optional(),
2029
+ active_time_ms: z5.number().int().nonnegative().optional(),
2030
+ active_intervals: z5.array(z5.looseObject({ start: IsoTimestampSchema, end: IsoTimestampSchema })).optional(),
2031
+ active_gap_cap_ms: z5.number().int().nonnegative().optional(),
2032
+ active_time_method: z5.string().optional(),
2033
+ machine_active_time_ms: z5.number().int().nonnegative().optional()
1629
2034
  });
1630
- var SessionIntegritySchema = z4.object({
1631
- head_hash: z4.string(),
1632
- event_count: z4.number().int().nonnegative()
2035
+ var SessionIntegritySchema = z5.object({
2036
+ head_hash: z5.string(),
2037
+ event_count: z5.number().int().nonnegative()
1633
2038
  }).strict();
1634
- var SessionInnerSchema = z4.looseObject({
2039
+ var SessionInnerSchema = z5.looseObject({
1635
2040
  id: SessionIdSchema,
1636
- label: z4.string().optional(),
2041
+ label: z5.string().optional(),
1637
2042
  task_id: TaskIdSchema.nullable().optional(),
1638
2043
  workspace_id: WorkspaceIdSchema,
1639
2044
  source: SessionSourceSchema,
@@ -1641,15 +2046,15 @@ var SessionInnerSchema = z4.looseObject({
1641
2046
  // ended_at is optional because initialized / running sessions have no end time yet.
1642
2047
  ended_at: IsoTimestampSchema.optional(),
1643
2048
  status: SessionStatusSchema,
1644
- working_directory: z4.string().min(1),
2049
+ working_directory: z5.string().min(1),
1645
2050
  invocation: InvocationSchema,
1646
- related_files: z4.array(z4.string()).default([]),
1647
- events_log: z4.string().default("events.jsonl"),
1648
- summary: z4.string().nullable().optional(),
2051
+ related_files: z5.array(z5.string()).default([]),
2052
+ events_log: z5.string().default("events.jsonl"),
2053
+ summary: z5.string().nullable().optional(),
1649
2054
  metrics: SessionMetricsSchema.optional(),
1650
2055
  integrity: SessionIntegritySchema.optional()
1651
2056
  });
1652
- var SessionSchema = z4.looseObject({
2057
+ var SessionSchema = z5.looseObject({
1653
2058
  schema_version: SchemaVersionSchema,
1654
2059
  session: SessionInnerSchema
1655
2060
  });
@@ -1855,7 +2260,7 @@ async function renderDecisions(input) {
1855
2260
  const abs = resolve(repoRoot, relPath);
1856
2261
  let exists;
1857
2262
  try {
1858
- await lstat(abs);
2263
+ await lstat2(abs);
1859
2264
  exists = true;
1860
2265
  } catch {
1861
2266
  exists = false;
@@ -1863,7 +2268,9 @@ async function renderDecisions(input) {
1863
2268
  fileExistenceCache.set(relPath, exists);
1864
2269
  return exists;
1865
2270
  }
2271
+ const language = input.language ?? await resolveViewLanguageFromPaths(input.paths);
1866
2272
  const body = await formatDecisionsBody({
2273
+ language,
1867
2274
  nowIso: input.nowIso,
1868
2275
  decisions,
1869
2276
  knownEventIds,
@@ -1872,6 +2279,7 @@ async function renderDecisions(input) {
1872
2279
  return { body, decisionCount: decisions.length };
1873
2280
  }
1874
2281
  async function formatDecisionsBody(args) {
2282
+ const t = viewStrings(args.language);
1875
2283
  const lines = [];
1876
2284
  lines.push("# Decisions");
1877
2285
  lines.push("");
@@ -1894,12 +2302,12 @@ async function formatDecisionsBody(args) {
1894
2302
  lines.push("");
1895
2303
  }
1896
2304
  const occurredDate = d.occurredAt.slice(0, 10);
1897
- lines.push(`- \u6C7A\u5B9A\u65E5: ${occurredDate}`);
2305
+ lines.push(`- ${t.decisions.dateLabel}: ${occurredDate}`);
1898
2306
  if (d.kind === "track" && d.voided === void 0) {
1899
- lines.push("- \u7A2E\u5225: track (close \u307E\u3067 orient/handoff \u306B\u7D99\u7D9A\u8868\u793A)");
2307
+ lines.push(t.decisions.trackKindLine);
1900
2308
  }
1901
2309
  lines.push(`- session: ${shortDecisionSessionId(d.sessionId)}`);
1902
- lines.push(`- \u5224\u65AD: ${d.title}`);
2310
+ lines.push(`- ${t.decisions.decisionLabel}: ${d.title}`);
1903
2311
  if (typeof d.rationale === "string" && d.rationale.length > 0) {
1904
2312
  lines.push(`- rationale: ${d.rationale}`);
1905
2313
  }
@@ -2147,27 +2555,27 @@ import { simpleGit } from "simple-git";
2147
2555
  import * as fsp from "fs/promises";
2148
2556
 
2149
2557
  // src/schemas/status.schema.ts
2150
- import { z as z5 } from "zod";
2151
- var StatusSchema = z5.object({
2558
+ import { z as z6 } from "zod";
2559
+ var StatusSchema = z6.object({
2152
2560
  // status.json is a rebuildable cache: exact-match-or-rebuild, not the
2153
2561
  // durable forward-compat gate.
2154
2562
  schema_version: CacheVersionSchema,
2155
2563
  generated_at: IsoTimestampSchema,
2156
- workspace: z5.object({
2564
+ workspace: z6.object({
2157
2565
  id: WorkspaceIdSchema,
2158
- name: z5.string().min(1),
2566
+ name: z6.string().min(1),
2159
2567
  // Mirrors the manifest's basou_version, so it uses the same
2160
2568
  // forward-compatible format gate (accept 0.x.y) rather than a literal.
2161
2569
  basou_version: SchemaVersionSchema
2162
2570
  }).strict(),
2163
- directories_present: z5.object({
2164
- sessions: z5.boolean(),
2165
- tasks: z5.boolean(),
2166
- approvals_pending: z5.boolean(),
2167
- approvals_resolved: z5.boolean(),
2168
- logs: z5.boolean(),
2169
- raw: z5.boolean(),
2170
- tmp: z5.boolean()
2571
+ directories_present: z6.object({
2572
+ sessions: z6.boolean(),
2573
+ tasks: z6.boolean(),
2574
+ approvals_pending: z6.boolean(),
2575
+ approvals_resolved: z6.boolean(),
2576
+ logs: z6.boolean(),
2577
+ raw: z6.boolean(),
2578
+ tmp: z6.boolean()
2171
2579
  }).strict()
2172
2580
  }).strict();
2173
2581
 
@@ -2186,7 +2594,7 @@ async function assertBasouRootSafe(rootPath) {
2186
2594
  try {
2187
2595
  stat4 = await fsp.lstat(rootPath);
2188
2596
  } catch (error) {
2189
- if (hasErrorCode2(error) && error.code === "ENOENT") {
2597
+ if (hasErrorCode3(error) && error.code === "ENOENT") {
2190
2598
  throw new Error("Basou workspace not found", { cause: error });
2191
2599
  }
2192
2600
  throw new Error("Failed to inspect .basou root", { cause: error });
@@ -2202,7 +2610,7 @@ async function dirPresent(path2) {
2202
2610
  try {
2203
2611
  return (await fsp.lstat(path2)).isDirectory();
2204
2612
  } catch (error) {
2205
- if (hasErrorCode2(error) && (error.code === "ENOENT" || error.code === "ENOTDIR")) {
2613
+ if (hasErrorCode3(error) && (error.code === "ENOENT" || error.code === "ENOTDIR")) {
2206
2614
  return false;
2207
2615
  }
2208
2616
  throw new Error("Failed to inspect .basou subdirectory", { cause: error });
@@ -2243,7 +2651,7 @@ async function readStatus(paths) {
2243
2651
  try {
2244
2652
  body = await fsp.readFile(paths.files.status, "utf8");
2245
2653
  } catch (error) {
2246
- if (hasErrorCode2(error) && error.code === "ENOENT") {
2654
+ if (hasErrorCode3(error) && error.code === "ENOENT") {
2247
2655
  throw new Error("Status file not found", { cause: error });
2248
2656
  }
2249
2657
  throw new Error("Failed to read status file", { cause: error });
@@ -2256,7 +2664,7 @@ async function readStatus(paths) {
2256
2664
  }
2257
2665
  return StatusSchema.parse(parsed);
2258
2666
  }
2259
- function hasErrorCode2(error) {
2667
+ function hasErrorCode3(error) {
2260
2668
  if (!(error instanceof Error)) return false;
2261
2669
  return typeof error.code === "string";
2262
2670
  }
@@ -2513,15 +2921,15 @@ import { createHash as createHash2 } from "crypto";
2513
2921
  import { mkdir as mkdir3, readdir as readdir4, readFile as readFile7, rename as rename2, stat as stat3, unlink as unlink3 } from "fs/promises";
2514
2922
  import { join as join12 } from "path";
2515
2923
  import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
2516
- import { z as z8 } from "zod";
2924
+ import { z as z9 } from "zod";
2517
2925
 
2518
2926
  // src/schemas/task.schema.ts
2519
- import { z as z6 } from "zod";
2520
- var TaskStatusSchema = z6.enum(["planned", "in_progress", "done", "cancelled"]);
2521
- var TaskInnerSchema = z6.looseObject({
2927
+ import { z as z7 } from "zod";
2928
+ var TaskStatusSchema = z7.enum(["planned", "in_progress", "done", "cancelled"]);
2929
+ var TaskInnerSchema = z7.looseObject({
2522
2930
  id: TaskIdSchema,
2523
- title: z6.string().min(1),
2524
- label: z6.string().min(1).optional(),
2931
+ title: z7.string().min(1),
2932
+ label: z7.string().min(1).optional(),
2525
2933
  status: TaskStatusSchema,
2526
2934
  created_at: IsoTimestampSchema,
2527
2935
  updated_at: IsoTimestampSchema,
@@ -2545,9 +2953,9 @@ var TaskInnerSchema = z6.looseObject({
2545
2953
  * task.md and immediately see related sessions. Defaults to `[]` for
2546
2954
  * backward compatibility.
2547
2955
  */
2548
- linked_sessions: z6.array(SessionIdSchema).default([])
2956
+ linked_sessions: z7.array(SessionIdSchema).default([])
2549
2957
  });
2550
- var TaskSchema = z6.looseObject({
2958
+ var TaskSchema = z7.looseObject({
2551
2959
  schema_version: SchemaVersionSchema,
2552
2960
  task: TaskInnerSchema
2553
2961
  });
@@ -2789,17 +3197,17 @@ import { readFile as readFile6 } from "fs/promises";
2789
3197
  import { join as join11 } from "path";
2790
3198
 
2791
3199
  // src/schemas/task-index.schema.ts
2792
- import { z as z7 } from "zod";
2793
- var TaskIndexEntrySchema = z7.object({
3200
+ import { z as z8 } from "zod";
3201
+ var TaskIndexEntrySchema = z8.object({
2794
3202
  id: TaskIdSchema,
2795
3203
  status: TaskStatusSchema,
2796
- label: z7.string().min(1).optional(),
3204
+ label: z8.string().min(1).optional(),
2797
3205
  updated_at: IsoTimestampSchema
2798
3206
  }).strict();
2799
- var TaskIndexSchema = z7.object({
3207
+ var TaskIndexSchema = z8.object({
2800
3208
  // Rebuildable cache: exact-match-or-rebuild, not the durable forward-compat gate.
2801
3209
  schema_version: CacheVersionSchema,
2802
- tasks: z7.array(TaskIndexEntrySchema),
3210
+ tasks: z8.array(TaskIndexEntrySchema),
2803
3211
  last_rebuilt_at: IsoTimestampSchema
2804
3212
  }).strict();
2805
3213
  var TASK_INDEX_SCHEMA_VERSION = "0.1.0";
@@ -2885,8 +3293,8 @@ var DEFAULT_ATTACHABLE_STATUSES2 = /* @__PURE__ */ new Set([
2885
3293
  "waiting_approval"
2886
3294
  ]);
2887
3295
  var InitialTaskStatusSchema = TaskStatusSchema;
2888
- var TaskTitleSchema = z8.string().min(1);
2889
- var TaskLabelSchema = z8.string().min(1);
3296
+ var TaskTitleSchema = z9.string().min(1);
3297
+ var TaskLabelSchema = z9.string().min(1);
2890
3298
  var CompletedAtSchema = IsoTimestampSchema;
2891
3299
  var TERMINAL_TASK_STATUSES = /* @__PURE__ */ new Set(["done", "cancelled"]);
2892
3300
  function isTerminalTaskStatus(status) {
@@ -4335,7 +4743,9 @@ async function renderHandoff(input) {
4335
4743
  const firstEntry = entries[0];
4336
4744
  const lastEntry = entries[entries.length - 1];
4337
4745
  const sessionRange = firstEntry !== void 0 && lastEntry !== void 0 ? `${shortIdWithPrefix(firstEntry.sessionId)}..${shortIdWithPrefix(lastEntry.sessionId)}` : "";
4746
+ const language = input.language ?? await resolveViewLanguageFromPaths(input.paths);
4338
4747
  const body = formatHandoffBody({
4748
+ language,
4339
4749
  nowIso: input.nowIso,
4340
4750
  sessionRange,
4341
4751
  sessionCount: entries.length,
@@ -4365,6 +4775,7 @@ async function renderHandoff(input) {
4365
4775
  };
4366
4776
  }
4367
4777
  function formatHandoffBody(args) {
4778
+ const t = viewStrings(args.language);
4368
4779
  const lines = [];
4369
4780
  lines.push("# Handoff");
4370
4781
  lines.push("");
@@ -4374,32 +4785,32 @@ function formatHandoffBody(args) {
4374
4785
  lines.push(`> Generated at ${args.nowIso}`);
4375
4786
  }
4376
4787
  lines.push("");
4377
- lines.push("## \u73FE\u5728\u306E\u72B6\u614B");
4788
+ lines.push(t.handoff.headingCurrentState);
4378
4789
  lines.push("");
4379
4790
  if (args.latestSession !== void 0) {
4380
4791
  const status = args.latestSession.session.session.status;
4381
4792
  const label = args.latestSession.session.session.label;
4382
4793
  const shortId2 = shortIdWithPrefix(args.latestSession.sessionId);
4383
4794
  if (label !== void 0 && label !== "") {
4384
- lines.push(`- \u6700\u7D42 session: ${label} (${status}) [${shortId2}]`);
4795
+ lines.push(`- ${t.common.lastSessionLabel}: ${label} (${status}) [${shortId2}]`);
4385
4796
  } else {
4386
- lines.push(`- \u6700\u7D42 session: ${shortId2} (${status})`);
4797
+ lines.push(`- ${t.common.lastSessionLabel}: ${shortId2} (${status})`);
4387
4798
  }
4388
4799
  } else {
4389
- lines.push("- \u6700\u7D42 session: (no live sessions)");
4800
+ lines.push(`- ${t.common.lastSessionLabel}: (no live sessions)`);
4390
4801
  }
4391
4802
  if (args.latestActivityRecord !== void 0) {
4392
4803
  const statusLabel = args.latestTaskDoc !== void 0 ? args.latestTaskDoc.task.task.status : "status unknown \u2014 task.md missing or invalid";
4393
4804
  const linkedCount = args.latestTaskDoc?.task.task.linked_sessions?.length;
4394
4805
  const linkedSuffix = linkedCount !== void 0 && linkedCount > 1 ? `, linked_sessions: ${linkedCount}` : "";
4395
4806
  lines.push(
4396
- `- \u6700\u7D42 task: ${args.latestActivityRecord.title} (${statusLabel}${linkedSuffix}) [${shortIdWithPrefix(args.latestActivityRecord.taskId)}]`
4807
+ `- ${t.handoff.lastTaskLabel}: ${args.latestActivityRecord.title} (${statusLabel}${linkedSuffix}) [${shortIdWithPrefix(args.latestActivityRecord.taskId)}]`
4397
4808
  );
4398
4809
  } else {
4399
- lines.push("- \u6700\u7D42 task: (no tasks recorded yet)");
4810
+ lines.push(`- ${t.handoff.lastTaskLabel}: (no tasks recorded yet)`);
4400
4811
  }
4401
4812
  lines.push("");
4402
- lines.push("## \u76F4\u8FD1\u306E\u5909\u66F4\u30D5\u30A1\u30A4\u30EB");
4813
+ lines.push(t.handoff.headingRecentFiles);
4403
4814
  lines.push("");
4404
4815
  if (args.displayedFiles.length === 0) {
4405
4816
  lines.push("(no related files recorded)");
@@ -4408,7 +4819,7 @@ function formatHandoffBody(args) {
4408
4819
  if (args.overflow > 0) lines.push(`- ... +${args.overflow} more`);
4409
4820
  }
4410
4821
  lines.push("");
4411
- lines.push("## \u76F4\u8FD1\u306E\u5224\u65AD");
4822
+ lines.push(t.handoff.headingLatestDecision);
4412
4823
  lines.push("");
4413
4824
  if (args.latestDecision === void 0) {
4414
4825
  lines.push("(no decisions recorded yet)");
@@ -4416,14 +4827,10 @@ function formatHandoffBody(args) {
4416
4827
  const last = args.latestDecision;
4417
4828
  lines.push(`- ${last.title} [${shortIdWithPrefix(last.decisionId)}]`);
4418
4829
  if (args.latestActivityAt !== null && isTrailingStale(args.latestActivityAt, last.occurredAt)) {
4419
- lines.push(
4420
- " - \u6CE8: \u6700\u7D42\u6D3B\u52D5\u306F\u3053\u306E\u5224\u65AD\u3088\u308A\u5F8C\u3067\u3059\u3002\u4F1A\u8A71\u3067\u65E2\u306B\u89E3\u6C7A\u6E08\u307F\u306E\u53EF\u80FD\u6027\u304C\u3042\u308B\u305F\u3081\u3001\u518D\u958B\u524D\u306B\u7D99\u7D9A\u70B9\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044(\u4F1A\u8A71\u3067\u306E\u610F\u601D\u6C7A\u5B9A\u306F\u81EA\u52D5\u8A18\u9332\u3055\u308C\u307E\u305B\u3093\u3002`basou decision capture` \u3067\u8A18\u9332\u3067\u304D\u307E\u3059)\u3002"
4421
- );
4830
+ lines.push(` - ${t.handoff.decisionStaleNote}`);
4422
4831
  }
4423
4832
  if (args.latestSession !== void 0 && last.sessionId !== args.latestSession.sessionId) {
4424
- lines.push(
4425
- ` - \u6CE8: \u3053\u306E\u5224\u65AD\u306F\u6700\u7D42 session \u3068\u306F\u5225\u306E session [${shortIdWithPrefix(last.sessionId)}] \u306E\u3082\u306E\u3067\u3059\u3002`
4426
- );
4833
+ lines.push(` - ${t.common.decisionOtherSessionNote(shortIdWithPrefix(last.sessionId))}`);
4427
4834
  }
4428
4835
  lines.push("");
4429
4836
  lines.push(`(${args.decisions.length} decisions total \u2014 see decisions.md)`);
@@ -4433,20 +4840,20 @@ function formatHandoffBody(args) {
4433
4840
  const TRACK_DISPLAY_LIMIT = 10;
4434
4841
  const shown = args.openTracks.slice(0, TRACK_DISPLAY_LIMIT);
4435
4842
  const overflow = args.openTracks.length - shown.length;
4436
- lines.push("## \u672A\u5B8C\u30C8\u30E9\u30C3\u30AF (close \u307E\u3067\u7D99\u7D9A\u8868\u793A)");
4843
+ lines.push(t.handoff.headingOpenTracks);
4437
4844
  lines.push("");
4438
- for (const t of shown) {
4439
- lines.push(`- ${t.title} [${shortIdWithPrefix(t.decisionId)}]`);
4440
- if (t.rationale !== null && t.rationale.trim() !== "") {
4441
- lines.push(` - \u7406\u7531: ${handoffRationale(t.rationale)}`);
4845
+ for (const track of shown) {
4846
+ lines.push(`- ${track.title} [${shortIdWithPrefix(track.decisionId)}]`);
4847
+ if (track.rationale !== null && track.rationale.trim() !== "") {
4848
+ lines.push(` - ${t.common.trackWhyLabel}: ${handoffRationale(track.rationale)}`);
4442
4849
  }
4443
4850
  }
4444
4851
  if (overflow > 0) lines.push(`- ... +${overflow} more (see decisions.md)`);
4445
4852
  lines.push("");
4446
- lines.push("\u5B8C\u4E86\u3057\u305F\u3089 `basou decision void <decision_id>` \u3067\u9589\u3058\u3066\u304F\u3060\u3055\u3044\u3002");
4853
+ lines.push(t.handoff.trackCloseInstruction);
4447
4854
  lines.push("");
4448
4855
  }
4449
- lines.push("## \u672A\u6C7A\u4E8B\u9805");
4856
+ lines.push(t.handoff.headingUnresolved);
4450
4857
  lines.push("");
4451
4858
  if (args.pendingApprovalsCount > 0) {
4452
4859
  lines.push(`- ${args.pendingApprovalsCount} pending approvals`);
@@ -4458,19 +4865,19 @@ function formatHandoffBody(args) {
4458
4865
  lines.push("(none)");
4459
4866
  }
4460
4867
  lines.push("");
4461
- lines.push("## \u6B21\u306B\u8AAD\u3080\u3079\u304D\u30D5\u30A1\u30A4\u30EB");
4868
+ lines.push(t.handoff.headingReadNext);
4462
4869
  lines.push("");
4463
4870
  lines.push("- .basou/decisions.md");
4464
4871
  for (const f of args.displayedFiles.slice(0, 3)) lines.push(`- ${f}`);
4465
4872
  lines.push("");
4466
- lines.push("## \u6B21\u306B\u5B9F\u884C\u3059\u3079\u304D\u4F5C\u696D");
4873
+ lines.push(t.handoff.headingNextWork);
4467
4874
  lines.push("");
4468
4875
  if (args.pendingTasks.length === 0) {
4469
4876
  lines.push("(no pending tasks)");
4470
4877
  } else {
4471
- for (const t of args.pendingTasks) {
4878
+ for (const t2 of args.pendingTasks) {
4472
4879
  lines.push(
4473
- `- ${t.task.task.title} (${t.task.task.status}) [${shortIdWithPrefix(t.task.task.id)}]`
4880
+ `- ${t2.task.task.title} (${t2.task.task.status}) [${shortIdWithPrefix(t2.task.task.id)}]`
4474
4881
  );
4475
4882
  }
4476
4883
  }
@@ -4479,7 +4886,7 @@ function formatHandoffBody(args) {
4479
4886
  const importedTableEntries = args.entries.filter(
4480
4887
  (e) => e.session.session.source.kind === "import"
4481
4888
  );
4482
- lines.push("## \u30BB\u30C3\u30B7\u30E7\u30F3\u4E00\u89A7");
4889
+ lines.push(t.handoff.headingSessions);
4483
4890
  lines.push("");
4484
4891
  if (args.entries.length === 0) {
4485
4892
  lines.push("(no sessions yet)");
@@ -4728,157 +5135,6 @@ async function classifyFilesBySourceRoot(input) {
4728
5135
 
4729
5136
  // src/orientation/orientation-renderer.ts
4730
5137
  import { dirname as dirname4, join as join15 } from "path";
4731
-
4732
- // src/storage/manifest.ts
4733
- import { lstat as lstat3 } from "fs/promises";
4734
-
4735
- // src/schemas/manifest.schema.ts
4736
- import { z as z9 } from "zod";
4737
- var ProjectSchema = z9.looseObject({
4738
- name: z9.string().optional(),
4739
- description: z9.string().optional()
4740
- });
4741
- var CapabilitiesSchema = z9.looseObject({
4742
- enabled: z9.array(z9.string())
4743
- });
4744
- var ApprovalConfigSchema = z9.looseObject({
4745
- required_for: z9.array(z9.string()).optional(),
4746
- default_risk_level: z9.enum(["low", "medium", "high", "critical"])
4747
- });
4748
- var ClaudeCodeAdapterConfigSchema = z9.looseObject({
4749
- enabled: z9.boolean(),
4750
- config_path: z9.string().optional()
4751
- });
4752
- var AdaptersSchema = z9.looseObject({
4753
- "claude-code": ClaudeCodeAdapterConfigSchema
4754
- });
4755
- var GitConfigSchema = z9.looseObject({
4756
- events_log: z9.enum(["ignore", "commit"]).default("ignore")
4757
- });
4758
- var SOURCE_ROOT_PATTERN = /^(?![~/\\])(?![A-Za-z]:)(?!\s)[^\0\\]*[^\0\\\s]$/;
4759
- var SourceRootSchema = z9.string().min(1).regex(SOURCE_ROOT_PATTERN, {
4760
- message: "source_roots entries must be relative paths (no absolute path, '~', '\\', or null byte)"
4761
- });
4762
- var ImportConfigSchema = z9.looseObject({
4763
- source_roots: z9.array(SourceRootSchema).min(1).optional()
4764
- });
4765
- var RepoVisibilitySchema = z9.enum(["public", "private", "future-public"]);
4766
- var RepoLanguageSchema = z9.enum(["en", "ja", "en+ja"]);
4767
- var PublishKindSchema = z9.enum(["web", "npm"]);
4768
- var RepoInstructionsSchema = z9.enum(["hub", "self"]);
4769
- var PublishTargetSchema = z9.looseObject({
4770
- kind: PublishKindSchema,
4771
- visibility: RepoVisibilitySchema.optional(),
4772
- language: RepoLanguageSchema.optional()
4773
- });
4774
- var RepoEntrySchema = z9.looseObject({
4775
- path: SourceRootSchema,
4776
- visibility: RepoVisibilitySchema.optional(),
4777
- language: RepoLanguageSchema.optional(),
4778
- publishes: z9.array(PublishTargetSchema).optional(),
4779
- instructions: RepoInstructionsSchema.optional()
4780
- });
4781
- var WorkspaceMetaSchema = z9.looseObject({
4782
- id: WorkspaceIdSchema,
4783
- name: z9.string().min(1),
4784
- created_at: IsoTimestampSchema,
4785
- updated_at: IsoTimestampSchema,
4786
- /**
4787
- * The generated workspace view: a throwaway directory that aggregates the
4788
- * roster repos via symlinks (one `<repo-basename>` symlink per repo). A path
4789
- * relative to the manifest root, reusing the machine-portable source-root
4790
- * constraint. Absent for a solo project (no view needed); `basou project
4791
- * workspace` reconciles the view's symlinks to the declared roster.
4792
- */
4793
- view: SourceRootSchema.optional()
4794
- });
4795
- var ManifestSchema = z9.looseObject({
4796
- schema_version: SchemaVersionSchema,
4797
- // Same forward-compatible format gate as schema_version (accept 0.x.y, gate a
4798
- // higher major with an upgrade error) rather than a hard literal. `basou_version`
4799
- // is a format stamp, not the npm/product version; consolidating it with
4800
- // schema_version is a candidate cleanup for the M4 freeze pass.
4801
- basou_version: SchemaVersionSchema,
4802
- workspace: WorkspaceMetaSchema,
4803
- project: ProjectSchema,
4804
- capabilities: CapabilitiesSchema,
4805
- approval: ApprovalConfigSchema,
4806
- adapters: AdaptersSchema,
4807
- git: GitConfigSchema,
4808
- import: ImportConfigSchema.optional(),
4809
- repos: z9.array(RepoEntrySchema).min(1).optional()
4810
- });
4811
- var KNOWN_TOP_LEVEL_KEYS = new Set(Object.keys(ManifestSchema.shape));
4812
- function unknownManifestKeys(manifest) {
4813
- return Object.keys(manifest).filter((k) => !KNOWN_TOP_LEVEL_KEYS.has(k)).sort();
4814
- }
4815
-
4816
- // src/storage/manifest.ts
4817
- function createManifest(input) {
4818
- if (input.workspaceName.length === 0) {
4819
- throw new Error("Workspace name is empty. Pass --name explicitly.");
4820
- }
4821
- const now = (input.now ?? /* @__PURE__ */ new Date()).toISOString();
4822
- const workspaceId = input.workspaceId ?? prefixedUlid("ws");
4823
- const project = {
4824
- ...input.projectName !== void 0 ? { name: input.projectName } : {},
4825
- ...input.projectDescription !== void 0 ? { description: input.projectDescription } : {}
4826
- };
4827
- const manifest = {
4828
- schema_version: "0.1.0",
4829
- basou_version: "0.1.0",
4830
- workspace: {
4831
- id: workspaceId,
4832
- name: input.workspaceName,
4833
- created_at: now,
4834
- updated_at: now
4835
- },
4836
- project,
4837
- capabilities: {
4838
- enabled: ["core", "claude-code-adapter", "terminal-recording", "git-capability", "approval"]
4839
- },
4840
- approval: {
4841
- required_for: ["destructive_command", "external_send"],
4842
- default_risk_level: "medium"
4843
- },
4844
- adapters: {
4845
- "claude-code": { enabled: true }
4846
- },
4847
- git: { events_log: "ignore" },
4848
- ...input.sourceRoots !== void 0 && input.sourceRoots.length > 0 ? { import: { source_roots: input.sourceRoots } } : {}
4849
- };
4850
- return ManifestSchema.parse(manifest);
4851
- }
4852
- async function writeManifest(paths, manifest, options) {
4853
- const force = options?.force === true;
4854
- const validated = ManifestSchema.parse(manifest);
4855
- delete validated.project.repository_url;
4856
- if (!force) {
4857
- let existed = false;
4858
- try {
4859
- await lstat3(paths.files.manifest);
4860
- existed = true;
4861
- } catch (error) {
4862
- if (!hasErrorCode3(error) || error.code !== "ENOENT") {
4863
- throw new Error("Failed to inspect existing manifest", { cause: error });
4864
- }
4865
- }
4866
- if (existed) {
4867
- throw new Error("Already initialized. Use --force to overwrite.");
4868
- }
4869
- }
4870
- await writeYamlFile(paths.files.manifest, validated);
4871
- }
4872
- async function readManifest(paths) {
4873
- const raw = await readYamlFile(paths.files.manifest);
4874
- return ManifestSchema.parse(raw);
4875
- }
4876
- function hasErrorCode3(error) {
4877
- if (!(error instanceof Error)) return false;
4878
- return typeof error.code === "string";
4879
- }
4880
-
4881
- // src/orientation/orientation-renderer.ts
4882
5138
  async function summarizeOrientation(input) {
4883
5139
  const limit = input.relatedFilesLimit ?? 10;
4884
5140
  const now = new Date(input.nowIso);
@@ -5111,10 +5367,12 @@ async function summarizeOrientation(input) {
5111
5367
  }
5112
5368
  async function renderOrientation(input) {
5113
5369
  const summary = await summarizeOrientation(input);
5370
+ const language = input.language ?? await resolveViewLanguageFromPaths(input.paths);
5114
5371
  return {
5115
5372
  body: formatOrientationBody(summary, {
5116
5373
  staleness: input.staleness ?? null,
5117
- verbose: input.verbose === true
5374
+ verbose: input.verbose === true,
5375
+ language
5118
5376
  }),
5119
5377
  sessionCount: summary.sessionCount,
5120
5378
  pendingApprovalsCount: summary.pendingApprovals.length,
@@ -5125,6 +5383,7 @@ async function renderOrientation(input) {
5125
5383
  };
5126
5384
  }
5127
5385
  function formatOrientationBody(summary, opts) {
5386
+ const t = viewStrings(opts.language);
5128
5387
  const lines = [];
5129
5388
  const now = new Date(summary.generatedAt);
5130
5389
  const newestRel = relativeAge(summary.freshness.newestStartedAt ?? void 0, now);
@@ -5138,100 +5397,100 @@ function formatOrientationBody(summary, opts) {
5138
5397
  lines.push(`> hosts: local, ${summary.hosts.join(", ")}`);
5139
5398
  }
5140
5399
  lines.push("");
5141
- const banner = stalenessBanner(opts.staleness);
5400
+ const banner = stalenessBanner(opts.staleness, t);
5142
5401
  if (banner.length > 0) {
5143
5402
  for (const line of banner) lines.push(line);
5144
5403
  lines.push("");
5145
5404
  }
5146
- lines.push("## \u4ECA\u3069\u3053\u306B\u3044\u308B");
5405
+ lines.push(t.orientation.headingWhere);
5147
5406
  lines.push("");
5148
5407
  if (summary.latestSession !== null) {
5149
5408
  const s = summary.latestSession;
5150
5409
  const sid = shortId(s.sessionId);
5151
5410
  if (s.label !== null && s.label !== "") {
5152
- lines.push(`- \u6700\u7D42 session: ${s.label} (${s.status}) [${sid}]${hostSuffix(s.host)}`);
5411
+ lines.push(
5412
+ `- ${t.common.lastSessionLabel}: ${s.label} (${s.status}) [${sid}]${hostSuffix(s.host)}`
5413
+ );
5153
5414
  } else {
5154
- lines.push(`- \u6700\u7D42 session: ${sid} (${s.status})${hostSuffix(s.host)}`);
5415
+ lines.push(`- ${t.common.lastSessionLabel}: ${sid} (${s.status})${hostSuffix(s.host)}`);
5155
5416
  }
5156
5417
  } else {
5157
- lines.push("- \u6700\u7D42 session: (no live sessions)");
5418
+ lines.push(`- ${t.common.lastSessionLabel}: (no live sessions)`);
5158
5419
  }
5159
5420
  if (summary.latestDecision !== null) {
5160
5421
  const dec = summary.latestDecision;
5161
- const decAge = relativeAgeJa(dec.occurredAt, now);
5422
+ const decAge = t.relativeAge(dec.occurredAt, now);
5162
5423
  lines.push(
5163
- `- \u76F4\u8FD1\u306E\u5224\u65AD: ${dec.title} [${shortId(dec.decisionId)}] (${decAge})${hostSuffix(dec.host)}`
5424
+ `- ${t.common.latestDecisionLabel}: ${dec.title} [${shortId(dec.decisionId)}] (${decAge})${hostSuffix(dec.host)}`
5164
5425
  );
5165
5426
  const activityAt = summary.freshness.latestActivityAt;
5166
5427
  if (activityAt !== null && isTrailingStale(activityAt, dec.occurredAt)) {
5167
- lines.push(
5168
- ` - \u6CE8: \u3053\u308C\u306F\u6700\u5F8C\u306B\u300C\u8A18\u9332\u3055\u308C\u305F\u300D\u5224\u65AD\u3067\u3059\u3002\u6700\u7D42\u6D3B\u52D5 (${relativeAgeJa(activityAt, now)}) \u306F\u3053\u308C\u3088\u308A\u5F8C\u306E\u305F\u3081\u3001\u73FE\u5728\u306E\u65B9\u91DD\u304C\u53CD\u6620\u3055\u308C\u3066\u3044\u306A\u3044\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059(\u4F1A\u8A71\u3067\u306E\u610F\u601D\u6C7A\u5B9A\u306F\u81EA\u52D5\u8A18\u9332\u3055\u308C\u307E\u305B\u3093\u3002\`basou decision capture\` \u3067\u3053\u306E session \u306E\u5224\u65AD\u3092\u8A18\u9332\u3067\u304D\u307E\u3059)\u3002`
5169
- );
5428
+ lines.push(` - ${t.orientation.decisionStaleNote(t.relativeAge(activityAt, now))}`);
5170
5429
  }
5171
5430
  if (summary.latestSession !== null && dec.sessionId !== summary.latestSession.sessionId) {
5172
- lines.push(
5173
- ` - \u6CE8: \u3053\u306E\u5224\u65AD\u306F\u6700\u7D42 session \u3068\u306F\u5225\u306E session [${shortId(dec.sessionId)}] \u306E\u3082\u306E\u3067\u3059\u3002`
5174
- );
5431
+ lines.push(` - ${t.common.decisionOtherSessionNote(shortId(dec.sessionId))}`);
5175
5432
  }
5176
5433
  if (summary.decisionCount > 1) {
5177
5434
  lines.push(` - ${summary.decisionCount} decisions total \u2014 see decisions.md`);
5178
5435
  }
5179
5436
  } else {
5180
- lines.push("- \u76F4\u8FD1\u306E\u5224\u65AD: (no decisions recorded yet; capture with `basou decision capture`)");
5437
+ lines.push(
5438
+ `- ${t.common.latestDecisionLabel}: (no decisions recorded yet; capture with \`basou decision capture\`)`
5439
+ );
5181
5440
  }
5182
5441
  if (summary.relatedFiles.displayed.length > 0) {
5183
5442
  const shown = summary.relatedFiles.displayed.join(", ");
5184
5443
  const more = summary.relatedFiles.overflow > 0 ? ` (... +${summary.relatedFiles.overflow} more)` : "";
5185
- lines.push(`- \u76F4\u8FD1\u306E\u5909\u66F4\u30D5\u30A1\u30A4\u30EB: ${shown}${more}`);
5444
+ lines.push(`- ${t.common.recentFilesLabel}: ${shown}${more}`);
5186
5445
  if (summary.relatedFiles.outOfRoot.length > 0) {
5187
5446
  const OUT_OF_ROOT_DISPLAY = 10;
5188
5447
  const out = summary.relatedFiles.outOfRoot;
5189
5448
  const shownOut = out.slice(0, OUT_OF_ROOT_DISPLAY).join(", ");
5190
5449
  const outMore = out.length > OUT_OF_ROOT_DISPLAY ? ` (... +${out.length - OUT_OF_ROOT_DISPLAY} more)` : "";
5191
- lines.push(
5192
- ` - \u26A0 source_roots \u5916 ${out.length} \u4EF6 (\u5225\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u53EF\u80FD\u6027): ${shownOut}${outMore}`
5193
- );
5450
+ lines.push(` - ${t.orientation.outOfRootWarning(out.length, `${shownOut}${outMore}`)}`);
5194
5451
  }
5195
5452
  } else {
5196
- lines.push("- \u76F4\u8FD1\u306E\u5909\u66F4\u30D5\u30A1\u30A4\u30EB: (none recorded)");
5453
+ lines.push(`- ${t.common.recentFilesLabel}: (none recorded)`);
5197
5454
  }
5198
5455
  lines.push("");
5199
- lines.push(`## \u6700\u8FD1\u306E\u6D41\u308C (\u76F4\u8FD1 ${RECENT_DIRECTION_SESSIONS} session)`);
5456
+ lines.push(t.orientation.headingRecent(RECENT_DIRECTION_SESSIONS));
5200
5457
  lines.push("");
5201
5458
  if (summary.recentDirection.length === 0) {
5202
- lines.push("- (\u307E\u3060\u8A18\u9332\u304C\u3042\u308A\u307E\u305B\u3093)");
5459
+ lines.push(`- ${t.orientation.recentEmpty}`);
5203
5460
  } else {
5204
5461
  for (const s of summary.recentDirection) {
5205
5462
  const sid = shortId(s.sessionId);
5206
- const age = relativeAgeJa(s.occurredAt, now);
5463
+ const age = t.relativeAge(s.occurredAt, now);
5207
5464
  const head = s.label !== null && s.label !== "" ? s.label : sid;
5208
5465
  lines.push(`- ${head} (${age})${hostSuffix(s.host)}`);
5209
5466
  if (s.decisions.length > 0) {
5210
5467
  const more = s.decisionsOverflow > 0 ? ` (+${s.decisionsOverflow})` : "";
5211
- lines.push(` - \u5224\u65AD: ${s.decisions.map(noteSummary).join("; ")}${more}`);
5468
+ lines.push(
5469
+ ` - ${t.orientation.recentDecisionsLabel}: ${s.decisions.map(noteSummary).join("; ")}${more}`
5470
+ );
5212
5471
  }
5213
5472
  for (const note of s.notes) {
5214
- lines.push(` - \u6B21\u306E\u8D77\u70B9: ${noteSummary(note)}`);
5473
+ lines.push(` - ${t.orientation.recentNextStepLabel}: ${noteSummary(note)}`);
5215
5474
  }
5216
5475
  if (s.files.length > 0) {
5217
- lines.push(` - \u5909\u66F4: ${s.files.join(", ")}`);
5476
+ lines.push(` - ${t.orientation.recentChangedLabel}: ${s.files.join(", ")}`);
5218
5477
  }
5219
5478
  }
5220
5479
  }
5221
5480
  lines.push("");
5222
- lines.push("## \u4F55\u304C\u52D5\u304F");
5481
+ lines.push(t.orientation.headingInFlight);
5223
5482
  lines.push("");
5224
- lines.push(`### \u9032\u884C\u4E2D task (${summary.inFlightTasks.length})`);
5483
+ lines.push(t.orientation.inFlightTasksHeading(summary.inFlightTasks.length));
5225
5484
  if (summary.inFlightTasks.length === 0) {
5226
5485
  lines.push("- (none)");
5227
5486
  } else {
5228
- for (const t of summary.inFlightTasks) {
5229
- const linkedSuffix = t.linkedSessions > 1 ? ` \u2014 linked_sessions: ${t.linkedSessions}` : "";
5230
- lines.push(`- ${t.title} (${t.status}) [${shortId(t.id)}]${linkedSuffix}`);
5487
+ for (const t2 of summary.inFlightTasks) {
5488
+ const linkedSuffix = t2.linkedSessions > 1 ? ` \u2014 linked_sessions: ${t2.linkedSessions}` : "";
5489
+ lines.push(`- ${t2.title} (${t2.status}) [${shortId(t2.id)}]${linkedSuffix}`);
5231
5490
  }
5232
5491
  }
5233
5492
  lines.push("");
5234
- lines.push(`### \u627F\u8A8D\u5F85\u3061 (${summary.pendingApprovals.length})`);
5493
+ lines.push(t.orientation.pendingApprovalsHeading(summary.pendingApprovals.length));
5235
5494
  if (summary.pendingApprovals.length === 0) {
5236
5495
  lines.push("- (none)");
5237
5496
  } else {
@@ -5243,7 +5502,7 @@ function formatOrientationBody(summary, opts) {
5243
5502
  }
5244
5503
  }
5245
5504
  lines.push("");
5246
- lines.push(`### \u8981\u6CE8\u610F session (${summary.suspects.length})`);
5505
+ lines.push(t.orientation.suspectSessionsHeading(summary.suspects.length));
5247
5506
  if (summary.suspects.length === 0) {
5248
5507
  lines.push("- (none)");
5249
5508
  } else {
@@ -5254,71 +5513,63 @@ function formatOrientationBody(summary, opts) {
5254
5513
  }
5255
5514
  }
5256
5515
  lines.push("");
5257
- lines.push("## \u3069\u3053\u3078\u5411\u304B\u3046");
5516
+ lines.push(t.orientation.headingForward);
5258
5517
  lines.push("");
5259
5518
  if (summary.openTracks.length > 0) {
5260
5519
  const TRACK_DISPLAY_LIMIT = 10;
5261
5520
  const shownTracks = summary.openTracks.slice(0, TRACK_DISPLAY_LIMIT);
5262
5521
  const trackOverflow = summary.openTracks.length - shownTracks.length;
5263
- lines.push(`### \u672A\u5B8C\u30C8\u30E9\u30C3\u30AF (close \u307E\u3067\u7D99\u7D9A\u8868\u793A) (${summary.openTracks.length})`);
5264
- for (const t of shownTracks) {
5265
- const trackAge = relativeAgeJa(t.occurredAt, now);
5266
- lines.push(`- ${t.title} [${shortId(t.decisionId)}] (${trackAge})${hostSuffix(t.host)}`);
5267
- if (t.rationale !== null && t.rationale.trim() !== "") {
5268
- lines.push(` - \u7406\u7531: ${trackRationale(t.rationale)}`);
5522
+ lines.push(t.orientation.openTracksHeading(summary.openTracks.length));
5523
+ for (const track of shownTracks) {
5524
+ const trackAge = t.relativeAge(track.occurredAt, now);
5525
+ lines.push(
5526
+ `- ${track.title} [${shortId(track.decisionId)}] (${trackAge})${hostSuffix(track.host)}`
5527
+ );
5528
+ if (track.rationale !== null && track.rationale.trim() !== "") {
5529
+ lines.push(` - ${t.common.trackWhyLabel}: ${trackRationale(track.rationale)}`);
5269
5530
  }
5270
5531
  }
5271
5532
  if (trackOverflow > 0) {
5272
5533
  lines.push(`- ... +${trackOverflow} more (see decisions.md)`);
5273
5534
  }
5274
- lines.push(
5275
- "\u5B8C\u4E86\u3057\u305F\u3089 `basou decision void <decision_id>` \u3067\u9589\u3058\u3066\u304F\u3060\u3055\u3044\u3002\u9589\u3058\u308B\u307E\u3067\u6BCE\u56DE\u3053\u3053\u306B\u8868\u793A\u3055\u308C\u307E\u3059\u3002"
5276
- );
5535
+ lines.push(t.orientation.trackCloseInstruction);
5277
5536
  lines.push("");
5278
5537
  }
5279
5538
  if (summary.latestNote !== null) {
5280
- const noteAge = relativeAgeJa(summary.latestNote.occurredAt, now);
5539
+ const noteAge = t.relativeAge(summary.latestNote.occurredAt, now);
5281
5540
  lines.push(
5282
- `- \u6B21\u306E\u8D77\u70B9 (\u8A18\u9332\u6E08\u307F, ${noteAge}): ${noteSummary(summary.latestNote.body)} [session ${shortId(summary.latestNote.sessionId)}]${hostSuffix(summary.latestNote.host)}`
5541
+ `- ${t.orientation.nextStepRecordedLabel(noteAge)}: ${noteSummary(summary.latestNote.body)} [session ${shortId(summary.latestNote.sessionId)}]${hostSuffix(summary.latestNote.host)}`
5283
5542
  );
5284
5543
  const activityAt = summary.freshness.latestActivityAt;
5285
5544
  if (activityAt !== null && isTrailingStale(activityAt, summary.latestNote.occurredAt)) {
5286
- lines.push(
5287
- ` - \u6CE8: \u3053\u306E\u8D77\u70B9\u306E\u8A18\u9332\u5F8C (\u6700\u7D42\u6D3B\u52D5 ${relativeAgeJa(activityAt, now)}) \u3082\u4F5C\u696D\u304C\u7D9A\u3044\u3066\u3044\u307E\u3059\u3002\u518D\u958B\u70B9\u304C\u53E4\u3044\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002`
5288
- );
5545
+ lines.push(` - ${t.orientation.noteStaleNote(t.relativeAge(activityAt, now))}`);
5289
5546
  }
5290
5547
  }
5291
- for (const t of summary.plannedTasks) {
5292
- lines.push(`- ${t.title} [${shortId(t.id)}]`);
5548
+ for (const task of summary.plannedTasks) {
5549
+ lines.push(`- ${task.title} [${shortId(task.id)}]`);
5293
5550
  }
5294
5551
  if (summary.openTracks.length === 0 && summary.latestNote === null && summary.plannedTasks.length === 0) {
5295
5552
  const dec = summary.latestDecision;
5296
5553
  if (dec === null) {
5297
5554
  lines.push("- (no planned tasks or recorded next step yet)");
5298
5555
  } else if (isTrailingStale(summary.freshness.latestActivityAt, dec.occurredAt)) {
5299
- lines.push(
5300
- "- (no planned tasks or recorded next step \u2014 \u6700\u7D42\u6D3B\u52D5\u306F\u76F4\u8FD1\u306E\u5224\u65AD\u3088\u308A\u5F8C\u3067\u3059\u3002\u7D99\u7D9A\u70B9\u3092\u30E6\u30FC\u30B6\u306B\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044)"
5301
- );
5302
- lines.push(` - \u53C2\u8003 (\u53E4\u3044\u53EF\u80FD\u6027\u30FB\u65B9\u91DD\u3067\u306F\u306A\u3044): ${dec.title}`);
5556
+ lines.push(t.orientation.fallbackStaleDirection);
5557
+ lines.push(` - ${t.orientation.fallbackStaleReferenceLabel}: ${dec.title}`);
5303
5558
  } else {
5304
5559
  lines.push("- (no planned tasks \u2014 direction is inferred from recent decisions)");
5305
- lines.push(` - \u76F4\u8FD1\u306E\u5224\u65AD: ${dec.title}`);
5560
+ lines.push(` - ${t.common.latestDecisionLabel}: ${dec.title}`);
5306
5561
  }
5307
5562
  if (dec !== null) {
5308
- lines.push(
5309
- ' - \u6B21\u306B\u4F5C\u308B\u3079\u304D\u672C\u8CEA\u7684\u306A\u65B9\u5411\u6027\u304C\u5B9A\u307E\u3063\u305F\u3089 `basou decision capture` (`"kind":"track"`) / `basou decision record --track` \u3067 track \u5316\u3059\u308B\u3068\u3001close \u307E\u3067\u6BCE session \u3053\u3053\u306B\u7D99\u7D9A\u8868\u793A\u3055\u308C\u307E\u3059\u3002'
5310
- );
5563
+ lines.push(` - ${t.orientation.trackNudge}`);
5311
5564
  }
5312
5565
  }
5313
5566
  lines.push("");
5314
- lines.push("## \u3053\u308C\u306F\u6700\u65B0\u304B");
5567
+ lines.push(t.orientation.headingCurrency);
5315
5568
  lines.push("");
5316
- for (const line of freshnessVerdict(summary, opts.staleness, now)) lines.push(line);
5569
+ for (const line of freshnessVerdict(summary, opts.staleness, now, t)) lines.push(line);
5317
5570
  if (summary.hosts.length > 0) {
5318
5571
  lines.push("");
5319
- lines.push(
5320
- "\u6CE8: \u9BAE\u5EA6\u5224\u5B9A\u306F\u3053\u306E\u30DE\u30B7\u30F3\u306E\u30ED\u30FC\u30AB\u30EB\u30B9\u30C8\u30A2\u306E\u307F\u304C\u5BFE\u8C61\u3067\u3059\u3002\u4ED6\u30DB\u30B9\u30C8\u306E\u53D6\u308A\u3053\u307C\u3057\u306F\u5224\u5B9A\u3067\u304D\u307E\u305B\u3093(\u5404\u30DB\u30B9\u30C8\u3067 basou refresh \u3092\u5B9F\u884C\u3057\u540C\u671F\u3057\u3066\u304F\u3060\u3055\u3044)\u3002"
5321
- );
5572
+ lines.push(t.orientation.federatedFreshnessNote);
5322
5573
  }
5323
5574
  if (opts.verbose) {
5324
5575
  lines.push("");
@@ -5348,7 +5599,7 @@ function formatOrientationBody(summary, opts) {
5348
5599
  }
5349
5600
  return lines.join("\n");
5350
5601
  }
5351
- function toolDisplayName(kind) {
5602
+ function toolDisplayName(kind, t) {
5352
5603
  switch (kind) {
5353
5604
  case "claude-code-import":
5354
5605
  case "claude-code-adapter":
@@ -5356,98 +5607,61 @@ function toolDisplayName(kind) {
5356
5607
  case "codex-import":
5357
5608
  return "Codex";
5358
5609
  case "terminal":
5359
- return "\u30BF\u30FC\u30DF\u30CA\u30EB";
5610
+ return t.orientation.toolTerminal;
5360
5611
  case "human":
5361
- return "\u624B\u52D5\u30E1\u30E2";
5612
+ return t.orientation.toolHuman;
5362
5613
  case "import":
5363
- return "\u4ED6\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9";
5614
+ return t.orientation.toolImport;
5364
5615
  default:
5365
- return kind ?? "\u4E0D\u660E";
5616
+ return kind ?? t.orientation.toolUnknown;
5366
5617
  }
5367
5618
  }
5368
- function stalenessBanner(staleness) {
5619
+ function stalenessBanner(staleness, t) {
5369
5620
  if (staleness === null) return [];
5370
5621
  if ((staleness.unverifiableSessions ?? 0) > 0) {
5371
- return [
5372
- `> \u26A0\uFE0F **\u518D\u53D6\u308A\u8FBC\u307F\u304C\u5FC5\u8981** \u2014 native \u30ED\u30B0\u304C\u5909\u5316\u3057\u305F\u304C\u901A\u5E38\u306E refresh \u3067\u306F\u53D6\u308A\u8FBC\u3081\u306A\u3044\u30BB\u30C3\u30B7\u30E7\u30F3\u304C ${staleness.unverifiableSessions} \u4EF6\u3042\u308A\u307E\u3059\u3002\`basou refresh --force\` \u3067\u518D\u53D6\u308A\u8FBC\u307F\u3057\u3066\u304F\u3060\u3055\u3044(\u8A73\u7D30\u306F\u672B\u5C3E\u300C\u3053\u308C\u306F\u6700\u65B0\u304B\u300D)\u3002`
5373
- ];
5622
+ return [t.orientation.bannerUnverifiable(staleness.unverifiableSessions ?? 0)];
5374
5623
  }
5375
5624
  if (staleness.newSessions > 0) {
5376
- const parts = [`\u65B0\u898F ${staleness.newSessions} \u4EF6`];
5377
- if (staleness.updatedSessions > 0) parts.push(`\u66F4\u65B0 ${staleness.updatedSessions} \u4EF6`);
5378
- return [
5379
- `> \u26A0\uFE0F **\u53E4\u3044\u3067\u3059\uFF08\u672A\u53D6\u308A\u8FBC\u307F ${parts.join("\u30FB")}\uFF09** \u2014 \u7740\u624B\u524D\u306B\u5FC5\u305A \`basou refresh\` \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044(\u8A73\u7D30\u306F\u672B\u5C3E\u300C\u3053\u308C\u306F\u6700\u65B0\u304B\u300D)\u3002`
5380
- ];
5625
+ const parts = [t.orientation.partNew(staleness.newSessions)];
5626
+ if (staleness.updatedSessions > 0)
5627
+ parts.push(t.orientation.partUpdated(staleness.updatedSessions));
5628
+ return [t.orientation.bannerStale(parts.join(t.orientation.partsJoiner))];
5381
5629
  }
5382
5630
  return [];
5383
5631
  }
5384
- function freshnessVerdict(summary, staleness, now) {
5632
+ function freshnessVerdict(summary, staleness, now, t) {
5385
5633
  if (staleness !== null && (staleness.unverifiableSessions ?? 0) > 0) {
5386
- return [
5387
- `\u26A0\uFE0F native \u30ED\u30B0\u304C\u5909\u5316\u3057\u307E\u3057\u305F\u304C\u3001\u901A\u5E38\u306E \`basou refresh\` \u3067\u306F\u5B89\u5168\u306B\u518D\u53D6\u308A\u8FBC\u307F\u3067\u304D\u306A\u3044\u30BB\u30C3\u30B7\u30E7\u30F3\u304C ${staleness.unverifiableSessions} \u4EF6\u3042\u308A\u307E\u3059(\u975E\u8FFD\u8A18\u5909\u66F4\u30FB\u524D\u30C1\u30A7\u30FC\u30F3\u4E0D\u6574\u5408\u306A\u3069)\u3002`,
5388
- "`basou refresh --force` \u3067\u518D\u53D6\u308A\u8FBC\u307F\u3057\u3066\u304F\u3060\u3055\u3044\u3002(`basou verify` \u306F\u5225\u7269=\u53D6\u308A\u8FBC\u307F\u6E08\u307F\u30C7\u30FC\u30BF\u306E\u6539\u7AC4/\u7834\u640D\u691C\u67FB\u3067\u3001\u30D8\u30C3\u30C0\u306E suspect \u3068\u306F\u5225\u8EF8\u3067\u3059\u3002verify \u304C clean \u3067\u3082\u672A\u53D6\u308A\u8FBC\u307F\u306F\u6B8B\u308A\u5F97\u307E\u3059\u3002)"
5389
- ];
5634
+ return [...t.orientation.verdictUnverifiable(staleness.unverifiableSessions ?? 0)];
5390
5635
  }
5391
5636
  if (staleness !== null && staleness.newSessions > 0) {
5392
- const parts = [`\u65B0\u898F ${staleness.newSessions} \u4EF6`];
5393
- if (staleness.updatedSessions > 0) parts.push(`\u66F4\u65B0 ${staleness.updatedSessions} \u4EF6`);
5394
- return [
5395
- `\u26A0\uFE0F \u53E4\u3044\u3067\u3059\u3002\u6700\u5F8C\u306E\u53D6\u308A\u8FBC\u307F\u4EE5\u964D\u306B\u672A\u53D6\u308A\u8FBC\u307F\u306E\u4F5C\u696D\u304C\u3042\u308A\u307E\u3059(${parts.join("\u30FB")})\u3002`,
5396
- "\u7740\u624B\u524D\u306B\u5FC5\u305A `basou refresh` \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
5397
- ];
5637
+ const parts = [t.orientation.partNew(staleness.newSessions)];
5638
+ if (staleness.updatedSessions > 0)
5639
+ parts.push(t.orientation.partUpdated(staleness.updatedSessions));
5640
+ return [...t.orientation.verdictStale(parts.join(t.orientation.partsJoiner))];
5398
5641
  }
5399
5642
  if (staleness !== null && staleness.updatedSessions > 0) {
5400
- const lines2 = [
5401
- `\u26A0\uFE0F \u66F4\u65B0\u3055\u308C\u305F\u30BB\u30C3\u30B7\u30E7\u30F3\u304C ${staleness.updatedSessions} \u4EF6\u3042\u308A\u307E\u3059\u3002\`basou refresh\` \u3067\u53D6\u308A\u8FBC\u3081\u307E\u3059\u3002`,
5402
- "(\u9032\u884C\u4E2D\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u304C\u3042\u308B\u5834\u5408\u3001\u305D\u308C\u81EA\u8EAB\u306F\u53D6\u308A\u8FBC\u307F\u5F8C\u3082\u5897\u3048\u7D9A\u3051\u308B\u305F\u3081\u6B8B\u308A\u307E\u3059\uFF1D\u6B63\u5E38\u3067\u3059\u3002)"
5403
- ];
5643
+ const lines2 = [...t.orientation.verdictUpdatedOnly(staleness.updatedSessions)];
5404
5644
  if (summary.suspects.length > 0) {
5405
- lines2.push(
5406
- `\u307E\u305F\u8981\u6CE8\u610F\u30BB\u30C3\u30B7\u30E7\u30F3\u304C ${summary.suspects.length} \u4EF6\u3042\u308A\u307E\u3059(\u4E0A\u8A18\u300C\u8981\u6CE8\u610F session\u300D\u53C2\u7167)\u3002`
5407
- );
5645
+ lines2.push(t.orientation.verdictSuspectsAlso(summary.suspects.length));
5408
5646
  }
5409
5647
  return lines2;
5410
5648
  }
5411
5649
  if (summary.freshness.newestStartedAt === null) {
5412
- return [
5413
- "\u2139\uFE0F \u307E\u3060\u8A18\u9332\u304C\u3042\u308A\u307E\u305B\u3093\u3002",
5414
- "\u3053\u306E\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u3067\u4F5C\u696D\u3059\u308B\u3068\u3001\u3053\u3053\u306B\u73FE\u5728\u5730\u304C\u8868\u793A\u3055\u308C\u307E\u3059\u3002"
5415
- ];
5650
+ return [...t.orientation.verdictEmpty];
5416
5651
  }
5417
- const rel = relativeAgeJa(summary.freshness.newestStartedAt, now);
5418
- const tool = toolDisplayName(summary.freshness.newestSource);
5652
+ const rel = t.relativeAge(summary.freshness.newestStartedAt, now);
5653
+ const tool = toolDisplayName(summary.freshness.newestSource, t);
5419
5654
  const suspectCount = summary.suspects.length;
5420
5655
  if (staleness === null) {
5421
- return [
5422
- `\u2139\uFE0F \u53D6\u308A\u8FBC\u307F\u6E08\u307F\u306E\u72B6\u614B\u3092\u8868\u793A\u3057\u3066\u3044\u307E\u3059\u3002\u6700\u5F8C\u306E\u4F5C\u696D\u306F ${rel}(${tool})\u3002`,
5423
- "\u6700\u65B0\u304B\u78BA\u8A8D\u3059\u308B\u306B\u306F `basou refresh` \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
5424
- ];
5656
+ return [...t.orientation.verdictUnprobed(rel, tool)];
5425
5657
  }
5426
- const localScope = summary.hosts.length > 0 ? "\u3053\u306E\u30DB\u30B9\u30C8(\u30ED\u30FC\u30AB\u30EB)\u306E" : "";
5427
- const lines = [
5428
- `\u2705 ${localScope}\u53D6\u308A\u8FBC\u307F\u306F\u6700\u65B0\u3067\u3059\u3002\u6700\u5F8C\u306E\u4F5C\u696D\u306F ${rel}(${tool})\u3002\u672A\u53D6\u308A\u8FBC\u307F\u306E native \u30BB\u30C3\u30B7\u30E7\u30F3\u306F\u3042\u308A\u307E\u305B\u3093\u3002`
5429
- ];
5658
+ const lines = [t.orientation.verdictCurrent(rel, tool, summary.hosts.length > 0)];
5430
5659
  if (suspectCount > 0) {
5431
- lines.push(`\u305F\u3060\u3057\u8981\u6CE8\u610F\u30BB\u30C3\u30B7\u30E7\u30F3\u304C ${suspectCount} \u4EF6\u3042\u308A\u307E\u3059(\u4E0A\u8A18\u300C\u8981\u6CE8\u610F session\u300D\u53C2\u7167)\u3002`);
5660
+ lines.push(t.orientation.verdictSuspectsCaveat(suspectCount));
5432
5661
  }
5433
- lines.push(
5434
- "\u6CE8: \u3053\u306E\u5224\u5B9A\u306F\u53D6\u308A\u8FBC\u307F\u6E08\u307F native \u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u9BAE\u5EA6\u3068 suspect \u306E\u6709\u7121\u3060\u3051\u3092\u898B\u307E\u3059\u3002\u8A08\u753B\u2194\u5B9F\u88C5\u306E\u30C9\u30EA\u30D5\u30C8\u3084\u672A\u8A18\u9332\u306E\u610F\u601D\u6C7A\u5B9A\u307E\u3067\u306F\u691C\u77E5\u3057\u307E\u305B\u3093\u3002"
5435
- );
5662
+ lines.push(t.orientation.verdictScopeDisclaimer);
5436
5663
  return lines;
5437
5664
  }
5438
- function relativeAgeJa(startedAt, now) {
5439
- if (startedAt === null) return "(\u4E0D\u660E)";
5440
- const ms = now.getTime() - Date.parse(startedAt);
5441
- if (!Number.isFinite(ms) || ms < 0) return "\u305F\u3063\u305F\u4ECA";
5442
- if (ms < 6e4) return "\u305F\u3063\u305F\u4ECA";
5443
- const totalMin = Math.floor(ms / 6e4);
5444
- const days = Math.floor(totalMin / 1440);
5445
- const hours = Math.floor(totalMin % 1440 / 60);
5446
- const mins = totalMin % 60;
5447
- if (days > 0) return hours > 0 ? `${days}\u65E5${hours}\u6642\u9593\u524D` : `${days}\u65E5\u524D`;
5448
- if (hours > 0) return mins > 0 ? `${hours}\u6642\u9593${mins}\u5206\u524D` : `${hours}\u6642\u9593\u524D`;
5449
- return `${mins}\u5206\u524D`;
5450
- }
5451
5665
  function relativeAge(startedAt, now) {
5452
5666
  if (startedAt === void 0) return "(unknown)";
5453
5667
  const ms = now.getTime() - Date.parse(startedAt);
@@ -5540,29 +5754,6 @@ function renderAnchorStarter(input) {
5540
5754
  `;
5541
5755
  }
5542
5756
 
5543
- // src/project/relative-path.ts
5544
- function normalizeRelativePath(p) {
5545
- const trimmed = p.trim();
5546
- const absolute = trimmed.startsWith("/");
5547
- const out = [];
5548
- for (const seg of trimmed.split("/")) {
5549
- if (seg === "" || seg === ".") continue;
5550
- if (seg === "..") {
5551
- const top = out[out.length - 1];
5552
- if (top !== void 0 && top !== "..") {
5553
- out.pop();
5554
- } else if (!absolute) {
5555
- out.push("..");
5556
- }
5557
- continue;
5558
- }
5559
- out.push(seg);
5560
- }
5561
- const joined = out.join("/");
5562
- if (absolute) return `/${joined}`;
5563
- return joined.length === 0 ? "." : joined;
5564
- }
5565
-
5566
5757
  // src/project/archive.ts
5567
5758
  function planArchive(input) {
5568
5759
  const target = normalizeRelativePath(input.target);
@@ -6733,7 +6924,8 @@ async function renderReport(input) {
6733
6924
  changedFiles,
6734
6925
  integrity
6735
6926
  };
6736
- return { body: formatReportBody(data), data };
6927
+ const language = input.language ?? await resolveViewLanguageFromPaths(input.paths);
6928
+ return { body: formatReportBody(data, viewStrings(language)), data };
6737
6929
  }
6738
6930
  function computePeriod(entries, nowIso) {
6739
6931
  if (entries.length === 0) return { from: null, to: null };
@@ -6760,7 +6952,7 @@ function tallyTaskStatus(items) {
6760
6952
  count: counts.get(status)
6761
6953
  }));
6762
6954
  }
6763
- function formatReportBody(data) {
6955
+ function formatReportBody(data, t) {
6764
6956
  const lines = [];
6765
6957
  const titleSuffix = data.title !== void 0 ? ` \u2014 ${data.title}` : "";
6766
6958
  lines.push(`# Report${titleSuffix}`);
@@ -6768,14 +6960,14 @@ function formatReportBody(data) {
6768
6960
  const periodSuffix = data.period.from !== null && data.period.to !== null ? ` (${data.period.from.slice(0, 10)}..${data.period.to.slice(0, 10)})` : "";
6769
6961
  lines.push(`> Generated at ${data.generatedAt}${periodSuffix}`);
6770
6962
  lines.push("");
6771
- lines.push("## \u6982\u8981");
6963
+ lines.push(t.report.headingSummary);
6772
6964
  lines.push("");
6773
6965
  lines.push(`- ${formatSessionsLine(data)}`);
6774
6966
  lines.push(
6775
6967
  `- Active time ${formatDurationMs(data.time.activeMs)}, ${formatInt(data.volume.outputTokens)} output tokens`
6776
6968
  );
6777
6969
  lines.push("");
6778
- lines.push("## \u4F5C\u696D\u91CF");
6970
+ lines.push(t.report.headingVolume);
6779
6971
  lines.push("");
6780
6972
  const tokenCaveat = data.volume.tokensAvailable ? "" : " (no token data captured)";
6781
6973
  lines.push(`- Output tokens: ${formatInt(data.volume.outputTokens)}${tokenCaveat}`);
@@ -6795,7 +6987,7 @@ function formatReportBody(data) {
6795
6987
  }
6796
6988
  lines.push(`- Span: ${formatDurationMs(data.time.spanMs)} (total elapsed)`);
6797
6989
  lines.push("");
6798
- lines.push("## \u5224\u65AD");
6990
+ lines.push(t.report.headingDecisions);
6799
6991
  lines.push("");
6800
6992
  if (data.decisions.items.length === 0) {
6801
6993
  lines.push("(no decisions recorded yet)");
@@ -6813,7 +7005,7 @@ function formatReportBody(data) {
6813
7005
  }
6814
7006
  }
6815
7007
  lines.push("");
6816
- lines.push("## \u627F\u8A8D");
7008
+ lines.push(t.report.headingApprovals);
6817
7009
  lines.push("");
6818
7010
  if (data.approvals.items.length === 0) {
6819
7011
  lines.push("(none)");
@@ -6830,7 +7022,7 @@ function formatReportBody(data) {
6830
7022
  if (overflow > 0) lines.push(`- ... +${overflow} more`);
6831
7023
  }
6832
7024
  lines.push("");
6833
- lines.push("## \u30BF\u30B9\u30AF");
7025
+ lines.push(t.report.headingTasks);
6834
7026
  lines.push("");
6835
7027
  if (data.tasks.items.length === 0) {
6836
7028
  lines.push("(no tasks recorded yet)");
@@ -6845,7 +7037,7 @@ function formatReportBody(data) {
6845
7037
  if (overflow > 0) lines.push(`- ... +${overflow} more`);
6846
7038
  }
6847
7039
  lines.push("");
6848
- lines.push("## \u5909\u66F4\u30D5\u30A1\u30A4\u30EB");
7040
+ lines.push(t.report.headingChangedFiles);
6849
7041
  lines.push("");
6850
7042
  if (data.changedFiles.length === 0) {
6851
7043
  lines.push("(no related files recorded)");
@@ -6855,7 +7047,7 @@ function formatReportBody(data) {
6855
7047
  if (overflow > 0) lines.push(`- ... +${overflow} more`);
6856
7048
  }
6857
7049
  lines.push("");
6858
- lines.push("## \u30BB\u30C3\u30B7\u30E7\u30F3\u4E00\u89A7");
7050
+ lines.push(t.report.headingSessions);
6859
7051
  lines.push("");
6860
7052
  if (data.sessions.items.length === 0) {
6861
7053
  lines.push("(no sessions yet)");
@@ -6874,7 +7066,7 @@ function formatReportBody(data) {
6874
7066
  }
6875
7067
  }
6876
7068
  lines.push("");
6877
- lines.push("## \u6574\u5408\u6027");
7069
+ lines.push(t.report.headingIntegrity);
6878
7070
  lines.push("");
6879
7071
  const i = data.integrity;
6880
7072
  lines.push(
@@ -8338,6 +8530,8 @@ export {
8338
8530
  resolveRepositoryRoot,
8339
8531
  resolveSessionId,
8340
8532
  resolveTaskId,
8533
+ resolveViewLanguage,
8534
+ resolveViewLanguageFromPaths,
8341
8535
  safeSimpleGit,
8342
8536
  sanitizePath,
8343
8537
  sanitizeRelatedFiles,
@@ -8359,6 +8553,7 @@ export {
8359
8553
  updateTaskStatusWithEvent,
8360
8554
  upsertStopHook,
8361
8555
  verifyEventsChain,
8556
+ viewStrings,
8362
8557
  writeEventsBulk,
8363
8558
  writeManifest,
8364
8559
  writeMarkdownFile,