@basou/cli 0.12.0 → 0.13.1

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
@@ -1443,6 +1443,7 @@ async function doRunImportClaudeCode(options, ctx) {
1443
1443
  });
1444
1444
  const projectsRoot = ctx.claudeProjectsDir ?? join3(homedir2(), ".claude", "projects");
1445
1445
  const files = await selectTranscriptFiles(projectsRoot, projectPaths, options);
1446
+ const projectSet = new Set(projectPaths);
1446
1447
  const candidates = files.map((file) => {
1447
1448
  const externalId = basename(file, ".jsonl");
1448
1449
  return {
@@ -1450,6 +1451,8 @@ async function doRunImportClaudeCode(options, ctx) {
1450
1451
  sourcePath: file,
1451
1452
  toPayload: async () => {
1452
1453
  const { records, sizeBytes } = await readJsonlRecords(file);
1454
+ const cwd = firstTranscriptCwd(records);
1455
+ if (cwd === void 0 || !projectSet.has(cwd)) return null;
1453
1456
  return claudeTranscriptToImportPayload(records, {
1454
1457
  workspaceId: manifest.workspace.id,
1455
1458
  externalId,
@@ -1623,7 +1626,14 @@ async function classifyReimport(priors, sourcePath, externalId, counts) {
1623
1626
  return prior;
1624
1627
  }
1625
1628
  function encodeProjectDir(projectPath) {
1626
- return projectPath.replaceAll("/", "-");
1629
+ return projectPath.replace(/[^a-zA-Z0-9]/g, "-");
1630
+ }
1631
+ function firstTranscriptCwd(records) {
1632
+ for (const record of records) {
1633
+ const cwd = record.cwd;
1634
+ if (typeof cwd === "string" && cwd.length > 0) return cwd;
1635
+ }
1636
+ return void 0;
1627
1637
  }
1628
1638
  async function loadExistingByExternalId(paths, sourceKind) {
1629
1639
  const byExternalId = /* @__PURE__ */ new Map();
@@ -2008,11 +2018,177 @@ async function resolveRepositoryRootForInit(cwd) {
2008
2018
  }
2009
2019
  }
2010
2020
 
2011
- // src/commands/orient.ts
2021
+ // src/commands/note.ts
2012
2022
  import {
2023
+ acquireLock as acquireLock4,
2024
+ appendEventToExistingSession as appendEventToExistingSession2,
2013
2025
  assertBasouRootSafe as assertBasouRootSafe7,
2014
2026
  basouPaths as basouPaths7,
2027
+ createAdHocSessionWithEvent as createAdHocSessionWithEvent2,
2015
2028
  findErrorCode as findErrorCode6,
2029
+ readManifest as readManifest4,
2030
+ resolveSessionId as resolveSessionId2
2031
+ } from "@basou/core";
2032
+ import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
2033
+
2034
+ // src/lib/repo-root.ts
2035
+ import { resolveBasouRepositoryRoot } from "@basou/core";
2036
+ async function resolveBasouRootForCommand(cwd, commandName) {
2037
+ try {
2038
+ return await resolveBasouRepositoryRoot(cwd, {
2039
+ onRedirect: ({ via, root }) => console.error(`Resolved workspace view to ${root} (via ${via}).`)
2040
+ });
2041
+ } catch (error) {
2042
+ if (error instanceof Error && error.message === "Not a git repository") {
2043
+ throw new Error(
2044
+ `Not a git repository. Run 'git init' first, then re-run 'basou ${commandName}'.`,
2045
+ { cause: error }
2046
+ );
2047
+ }
2048
+ throw error;
2049
+ }
2050
+ }
2051
+
2052
+ // src/commands/note.ts
2053
+ var LABEL_BODY_MAX = 80;
2054
+ var LABEL_TRUNCATE_HEAD2 = LABEL_BODY_MAX - 3;
2055
+ function registerNoteCommand(program2) {
2056
+ program2.command("note").description("Record a free-text note (orientation surfaces the latest as the next step)").argument("<body>", "Note text", parseBody).option(
2057
+ "--session <session_id>",
2058
+ "Attach to an existing session; otherwise an ad-hoc session is created"
2059
+ ).option("--json", "Output the result as JSON").option("-v, --verbose", "Show error causes").action(async (body, options) => {
2060
+ await runNote(body, options);
2061
+ });
2062
+ }
2063
+ async function runNote(body, options, ctx = {}) {
2064
+ try {
2065
+ await doRunNote(body, options, ctx);
2066
+ } catch (error) {
2067
+ renderCliError(error, {
2068
+ verbose: isVerbose(options),
2069
+ classifiers: [failedToFinalizeClassifier]
2070
+ });
2071
+ process.exitCode = 1;
2072
+ }
2073
+ }
2074
+ async function doRunNote(body, options, ctx) {
2075
+ if (body.trim().length === 0) {
2076
+ throw new Error("Note body must not be empty");
2077
+ }
2078
+ const cwd = ctx.cwd ?? process.cwd();
2079
+ const repositoryRoot = await resolveBasouRootForCommand(cwd, "note");
2080
+ const paths = basouPaths7(repositoryRoot);
2081
+ await assertWorkspaceInitialized6(paths.root);
2082
+ const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
2083
+ const occurredAt = now.toISOString();
2084
+ if (options.session !== void 0) {
2085
+ const sessionId = await resolveSessionId2(paths, options.session);
2086
+ const sesId = sessionId;
2087
+ const sessionLock = await acquireLock4(paths, "session", sesId);
2088
+ let result;
2089
+ try {
2090
+ result = await appendEventToExistingSession2({
2091
+ paths,
2092
+ sessionId: sesId,
2093
+ eventBuilder: (eventId) => buildNoteEvent({ eventId, sessionId: sesId, occurredAt, body })
2094
+ });
2095
+ } finally {
2096
+ await sessionLock.release();
2097
+ }
2098
+ printNoteResult(options, {
2099
+ mode: "attached",
2100
+ sessionId,
2101
+ eventId: result.eventId,
2102
+ sessionStatus: result.sessionStatus,
2103
+ body
2104
+ });
2105
+ return;
2106
+ }
2107
+ const manifest = await readManifest4(paths);
2108
+ const adHoc = await createAdHocSessionWithEvent2({
2109
+ paths,
2110
+ manifest,
2111
+ label: buildAdHocLabel2(body),
2112
+ occurredAt,
2113
+ sessionSource: "human",
2114
+ workingDirectory: repositoryRoot,
2115
+ invocation: {
2116
+ command: "basou note",
2117
+ args: [body]
2118
+ },
2119
+ targetEventBuilders: [
2120
+ (sessionId, eventId) => buildNoteEvent({ eventId, sessionId, occurredAt, body })
2121
+ ]
2122
+ });
2123
+ printNoteResult(options, {
2124
+ mode: "ad-hoc",
2125
+ sessionId: adHoc.sessionId,
2126
+ eventId: adHoc.targetEventIds[0],
2127
+ sessionStatus: "completed",
2128
+ body
2129
+ });
2130
+ }
2131
+ function buildNoteEvent(input) {
2132
+ return {
2133
+ schema_version: "0.1.0",
2134
+ id: input.eventId,
2135
+ session_id: input.sessionId,
2136
+ occurred_at: input.occurredAt,
2137
+ source: "local-cli",
2138
+ type: "note_added",
2139
+ body: input.body,
2140
+ // `basou note` is the resume-hint command; mark it so orientation surfaces
2141
+ // it as the next step and a plain `basou session note` annotation does not.
2142
+ kind: "next_step"
2143
+ };
2144
+ }
2145
+ function buildAdHocLabel2(body) {
2146
+ const oneLine = body.replace(/\s+/g, " ").trim();
2147
+ const truncated = oneLine.length > LABEL_BODY_MAX ? `${oneLine.slice(0, LABEL_TRUNCATE_HEAD2)}...` : oneLine;
2148
+ return `Ad-hoc note: ${truncated}`;
2149
+ }
2150
+ function parseBody(raw) {
2151
+ if (raw.trim().length === 0) {
2152
+ throw new InvalidArgumentError2("Note body must not be empty");
2153
+ }
2154
+ return raw;
2155
+ }
2156
+ function printNoteResult(options, result) {
2157
+ const sid = shortSessionId(result.sessionId);
2158
+ if (options.json === true) {
2159
+ console.log(
2160
+ JSON.stringify({
2161
+ event_id: result.eventId,
2162
+ session_id: result.sessionId,
2163
+ session_status: result.sessionStatus,
2164
+ mode: result.mode,
2165
+ body: result.body
2166
+ })
2167
+ );
2168
+ return;
2169
+ }
2170
+ if (result.mode === "ad-hoc") {
2171
+ console.log(`Recorded note ${result.eventId} in ad-hoc session ${sid}`);
2172
+ } else {
2173
+ console.log(`Recorded note ${result.eventId} in session ${sid} (${result.sessionStatus})`);
2174
+ }
2175
+ }
2176
+ async function assertWorkspaceInitialized6(basouRoot) {
2177
+ try {
2178
+ await assertBasouRootSafe7(basouRoot);
2179
+ } catch (error) {
2180
+ if (findErrorCode6(error, "ENOENT")) {
2181
+ throw new Error("Workspace not initialized. Run 'basou init' first.");
2182
+ }
2183
+ throw error;
2184
+ }
2185
+ }
2186
+
2187
+ // src/commands/orient.ts
2188
+ import {
2189
+ assertBasouRootSafe as assertBasouRootSafe8,
2190
+ basouPaths as basouPaths8,
2191
+ findErrorCode as findErrorCode7,
2016
2192
  renderOrientation as renderOrientation2,
2017
2193
  writeMarkdownFile as writeMarkdownFile4
2018
2194
  } from "@basou/core";
@@ -2199,24 +2375,6 @@ async function probeStaleness(args) {
2199
2375
  }
2200
2376
  }
2201
2377
 
2202
- // src/lib/repo-root.ts
2203
- import { resolveBasouRepositoryRoot } from "@basou/core";
2204
- async function resolveBasouRootForCommand(cwd, commandName) {
2205
- try {
2206
- return await resolveBasouRepositoryRoot(cwd, {
2207
- onRedirect: ({ via, root }) => console.error(`Resolved workspace view to ${root} (via ${via}).`)
2208
- });
2209
- } catch (error) {
2210
- if (error instanceof Error && error.message === "Not a git repository") {
2211
- throw new Error(
2212
- `Not a git repository. Run 'git init' first, then re-run 'basou ${commandName}'.`,
2213
- { cause: error }
2214
- );
2215
- }
2216
- throw error;
2217
- }
2218
- }
2219
-
2220
2378
  // src/commands/orient.ts
2221
2379
  function registerOrientCommand(program2) {
2222
2380
  program2.command("orient").description("Show the workspace's current position (also writes .basou/orientation.md)").option("-q, --quiet", "Write the file without printing the body").option("-v, --verbose", "Show error causes").action(async (opts) => {
@@ -2234,8 +2392,8 @@ async function runOrient(options, ctx = {}) {
2234
2392
  async function doRunOrient(options, ctx) {
2235
2393
  const cwd = ctx.cwd ?? process.cwd();
2236
2394
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "orient");
2237
- const paths = basouPaths7(repositoryRoot);
2238
- await assertWorkspaceInitialized6(paths.root);
2395
+ const paths = basouPaths8(repositoryRoot);
2396
+ await assertWorkspaceInitialized7(paths.root);
2239
2397
  const nowIso = (ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()).toISOString();
2240
2398
  const probeCtx = { cwd: repositoryRoot };
2241
2399
  if (ctx.claudeProjectsDir !== void 0) probeCtx.claudeProjectsDir = ctx.claudeProjectsDir;
@@ -2260,104 +2418,1781 @@ async function doRunOrient(options, ctx) {
2260
2418
  console.log(result.body);
2261
2419
  }
2262
2420
  }
2263
- async function assertWorkspaceInitialized6(basouRoot) {
2421
+ async function assertWorkspaceInitialized7(basouRoot) {
2264
2422
  try {
2265
- await assertBasouRootSafe7(basouRoot);
2423
+ await assertBasouRootSafe8(basouRoot);
2266
2424
  } catch (error) {
2267
- if (findErrorCode6(error, "ENOENT")) {
2425
+ if (findErrorCode7(error, "ENOENT")) {
2268
2426
  throw new Error("Workspace not initialized. Run 'basou init' first.");
2269
2427
  }
2270
2428
  throw error;
2271
2429
  }
2272
2430
  }
2273
2431
 
2274
- // src/commands/refresh.ts
2275
- import { assertBasouRootSafe as assertBasouRootSafe8, basouPaths as basouPaths8, findErrorCode as findErrorCode8 } from "@basou/core";
2276
- import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
2277
-
2278
- // src/lib/portfolio-config.ts
2279
- import { homedir as homedir3 } from "os";
2280
- import { isAbsolute, join as join4, resolve as resolve3 } from "path";
2281
- import { readYamlFile as readYamlFile3 } from "@basou/core";
2282
- var DEFAULT_PORTFOLIO_CONFIG_PATH = join4(homedir3(), ".basou", "portfolio.yaml");
2283
- function expandTilde(p) {
2284
- if (p === "~") return homedir3();
2285
- if (p.startsWith("~/")) return join4(homedir3(), p.slice(2));
2286
- return p;
2287
- }
2288
- function isRecord(value) {
2289
- return typeof value === "object" && value !== null && !Array.isArray(value);
2432
+ // src/commands/project.ts
2433
+ import {
2434
+ existsSync,
2435
+ lstatSync,
2436
+ mkdirSync,
2437
+ readdirSync,
2438
+ readFileSync,
2439
+ readlinkSync,
2440
+ realpathSync,
2441
+ statSync,
2442
+ symlinkSync,
2443
+ unlinkSync,
2444
+ writeFileSync
2445
+ } from "fs";
2446
+ import { basename as basename3, dirname, isAbsolute, join as join4, relative as relative2, resolve as resolve3 } from "path";
2447
+ import {
2448
+ basouPaths as basouPaths9,
2449
+ GENERATED_END,
2450
+ GENERATED_START,
2451
+ isGitNotFound,
2452
+ parseMarkers,
2453
+ pathBasename,
2454
+ planArchive,
2455
+ planGitignore,
2456
+ planRename,
2457
+ planRosterAdoption,
2458
+ planWorkspaceView,
2459
+ readManifest as readManifest5,
2460
+ readMarkdownFile as readMarkdownFile4,
2461
+ reconcileSourceRoots,
2462
+ renderWithMarkers as renderWithMarkers4,
2463
+ safeSimpleGit,
2464
+ summarizePresetPlan,
2465
+ summarizeRosterDrift,
2466
+ summarizeSymlinkPlan,
2467
+ summarizeWiring,
2468
+ unknownManifestKeys,
2469
+ writeManifest as writeManifest2,
2470
+ writeMarkdownFile as writeMarkdownFile5
2471
+ } from "@basou/core";
2472
+ var INSTRUCTION_FILES = ["AGENTS.md", "CLAUDE.md", ".github/copilot-instructions.md"];
2473
+ var CANONICAL_FILE = "AGENTS.md";
2474
+ function registerProjectCommand(program2) {
2475
+ const project = program2.command("project").description("Inspect a project's declared repo roster (read-only)");
2476
+ project.command("check").description(
2477
+ "Compare the declared repo roster (manifest `repos`) against the capture config (`source_roots`) and surface drift (read-only, advisory)"
2478
+ ).option("--json", "Output the result as JSON").option("-v, --verbose", "Show error causes").action(async (opts) => {
2479
+ await runProjectCheck(opts);
2480
+ });
2481
+ project.command("sync").description(
2482
+ "Reconcile the capture config (`source_roots`) to cover every declared repo (manifest `repos`). Dry-run by default; pass --apply to write. Additive only \u2014 it never removes an existing source root (e.g. the workspace view)"
2483
+ ).option(
2484
+ "--apply",
2485
+ "Write the reconciled source_roots to the manifest (default: dry-run preview)"
2486
+ ).option("--json", "Output the result as JSON").option("-v, --verbose", "Show error causes").action(async (opts) => {
2487
+ await runProjectSync(opts);
2488
+ });
2489
+ project.command("adopt").description(
2490
+ "Bootstrap a repo roster (manifest `repos`) from the existing capture config (`source_roots`): classify each by realpath + `.git`, keep the git repos, exclude non-repos (the workspace view, /tmp). Dry-run by default; pass --apply to write (refuses if a roster already exists)"
2491
+ ).option("--apply", "Write the bootstrapped roster to the manifest (default: dry-run preview)").option("--json", "Output the result as JSON").option("-v, --verbose", "Show error causes").action(async (opts) => {
2492
+ await runProjectAdopt(opts);
2493
+ });
2494
+ project.command("wiring").description(
2495
+ "Inspect each declared repo's agent instruction-file wiring (AGENTS.md, CLAUDE.md, copilot-instructions.md): present? tracked by git? Surfaces privacy risks (a public repo tracking an instruction file) and gaps (read-only, advisory)"
2496
+ ).option("--json", "Output the result as JSON").option("-v, --verbose", "Show error causes").action(async (opts) => {
2497
+ await runProjectWiring(opts);
2498
+ });
2499
+ project.command("gitignore").description(
2500
+ "Reconcile each public-facing repo's .gitignore to exclude the agent instruction files (so the gitignored symlinks never enter public history). Dry-run by default; pass --apply to write. Additive only \u2014 it never removes a line; private repos and unset-visibility repos are left untouched"
2501
+ ).option(
2502
+ "--apply",
2503
+ "Append the missing patterns to each repo's .gitignore (default: dry-run preview)"
2504
+ ).option("--json", "Output the result as JSON").option("-v, --verbose", "Show error causes").action(async (opts) => {
2505
+ await runProjectGitignore(opts);
2506
+ });
2507
+ project.command("symlinks").description(
2508
+ "Generate each declared repo's agent instruction-file symlinks (AGENTS.md, CLAUDE.md, copilot-instructions.md) pointing at the project anchor's canonical (agents/<repo>/AGENTS.md). Dry-run by default; pass --apply to create. Non-destructive \u2014 it only creates missing links and never overwrites an existing file or repoints a link"
2509
+ ).option("--apply", "Create the missing instruction-file symlinks (default: dry-run preview)").option("--json", "Output the result as JSON").option("-v, --verbose", "Show error causes").action(async (opts) => {
2510
+ await runProjectSymlinks(opts);
2511
+ });
2512
+ project.command("workspace").description(
2513
+ "Generate the project's workspace view: a directory (manifest `workspace.view`) that aggregates every declared repo via a `<repo-basename>` symlink (the anchor included). Dry-run by default; pass --apply to create missing links. Creation is non-destructive \u2014 it never overwrites an existing entry or repoints a link. Stray repo links (a view symlink whose repo is no longer in the roster) are reported always and removed only with --prune; pruning removes ONLY a symlink whose relative target resolves to a git repository (never a real file/dir, the view's own instruction files, a broken link, or a non-repo target), and never the linked repo itself"
2514
+ ).option("--apply", "Create the missing view symlinks (default: dry-run preview)").option(
2515
+ "--prune",
2516
+ "Remove stray repo symlinks (links the roster no longer backs); default: dry-run preview. Independent of --apply"
2517
+ ).option("--json", "Output the result as JSON").option("-v, --verbose", "Show error causes").action(async (opts) => {
2518
+ await runProjectWorkspace(opts);
2519
+ });
2520
+ project.command("preset").description(
2521
+ "Generate the stable-preset block (source visibility, source language, published surfaces) of each declared repo's canonical instruction file (agents/<repo>/AGENTS.md) from the manifest. Dry-run by default; pass --apply to write. Non-destructive \u2014 it only writes the marker-delimited region (creating an absent canonical, updating an out-of-date one) and never touches hand-authored content or a canonical whose markers are missing/malformed"
2522
+ ).option(
2523
+ "--apply",
2524
+ "Write the generated preset block to each canonical (default: dry-run preview)"
2525
+ ).option("--json", "Output the result as JSON").option("-v, --verbose", "Show error causes").action(async (opts) => {
2526
+ await runProjectPreset(opts);
2527
+ });
2528
+ project.command("archive").argument("<repo>", "The roster repo path to archive (as declared, e.g. ../takuhon)").description(
2529
+ "Fold a repo out of the project: remove it from the declared roster (manifest `repos`) and prune its capture entry (`source_roots`). Dry-run by default; pass --apply to write. Manifest-only and reversible (the manifest is git-tracked); it never deletes the repo, its captured history, or its on-disk wiring (view symlink / instruction symlinks / .gitignore / canonical) \u2014 those are reported as a manual teardown checklist. Archiving the anchor (`.`) is refused"
2530
+ ).option(
2531
+ "--apply",
2532
+ "Write the pruned roster / source_roots to the manifest (default: dry-run preview)"
2533
+ ).option("--json", "Output the result as JSON").option("-v, --verbose", "Show error causes").action(async (repo, opts) => {
2534
+ await runProjectArchive(repo, opts);
2535
+ });
2536
+ project.command("rename").argument("<old>", "The current roster repo path (as declared, e.g. ../takuhon)").argument("<new>", "The new roster repo path (e.g. ../takuhon-cli)").description(
2537
+ "Re-path a repo in the project: update its declared roster path (manifest `repos`) and its capture entry (`source_roots`). Dry-run by default; pass --apply to write. Manifest-only and reversible (the manifest is git-tracked); it does not move the repo on disk or rewire it \u2014 when the basename changes, the anchor canonical dir and view symlink that still use the old name are reported as a manual checklist (re-run `basou project symlinks` / `workspace` after). Renaming the anchor (`.`) or onto an existing entry is refused"
2538
+ ).option(
2539
+ "--apply",
2540
+ "Write the re-pathed roster / source_roots to the manifest (default: dry-run preview)"
2541
+ ).option("--json", "Output the result as JSON").option("-v, --verbose", "Show error causes").action(async (oldPath, newPath, opts) => {
2542
+ await runProjectRename(oldPath, newPath, opts);
2543
+ });
2290
2544
  }
2291
- async function loadPortfolioConfig(configPath = DEFAULT_PORTFOLIO_CONFIG_PATH) {
2292
- let raw;
2545
+ async function runProjectCheck(options, ctx = {}) {
2293
2546
  try {
2294
- raw = await readYamlFile3(configPath);
2547
+ await doRunProjectCheck(options, ctx);
2295
2548
  } catch (error) {
2296
- if (error instanceof Error && error.message === "YAML file not found") {
2297
- throw new Error(
2298
- "No portfolio config at ~/.basou/portfolio.yaml. Create one (a 'workspaces:' list of repo paths) or pass --workspace <path>."
2299
- );
2300
- }
2301
- if (error instanceof Error && error.message === "Failed to parse YAML content") {
2302
- throw new Error("~/.basou/portfolio.yaml is not valid YAML.");
2303
- }
2304
- throw error;
2549
+ renderCliError(error, { verbose: isVerbose(options) });
2550
+ process.exitCode = 1;
2305
2551
  }
2306
- if (!isRecord(raw) || !Array.isArray(raw.workspaces)) {
2307
- throw new Error("~/.basou/portfolio.yaml must contain a 'workspaces:' list.");
2552
+ }
2553
+ function effectiveSourceRoots(manifest) {
2554
+ return manifest.import?.source_roots ?? ["."];
2555
+ }
2556
+ function preservedUnknownLines(fields) {
2557
+ if (fields.length === 0) return [];
2558
+ return [
2559
+ `\u2139\uFE0F basou \u304C\u8A8D\u8B58\u3057\u306A\u3044 manifest \u306E\u30C8\u30C3\u30D7\u30EC\u30D9\u30EB\u30D5\u30A3\u30FC\u30EB\u30C9\u3092 ${fields.length} \u4EF6\u4FDD\u6301\u3057\u3066\u3044\u307E\u3059(write \u6642\u3082\u524A\u9664\u3057\u307E\u305B\u3093): ${fields.join(", ")}`,
2560
+ ""
2561
+ ];
2562
+ }
2563
+ async function doRunProjectCheck(options, ctx) {
2564
+ const cwd = ctx.cwd ?? process.cwd();
2565
+ const repositoryRoot = await resolveBasouRootForCommand(cwd, "project check");
2566
+ const paths = basouPaths9(repositoryRoot);
2567
+ const manifest = await readManifest5(paths);
2568
+ const summary = summarizeRosterDrift({
2569
+ ...manifest.repos !== void 0 ? { repos: manifest.repos } : {},
2570
+ sourceRoots: effectiveSourceRoots(manifest)
2571
+ });
2572
+ if (options.json === true) {
2573
+ console.log(JSON.stringify(summary));
2574
+ } else {
2575
+ console.log(renderProjectCheck(summary));
2308
2576
  }
2309
- const seen = /* @__PURE__ */ new Set();
2310
- const result = [];
2311
- for (const entry of raw.workspaces) {
2312
- if (!isRecord(entry) || typeof entry.path !== "string" || entry.path.trim().length === 0) {
2313
- throw new Error("Each portfolio workspace needs a non-empty string 'path'.");
2314
- }
2315
- if (entry.label !== void 0 && typeof entry.label !== "string") {
2316
- throw new Error("A portfolio workspace 'label' must be a string when present.");
2577
+ return summary;
2578
+ }
2579
+ function renderProjectCheck(summary) {
2580
+ const lines = [];
2581
+ lines.push("# \u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u69CB\u6210\u30C1\u30A7\u30C3\u30AF(\u5BA3\u8A00 vs \u6355\u6349)");
2582
+ lines.push("");
2583
+ if (summary.declaredCount === 0) {
2584
+ lines.push(
2585
+ "\u2139\uFE0F repo \u30ED\u30FC\u30B9\u30BF\u30FC\u304C\u672A\u5BA3\u8A00\u3067\u3059(manifest \u306E `repos`)\u3002`source_roots` \u306E\u307F\u3067\u904B\u7528\u4E2D\u306E\u305F\u3081\u3001\u5BA3\u8A00\u3068\u306E\u7167\u5408\u306F\u3067\u304D\u307E\u305B\u3093\u3002"
2586
+ );
2587
+ if (summary.extra.length > 0) {
2588
+ lines.push("");
2589
+ lines.push(`\u6355\u6349\u4E2D\u306E source_roots (${summary.extra.length}):`);
2590
+ for (const p of summary.extra) lines.push(`- ${p}`);
2317
2591
  }
2318
- const expanded = expandTilde(entry.path.trim());
2319
- if (!isAbsolute(expanded)) {
2320
- throw new Error(
2321
- "Portfolio workspace paths must be absolute (or start with '~'); use --workspace for relative ad-hoc paths."
2322
- );
2592
+ return lines.join("\n");
2593
+ }
2594
+ if (summary.gaps.length === 0) {
2595
+ lines.push(
2596
+ `\u2705 \u5BA3\u8A00\u3055\u308C\u305F ${summary.declaredCount} repo \u306F\u3059\u3079\u3066\u6355\u6349\u5BFE\u8C61(source_roots)\u306B\u542B\u307E\u308C\u3066\u3044\u307E\u3059\u3002`
2597
+ );
2598
+ } else {
2599
+ lines.push(`\u26A0\uFE0F \u5BA3\u8A00\u3055\u308C\u3066\u3044\u308B\u306E\u306B\u6355\u6349\u5BFE\u8C61\u306B\u7121\u3044 repo: ${summary.gaps.length}(\u53D6\u308A\u3053\u307C\u3057)`);
2600
+ for (const g of summary.gaps) {
2601
+ lines.push(`- ${g.path}${g.visibility ? ` [${g.visibility}]` : ""} \u2014 source_roots \u306B\u672A\u767B\u9332`);
2323
2602
  }
2324
- const abs = resolve3(expanded);
2325
- if (seen.has(abs)) continue;
2326
- seen.add(abs);
2327
- result.push(entry.label !== void 0 ? { path: abs, label: entry.label } : { path: abs });
2328
2603
  }
2329
- if (result.length === 0) {
2330
- throw new Error("~/.basou/portfolio.yaml has no workspaces.");
2604
+ lines.push("");
2605
+ if (summary.extra.length > 0) {
2606
+ lines.push(
2607
+ `## \u5BA3\u8A00\u5916\u306E\u6355\u6349\u5BFE\u8C61 (${summary.extra.length}) \u2014 workspace view \u304B\u3001\u5BA3\u8A00\u6F0F\u308C\u306E\u53EF\u80FD\u6027`
2608
+ );
2609
+ for (const p of summary.extra) lines.push(`- ${p}`);
2610
+ lines.push("");
2331
2611
  }
2332
- return result;
2612
+ lines.push(
2613
+ "\u6CE8: read-only \u306E advisory \u3067\u3059\u3002\u5BA3\u8A00(repos)\u3068\u6355\u6349\u8A2D\u5B9A(source_roots)\u306E\u5DEE\u5206\u306E\u307F\u3092\u8868\u793A\u3057\u3001enforce \u306F\u3057\u307E\u305B\u3093\u3002"
2614
+ );
2615
+ return lines.join("\n");
2333
2616
  }
2334
-
2335
- // src/commands/refresh-watch.ts
2336
- import { readdir as readdir2, stat as stat2 } from "fs/promises";
2337
- import { homedir as homedir4 } from "os";
2338
- import { join as join5 } from "path";
2339
- import { findErrorCode as findErrorCode7 } from "@basou/core";
2340
- var DEFAULT_WATCH_INTERVAL_SEC = 30;
2341
- var MIN_WATCH_INTERVAL_SEC = 5;
2342
- var MAX_WATCH_INTERVAL_SEC = 86400;
2343
- function watchedRoots(ctx) {
2344
- return [
2345
- ctx.codexSessionsDir ?? join5(homedir4(), ".codex", "sessions"),
2346
- ctx.claudeProjectsDir ?? join5(homedir4(), ".claude", "projects")
2347
- ];
2617
+ async function runProjectSync(options, ctx = {}) {
2618
+ try {
2619
+ await doRunProjectSync(options, ctx);
2620
+ } catch (error) {
2621
+ renderCliError(error, { verbose: isVerbose(options) });
2622
+ process.exitCode = 1;
2623
+ }
2348
2624
  }
2349
- async function scanSourceLogs(roots) {
2350
- const out = /* @__PURE__ */ new Map();
2351
- const walk = async (dir) => {
2352
- let entries;
2625
+ async function doRunProjectSync(options, ctx) {
2626
+ const cwd = ctx.cwd ?? process.cwd();
2627
+ const repositoryRoot = await resolveBasouRootForCommand(cwd, "project sync");
2628
+ const paths = basouPaths9(repositoryRoot);
2629
+ const manifest = await readManifest5(paths);
2630
+ const hasRoster = manifest.repos !== void 0 && manifest.repos.length > 0;
2631
+ const reconcile = reconcileSourceRoots({
2632
+ ...manifest.repos !== void 0 ? { repos: manifest.repos } : {},
2633
+ ...manifest.import?.source_roots !== void 0 ? { sourceRoots: manifest.import.source_roots } : {}
2634
+ });
2635
+ const applied = options.apply === true && hasRoster && !reconcile.unchanged;
2636
+ if (applied) {
2637
+ const now = ctx.now ?? (() => /* @__PURE__ */ new Date());
2638
+ await writeManifest2(
2639
+ paths,
2640
+ {
2641
+ ...manifest,
2642
+ import: { ...manifest.import, source_roots: reconcile.next },
2643
+ workspace: { ...manifest.workspace, updated_at: now().toISOString() }
2644
+ },
2645
+ { force: true }
2646
+ );
2647
+ }
2648
+ const result = {
2649
+ ...reconcile,
2650
+ hasRoster,
2651
+ applied,
2652
+ preservedUnknownFields: unknownManifestKeys(manifest)
2653
+ };
2654
+ if (options.json === true) {
2655
+ console.log(JSON.stringify(result));
2656
+ } else {
2657
+ console.log(renderProjectSync(result));
2658
+ }
2659
+ return result;
2660
+ }
2661
+ function renderProjectSync(result) {
2662
+ const lines = [];
2663
+ lines.push("# source_roots \u540C\u671F(\u5BA3\u8A00\u30ED\u30FC\u30B9\u30BF\u30FC \u2192 \u6355\u6349\u8A2D\u5B9A)");
2664
+ lines.push("");
2665
+ lines.push(...preservedUnknownLines(result.preservedUnknownFields));
2666
+ if (!result.hasRoster) {
2667
+ lines.push(
2668
+ "\u2139\uFE0F repo \u30ED\u30FC\u30B9\u30BF\u30FC\u304C\u672A\u5BA3\u8A00\u3067\u3059(manifest \u306E `repos`)\u3002\u540C\u671F\u306E\u5143\u306B\u306A\u308B\u5BA3\u8A00\u304C\u7121\u3044\u305F\u3081\u3001\u5909\u66F4\u306F\u3042\u308A\u307E\u305B\u3093\u3002"
2669
+ );
2670
+ return lines.join("\n");
2671
+ }
2672
+ if (result.unchanged) {
2673
+ lines.push("\u2705 source_roots \u306F\u5BA3\u8A00\u30ED\u30FC\u30B9\u30BF\u30FC\u3092\u3059\u3079\u3066\u8986\u3063\u3066\u3044\u307E\u3059(\u540C\u671F\u4E0D\u8981)\u3002");
2674
+ return lines.join("\n");
2675
+ }
2676
+ if (result.applied) {
2677
+ lines.push(`\u2705 source_roots \u306B ${result.added.length} \u4EF6\u8FFD\u52A0\u3057\u307E\u3057\u305F:`);
2678
+ for (const p of result.added) lines.push(`- ${p}`);
2679
+ } else {
2680
+ lines.push(
2681
+ `${result.added.length} \u4EF6\u306E repo \u304C source_roots \u306B\u672A\u767B\u9332\u3067\u3059\u3002\u8FFD\u52A0\u4E88\u5B9A(dry-run\u3001\u53CD\u6620\u3059\u308B\u306B\u306F --apply):`
2682
+ );
2683
+ for (const p of result.added) lines.push(`- ${p}`);
2684
+ lines.push("");
2685
+ lines.push("\u6CE8: \u65E2\u5B58\u306E source_roots \u306F\u4FDD\u6301\u3057\u3001\u4E0D\u8DB3\u5206\u306E\u8FFD\u8A18\u306E\u307F\u884C\u3044\u307E\u3059(\u524A\u9664\u306F\u3057\u307E\u305B\u3093)\u3002");
2686
+ }
2687
+ return lines.join("\n");
2688
+ }
2689
+ async function runProjectAdopt(options, ctx = {}) {
2690
+ try {
2691
+ await doRunProjectAdopt(options, ctx);
2692
+ } catch (error) {
2693
+ renderCliError(error, { verbose: isVerbose(options) });
2694
+ process.exitCode = 1;
2695
+ }
2696
+ }
2697
+ function classifySourceRoot(repositoryRoot, declaredPath) {
2698
+ const absolute = resolve3(repositoryRoot, declaredPath);
2699
+ let real;
2700
+ try {
2701
+ real = realpathSync(absolute);
2702
+ } catch {
2703
+ return { path: declaredPath, kind: "unresolved" };
2704
+ }
2705
+ return { path: declaredPath, kind: existsSync(join4(real, ".git")) ? "repo" : "non-repo" };
2706
+ }
2707
+ async function doRunProjectAdopt(options, ctx) {
2708
+ const cwd = ctx.cwd ?? process.cwd();
2709
+ const repositoryRoot = await resolveBasouRootForCommand(cwd, "project adopt");
2710
+ const paths = basouPaths9(repositoryRoot);
2711
+ const manifest = await readManifest5(paths);
2712
+ const alreadyDeclared = manifest.repos !== void 0 && manifest.repos.length > 0;
2713
+ const candidates = effectiveSourceRoots(manifest).map(
2714
+ (r) => classifySourceRoot(repositoryRoot, r)
2715
+ );
2716
+ const plan = planRosterAdoption(candidates);
2717
+ const applied = options.apply === true && !alreadyDeclared && plan.repos.length > 0;
2718
+ if (applied) {
2719
+ const now = ctx.now ?? (() => /* @__PURE__ */ new Date());
2720
+ await writeManifest2(
2721
+ paths,
2722
+ {
2723
+ ...manifest,
2724
+ repos: plan.repos,
2725
+ workspace: { ...manifest.workspace, updated_at: now().toISOString() }
2726
+ },
2727
+ { force: true }
2728
+ );
2729
+ }
2730
+ const result = {
2731
+ ...plan,
2732
+ alreadyDeclared,
2733
+ applied,
2734
+ preservedUnknownFields: unknownManifestKeys(manifest)
2735
+ };
2736
+ if (options.json === true) {
2737
+ console.log(JSON.stringify(result));
2738
+ } else {
2739
+ console.log(renderProjectAdopt(result));
2740
+ }
2741
+ return result;
2742
+ }
2743
+ function renderProjectAdopt(result) {
2744
+ const lines = [];
2745
+ lines.push("# repo \u30ED\u30FC\u30B9\u30BF\u30FC\u306E bootstrap(source_roots \u2192 repos)");
2746
+ lines.push("");
2747
+ lines.push(...preservedUnknownLines(result.preservedUnknownFields));
2748
+ if (result.alreadyDeclared) {
2749
+ lines.push(
2750
+ "\u2139\uFE0F repo \u30ED\u30FC\u30B9\u30BF\u30FC(manifest \u306E `repos`)\u306F\u65E2\u306B\u5BA3\u8A00\u6E08\u307F\u3067\u3059\u3002adopt \u306F\u4E00\u5EA6\u304D\u308A\u306E bootstrap \u306E\u305F\u3081\u4F55\u3082\u66F8\u304D\u8FBC\u307F\u307E\u305B\u3093\u3002\u4EE5\u5F8C\u306E\u4FDD\u5B88\u306F `project check` / `project sync` \u3092\u4F7F\u3063\u3066\u304F\u3060\u3055\u3044\u3002"
2751
+ );
2752
+ return lines.join("\n");
2753
+ }
2754
+ if (result.repos.length === 0) {
2755
+ lines.push("\u2139\uFE0F source_roots \u306B git repo \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F(bootstrap \u5BFE\u8C61\u306A\u3057)\u3002");
2756
+ } else if (result.applied) {
2757
+ lines.push(`\u2705 ${result.repos.length} repo \u3092 repos \u30ED\u30FC\u30B9\u30BF\u30FC\u306B\u66F8\u304D\u8FBC\u307F\u307E\u3057\u305F:`);
2758
+ for (const r of result.repos) lines.push(`- ${r.path}`);
2759
+ lines.push("");
2760
+ lines.push(
2761
+ "\u6CE8: visibility \u306F\u672A\u8A2D\u5B9A\u3067\u3059\u3002\u5404 repo \u306B public / private / future-public \u3092\u624B\u52D5\u3067\u4ED8\u4E0E\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
2762
+ );
2763
+ } else {
2764
+ lines.push(
2765
+ `${result.repos.length} repo \u3092 repos \u30ED\u30FC\u30B9\u30BF\u30FC\u306B\u5BA3\u8A00\u4E88\u5B9A(dry-run\u3001\u53CD\u6620\u3059\u308B\u306B\u306F --apply):`
2766
+ );
2767
+ for (const r of result.repos) lines.push(`- ${r.path}`);
2768
+ lines.push("");
2769
+ lines.push("\u6CE8: visibility \u306F\u672A\u8A2D\u5B9A\u3067\u63D0\u6848\u3057\u307E\u3059\u3002\u53CD\u6620\u5F8C\u306B\u624B\u52D5\u3067\u4ED8\u4E0E\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
2770
+ }
2771
+ if (result.excluded.length > 0) {
2772
+ lines.push("");
2773
+ lines.push(`## \u9664\u5916 (${result.excluded.length}) \u2014 git repo \u3067\u306F\u306A\u3044\u305F\u3081 repos \u306B\u542B\u3081\u307E\u305B\u3093`);
2774
+ for (const e of result.excluded) {
2775
+ const reason = e.kind === "non-repo" ? "\u975E repo(workspace view / tmp \u7B49)" : "\u89E3\u6C7A\u4E0D\u80FD(\u30D1\u30B9\u304C\u5B58\u5728\u3057\u306A\u3044)";
2776
+ lines.push(`- ${e.path} \u2014 ${reason}`);
2777
+ }
2778
+ }
2779
+ return lines.join("\n");
2780
+ }
2781
+ async function runProjectWiring(options, ctx = {}) {
2782
+ try {
2783
+ await doRunProjectWiring(options, ctx);
2784
+ } catch (error) {
2785
+ renderCliError(error, { verbose: isVerbose(options) });
2786
+ process.exitCode = 1;
2787
+ }
2788
+ }
2789
+ async function isTrackedByGit(repoRoot, relPath) {
2790
+ const out = await safeSimpleGit(repoRoot).raw(["ls-files", "--", relPath]);
2791
+ return out.trim().length > 0;
2792
+ }
2793
+ async function gatherRepoWiring(repositoryRoot, entry) {
2794
+ const base = {
2795
+ path: entry.path,
2796
+ ...entry.visibility !== void 0 ? { visibility: entry.visibility } : {}
2797
+ };
2798
+ let real;
2799
+ try {
2800
+ real = realpathSync(resolve3(repositoryRoot, entry.path));
2801
+ } catch {
2802
+ return { ...base, reachable: false, instructionFiles: [] };
2803
+ }
2804
+ if (!existsSync(join4(real, ".git"))) {
2805
+ return { ...base, reachable: false, instructionFiles: [] };
2806
+ }
2807
+ try {
2808
+ const instructionFiles = [];
2809
+ for (const name of INSTRUCTION_FILES) {
2810
+ let present = true;
2811
+ try {
2812
+ lstatSync(join4(real, name));
2813
+ } catch {
2814
+ present = false;
2815
+ }
2816
+ instructionFiles.push({ name, present, tracked: await isTrackedByGit(real, name) });
2817
+ }
2818
+ return { ...base, reachable: true, instructionFiles };
2819
+ } catch (error) {
2820
+ if (isGitNotFound(error)) throw error;
2821
+ return { ...base, reachable: false, instructionFiles: [] };
2822
+ }
2823
+ }
2824
+ async function doRunProjectWiring(options, ctx) {
2825
+ const cwd = ctx.cwd ?? process.cwd();
2826
+ const repositoryRoot = await resolveBasouRootForCommand(cwd, "project wiring");
2827
+ const paths = basouPaths9(repositoryRoot);
2828
+ const manifest = await readManifest5(paths);
2829
+ const roster = manifest.repos ?? [];
2830
+ const facts = [];
2831
+ for (const entry of roster) facts.push(await gatherRepoWiring(repositoryRoot, entry));
2832
+ const summary = summarizeWiring(facts);
2833
+ const result = { ...summary, hasRoster: roster.length > 0 };
2834
+ if (options.json === true) {
2835
+ console.log(JSON.stringify(result));
2836
+ } else {
2837
+ console.log(renderProjectWiring(result));
2838
+ }
2839
+ return result;
2840
+ }
2841
+ function renderProjectWiring(result) {
2842
+ const lines = [];
2843
+ lines.push("# \u6307\u793A\u66F8 wiring \u30C1\u30A7\u30C3\u30AF(\u5BA3\u8A00\u30ED\u30FC\u30B9\u30BF\u30FC \xD7 \u6307\u793A\u66F8\u306E\u5B58\u5728/git \u8FFD\u8DE1)");
2844
+ lines.push("");
2845
+ if (!result.hasRoster) {
2846
+ lines.push(
2847
+ "\u2139\uFE0F repo \u30ED\u30FC\u30B9\u30BF\u30FC\u304C\u672A\u5BA3\u8A00\u3067\u3059(manifest \u306E `repos`)\u3002`basou project adopt` \u3067\u5BA3\u8A00\u3057\u3066\u304B\u3089\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
2848
+ );
2849
+ return lines.join("\n");
2850
+ }
2851
+ if (result.risks.length > 0) {
2852
+ lines.push(
2853
+ `\u26A0\uFE0F \u516C\u958B\u7CFB repo \u3067\u6307\u793A\u66F8\u304C git \u8FFD\u8DE1\u3055\u308C\u3066\u3044\u307E\u3059: ${result.risks.length}(canonical \u306E\u6F0F\u6D29\u30EA\u30B9\u30AF)`
2854
+ );
2855
+ for (const r of result.risks) {
2856
+ lines.push(
2857
+ `- ${r.repo} [${r.visibility}] \u2014 ${r.file} \u304C tracked(gitignore \u3055\u308C\u305F symlink \u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059)`
2858
+ );
2859
+ }
2860
+ } else if (result.ok) {
2861
+ lines.push("\u2705 \u516C\u958B\u7CFB repo \u3067 git \u8FFD\u8DE1\u3055\u308C\u3066\u3044\u308B\u6307\u793A\u66F8\u306F\u3042\u308A\u307E\u305B\u3093(privacy \u30EA\u30B9\u30AF\u306A\u3057)\u3002");
2862
+ } else {
2863
+ lines.push(
2864
+ "\u2139\uFE0F \u78BA\u5B9A\u3057\u305F privacy \u30EA\u30B9\u30AF\u306F\u3042\u308A\u307E\u305B\u3093\u304C\u3001\u5224\u5B9A\u3067\u304D\u306A\u3044/\u5230\u9054\u3067\u304D\u306A\u3044 repo \u304C\u3042\u308A\u307E\u3059(\u4E0B\u8A18\u53C2\u7167)\u3002"
2865
+ );
2866
+ }
2867
+ lines.push("");
2868
+ if (result.unknown.length > 0) {
2869
+ lines.push(
2870
+ `## visibility \u672A\u8A2D\u5B9A (${result.unknown.length}) \u2014 privacy \u5224\u5B9A\u4E0D\u53EF\u3002manifest \u306E repos \u306B visibility \u3092\u4ED8\u4E0E\u3057\u3066\u304F\u3060\u3055\u3044`
2871
+ );
2872
+ for (const p of result.unknown) lines.push(`- ${p}`);
2873
+ lines.push("");
2874
+ }
2875
+ if (result.incomplete.length > 0) {
2876
+ lines.push(`## \u6307\u793A\u66F8\u306E\u6B20\u843D (${result.incomplete.length}) \u2014 \u5F8C\u7D9A\u306E\u751F\u6210\u30B9\u30E9\u30A4\u30B9\u3067\u88DC\u5B8C\u4E88\u5B9A`);
2877
+ for (const i of result.incomplete) lines.push(`- ${i.repo} \u2014 ${i.missing.join(", ")}`);
2878
+ lines.push("");
2879
+ }
2880
+ if (result.unreachable.length > 0) {
2881
+ lines.push(`## \u5230\u9054\u4E0D\u80FD (${result.unreachable.length}) \u2014 \u30D1\u30B9\u672A\u89E3\u6C7A / git repo \u3067\u306A\u3044`);
2882
+ for (const p of result.unreachable) lines.push(`- ${p}`);
2883
+ lines.push("");
2884
+ }
2885
+ lines.push(
2886
+ "\u6CE8: read-only \u306E advisory \u3067\u3059\u3002\u6307\u793A\u66F8\u306E\u5B58\u5728\u3068 git \u8FFD\u8DE1\u72B6\u6CC1\u306E\u307F\u3092\u8868\u793A\u3057\u3001\u751F\u6210\u30FBenforce \u306F\u3057\u307E\u305B\u3093(.basou \u306E\u30D5\u30C3\u30C8\u30D7\u30EA\u30F3\u30C8\u306F `basou view --check`)\u3002"
2887
+ );
2888
+ return lines.join("\n");
2889
+ }
2890
+ async function runProjectGitignore(options, ctx = {}) {
2891
+ try {
2892
+ await doRunProjectGitignore(options, ctx);
2893
+ } catch (error) {
2894
+ renderCliError(error, { verbose: isVerbose(options) });
2895
+ process.exitCode = 1;
2896
+ }
2897
+ }
2898
+ function gatherRepoGitignore(repositoryRoot, entry) {
2899
+ const base = {
2900
+ path: entry.path,
2901
+ ...entry.visibility !== void 0 ? { visibility: entry.visibility } : {}
2902
+ };
2903
+ let real;
2904
+ try {
2905
+ real = realpathSync(resolve3(repositoryRoot, entry.path));
2906
+ } catch {
2907
+ return { ...base, reachable: false, currentLines: [] };
2908
+ }
2909
+ if (!existsSync(join4(real, ".git"))) {
2910
+ return { ...base, reachable: false, currentLines: [] };
2911
+ }
2912
+ return { ...base, reachable: true, currentLines: readGitignoreLines(join4(real, ".gitignore")) };
2913
+ }
2914
+ function hasErrorCode(error) {
2915
+ return error instanceof Error && typeof error.code === "string";
2916
+ }
2917
+ function readGitignoreLines(file) {
2918
+ try {
2919
+ return readFileSync(file, "utf8").split(/\r?\n/);
2920
+ } catch (error) {
2921
+ if (hasErrorCode(error) && error.code === "ENOENT") return [];
2922
+ throw new Error("Failed to read .gitignore", { cause: error });
2923
+ }
2924
+ }
2925
+ function applyGitignorePlan(repositoryRoot, plan) {
2926
+ const file = join4(realpathSync(resolve3(repositoryRoot, plan.path)), ".gitignore");
2927
+ let existing = "";
2928
+ try {
2929
+ existing = readFileSync(file, "utf8");
2930
+ } catch (error) {
2931
+ if (!(hasErrorCode(error) && error.code === "ENOENT")) {
2932
+ throw new Error("Failed to read .gitignore", { cause: error });
2933
+ }
2934
+ }
2935
+ const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
2936
+ try {
2937
+ writeFileSync(file, `${existing}${sep}${plan.toAdd.join("\n")}
2938
+ `);
2939
+ } catch (error) {
2940
+ throw new Error("Failed to write .gitignore", { cause: error });
2941
+ }
2942
+ }
2943
+ async function doRunProjectGitignore(options, ctx) {
2944
+ const cwd = ctx.cwd ?? process.cwd();
2945
+ const repositoryRoot = await resolveBasouRootForCommand(cwd, "project gitignore");
2946
+ const paths = basouPaths9(repositoryRoot);
2947
+ const manifest = await readManifest5(paths);
2948
+ const roster = manifest.repos ?? [];
2949
+ const facts = roster.map((entry) => gatherRepoGitignore(repositoryRoot, entry));
2950
+ const summary = planGitignore({ repos: facts, required: [...INSTRUCTION_FILES] });
2951
+ const applied = options.apply === true && summary.plans.length > 0;
2952
+ if (applied) {
2953
+ for (const plan of summary.plans) applyGitignorePlan(repositoryRoot, plan);
2954
+ }
2955
+ const result = { ...summary, hasRoster: roster.length > 0, applied };
2956
+ if (options.json === true) {
2957
+ console.log(JSON.stringify(result));
2958
+ } else {
2959
+ console.log(renderProjectGitignore(result));
2960
+ }
2961
+ return result;
2962
+ }
2963
+ function renderProjectGitignore(result) {
2964
+ const lines = [];
2965
+ lines.push("# .gitignore \u751F\u6210(\u516C\u958B\u7CFB repo \u306E\u6307\u793A\u66F8\u3092\u9664\u5916)");
2966
+ lines.push("");
2967
+ if (!result.hasRoster) {
2968
+ lines.push(
2969
+ "\u2139\uFE0F repo \u30ED\u30FC\u30B9\u30BF\u30FC\u304C\u672A\u5BA3\u8A00\u3067\u3059(manifest \u306E `repos`)\u3002`basou project adopt` \u3067\u5BA3\u8A00\u3057\u3066\u304B\u3089\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
2970
+ );
2971
+ return lines.join("\n");
2972
+ }
2973
+ if (result.plans.length > 0) {
2974
+ const verb = result.applied ? "\u8FFD\u52A0\u3057\u307E\u3057\u305F" : "\u8FFD\u52A0\u4E88\u5B9A(dry-run\u3001\u53CD\u6620\u3059\u308B\u306B\u306F --apply)";
2975
+ lines.push(
2976
+ `${result.applied ? "\u2705 " : ""}${result.plans.length} repo \u306E .gitignore \u306B${verb}:`
2977
+ );
2978
+ for (const p of result.plans) lines.push(`- ${p.path} \u2014 ${p.toAdd.join(", ")}`);
2979
+ } else if (result.ok) {
2980
+ lines.push("\u2705 \u516C\u958B\u7CFB repo \u306E .gitignore \u306F\u6307\u793A\u66F8\u3092\u3059\u3079\u3066\u9664\u5916\u6E08\u307F\u3067\u3059(\u8FFD\u52A0\u4E0D\u8981)\u3002");
2981
+ } else {
2982
+ lines.push(
2983
+ "\u2139\uFE0F \u8FFD\u52A0\u304C\u5FC5\u8981\u306A\u516C\u958B\u7CFB repo \u306F\u3042\u308A\u307E\u305B\u3093\u304C\u3001\u5224\u5B9A\u3067\u304D\u306A\u3044/\u5230\u9054\u3067\u304D\u306A\u3044 repo \u304C\u3042\u308A\u307E\u3059(\u4E0B\u8A18\u53C2\u7167)\u3002"
2984
+ );
2985
+ }
2986
+ lines.push("");
2987
+ if (result.unknown.length > 0) {
2988
+ lines.push(
2989
+ `## visibility \u672A\u8A2D\u5B9A (${result.unknown.length}) \u2014 \u5BFE\u8C61\u5916\u3002manifest \u306E repos \u306B visibility \u3092\u4ED8\u4E0E\u3057\u3066\u304F\u3060\u3055\u3044`
2990
+ );
2991
+ for (const p of result.unknown) lines.push(`- ${p}`);
2992
+ lines.push("");
2993
+ }
2994
+ if (result.unreachable.length > 0) {
2995
+ lines.push(`## \u5230\u9054\u4E0D\u80FD (${result.unreachable.length}) \u2014 \u30D1\u30B9\u672A\u89E3\u6C7A / git repo \u3067\u306A\u3044`);
2996
+ for (const p of result.unreachable) lines.push(`- ${p}`);
2997
+ lines.push("");
2998
+ }
2999
+ lines.push(
3000
+ "\u6CE8: \u65E2\u5B58\u306E .gitignore \u884C\u306F\u4FDD\u6301\u3057\u3001\u4E0D\u8DB3\u30D1\u30BF\u30FC\u30F3\u306E\u8FFD\u8A18\u306E\u307F\u884C\u3044\u307E\u3059(\u524A\u9664\u306F\u3057\u307E\u305B\u3093)\u3002private / visibility \u672A\u8A2D\u5B9A\u306E repo \u306F\u5BFE\u8C61\u5916\u3067\u3059\u3002"
3001
+ );
3002
+ lines.push(
3003
+ "\u6CE8: .gitignore \u3078\u306E\u8FFD\u8A18\u306F\u3001\u65E2\u306B git \u8FFD\u8DE1\u6E08\u307F\u306E\u30D5\u30A1\u30A4\u30EB\u3092 untrack \u3057\u307E\u305B\u3093\u3002\u8FFD\u8DE1\u6E08\u307F\u306E\u6307\u793A\u66F8\u306F `basou project wiring` \u3067\u691C\u51FA\u3057\u3001`git rm --cached <file>` \u3067\u5916\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
3004
+ );
3005
+ return lines.join("\n");
3006
+ }
3007
+ async function runProjectSymlinks(options, ctx = {}) {
3008
+ try {
3009
+ await doRunProjectSymlinks(options, ctx);
3010
+ } catch (error) {
3011
+ renderCliError(error, { verbose: isVerbose(options) });
3012
+ process.exitCode = 1;
3013
+ }
3014
+ }
3015
+ function expectedSymlinkTargets(repoDirReal, canonicalFile) {
3016
+ return [
3017
+ { name: "AGENTS.md", target: relative2(repoDirReal, canonicalFile) },
3018
+ { name: "CLAUDE.md", target: CANONICAL_FILE },
3019
+ { name: ".github/copilot-instructions.md", target: `../${CANONICAL_FILE}` }
3020
+ ];
3021
+ }
3022
+ function inspectSymlink(filePath, expectedTarget) {
3023
+ let isLink;
3024
+ try {
3025
+ isLink = lstatSync(filePath).isSymbolicLink();
3026
+ } catch (error) {
3027
+ if (hasErrorCode(error) && error.code === "ENOENT") return { state: "missing" };
3028
+ return { state: "blocked" };
3029
+ }
3030
+ if (!isLink) return { state: "occupied" };
3031
+ const actual = readlinkSync(filePath);
3032
+ return actual === expectedTarget ? { state: "correct" } : { state: "mismatch", actualTarget: actual };
3033
+ }
3034
+ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
3035
+ const base = { path: entry.path };
3036
+ let real;
3037
+ try {
3038
+ real = realpathSync(resolve3(repositoryRoot, entry.path));
3039
+ } catch {
3040
+ return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
3041
+ }
3042
+ if (real === anchorReal) {
3043
+ return { ...base, isAnchor: true, reachable: true, canonicalPresent: false, files: [] };
3044
+ }
3045
+ if (!existsSync(join4(real, ".git"))) {
3046
+ return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
3047
+ }
3048
+ const canonicalFile = join4(anchorReal, "agents", basename3(real), CANONICAL_FILE);
3049
+ if (!existsSync(canonicalFile)) {
3050
+ return { ...base, isAnchor: false, reachable: true, canonicalPresent: false, files: [] };
3051
+ }
3052
+ const files = expectedSymlinkTargets(real, canonicalFile).map(
3053
+ (spec) => {
3054
+ const { state, actualTarget } = inspectSymlink(join4(real, spec.name), spec.target);
3055
+ return {
3056
+ name: spec.name,
3057
+ expectedTarget: spec.target,
3058
+ state,
3059
+ ...actualTarget !== void 0 ? { actualTarget } : {}
3060
+ };
3061
+ }
3062
+ );
3063
+ return {
3064
+ ...base,
3065
+ isAnchor: false,
3066
+ reachable: true,
3067
+ canonicalPresent: true,
3068
+ canonicalName: basename3(real),
3069
+ files
3070
+ };
3071
+ }
3072
+ function applySymlinkPlan(repositoryRoot, plan) {
3073
+ let real;
3074
+ try {
3075
+ real = realpathSync(resolve3(repositoryRoot, plan.path));
3076
+ } catch (error) {
3077
+ const message = failureReason(error);
3078
+ return { created: [], failed: plan.toCreate.map((c) => ({ file: c.name, message })) };
3079
+ }
3080
+ const created = [];
3081
+ const failed = [];
3082
+ for (const { name, target } of plan.toCreate) {
3083
+ const filePath = join4(real, name);
3084
+ try {
3085
+ mkdirSync(dirname(filePath), { recursive: true });
3086
+ symlinkSync(target, filePath);
3087
+ created.push(name);
3088
+ } catch (error) {
3089
+ failed.push({ file: name, message: failureReason(error) });
3090
+ }
3091
+ }
3092
+ return { created, failed };
3093
+ }
3094
+ function failureReason(error) {
3095
+ return hasErrorCode(error) ? error.code : "unknown error";
3096
+ }
3097
+ async function doRunProjectSymlinks(options, ctx) {
3098
+ const cwd = ctx.cwd ?? process.cwd();
3099
+ const repositoryRoot = await resolveBasouRootForCommand(cwd, "project symlinks");
3100
+ const paths = basouPaths9(repositoryRoot);
3101
+ const manifest = await readManifest5(paths);
3102
+ const roster = manifest.repos ?? [];
3103
+ const anchorReal = realpathSync(repositoryRoot);
3104
+ const facts = roster.map((entry) => gatherRepoSymlinks(repositoryRoot, anchorReal, entry));
3105
+ const summary = summarizeSymlinkPlan(facts);
3106
+ const wantApply = options.apply === true && summary.plans.length > 0;
3107
+ const failures = [];
3108
+ let createdCount = 0;
3109
+ if (wantApply) {
3110
+ for (const plan of summary.plans) {
3111
+ const { created, failed } = applySymlinkPlan(repositoryRoot, plan);
3112
+ createdCount += created.length;
3113
+ for (const f of failed) failures.push({ repo: plan.path, file: f.file, message: f.message });
3114
+ }
3115
+ }
3116
+ const result = {
3117
+ ...summary,
3118
+ hasRoster: roster.length > 0,
3119
+ applied: createdCount > 0,
3120
+ failures
3121
+ };
3122
+ if (options.json === true) {
3123
+ console.log(JSON.stringify(result));
3124
+ } else {
3125
+ console.log(renderProjectSymlinks(result));
3126
+ }
3127
+ return result;
3128
+ }
3129
+ function renderProjectSymlinks(result) {
3130
+ const lines = [];
3131
+ lines.push("# \u6307\u793A\u66F8 symlink \u751F\u6210(\u5404 repo \u2192 anchor \u306E canonical)");
3132
+ lines.push("");
3133
+ if (!result.hasRoster) {
3134
+ lines.push(
3135
+ "\u2139\uFE0F repo \u30ED\u30FC\u30B9\u30BF\u30FC\u304C\u672A\u5BA3\u8A00\u3067\u3059(manifest \u306E `repos`)\u3002`basou project adopt` \u3067\u5BA3\u8A00\u3057\u3066\u304B\u3089\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
3136
+ );
3137
+ return lines.join("\n");
3138
+ }
3139
+ if (result.plans.length > 0) {
3140
+ const attempted = result.applied || result.failures.length > 0;
3141
+ if (!attempted) {
3142
+ lines.push(
3143
+ `${result.plans.length} repo \u306B\u6307\u793A\u66F8 symlink \u3092\u4F5C\u6210\u4E88\u5B9A(dry-run\u3001\u53CD\u6620\u3059\u308B\u306B\u306F --apply):`
3144
+ );
3145
+ for (const p of result.plans) {
3146
+ lines.push(`- ${p.path}`);
3147
+ for (const c of p.toCreate) lines.push(` ${c.name} -> ${c.target}`);
3148
+ }
3149
+ } else {
3150
+ const header = result.failures.length === 0 ? "\u2705 \u6307\u793A\u66F8 symlink \u3092\u4F5C\u6210\u3057\u307E\u3057\u305F:" : result.applied ? "\u6307\u793A\u66F8 symlink \u3092\u4F5C\u6210\u3057\u307E\u3057\u305F(\u4E00\u90E8\u5931\u6557\u3001\u4E0B\u8A18\u53C2\u7167):" : "\u6307\u793A\u66F8 symlink \u3092\u4F5C\u6210\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F(\u4E0B\u8A18\u53C2\u7167):";
3151
+ lines.push(header);
3152
+ for (const p of result.plans) {
3153
+ const failedFiles = new Set(
3154
+ result.failures.filter((f) => f.repo === p.path).map((f) => f.file)
3155
+ );
3156
+ const created = p.toCreate.filter((c) => !failedFiles.has(c.name));
3157
+ if (created.length === 0) continue;
3158
+ lines.push(`- ${p.path}`);
3159
+ for (const c of created) lines.push(` ${c.name} -> ${c.target}`);
3160
+ }
3161
+ }
3162
+ } else if (result.ok) {
3163
+ lines.push("\u2705 \u5BA3\u8A00\u3055\u308C\u305F\u5168 repo \u306E\u6307\u793A\u66F8 symlink \u306F\u6B63\u3057\u304F\u5F35\u3089\u308C\u3066\u3044\u307E\u3059(\u751F\u6210\u4E0D\u8981)\u3002");
3164
+ } else {
3165
+ lines.push(
3166
+ "\u2139\uFE0F \u751F\u6210\u304C\u5FC5\u8981\u306A symlink \u306F\u3042\u308A\u307E\u305B\u3093\u304C\u3001\u7AF6\u5408 / \u885D\u7A81 / canonical \u4E0D\u5728 / \u5230\u9054\u3067\u304D\u306A\u3044 repo \u304C\u3042\u308A\u307E\u3059(\u4E0B\u8A18\u53C2\u7167)\u3002"
3167
+ );
3168
+ }
3169
+ lines.push("");
3170
+ if (result.failures.length > 0) {
3171
+ lines.push(`## \u4F5C\u6210\u306B\u5931\u6557 (${result.failures.length}) \u2014 \u4E00\u90E8\u306E symlink \u3092\u4F5C\u6210\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F`);
3172
+ for (const f of result.failures) lines.push(`- ${f.repo} \u2014 ${f.file}: ${f.message}`);
3173
+ lines.push("");
3174
+ }
3175
+ if (result.conflicts.length > 0) {
3176
+ lines.push(
3177
+ `## \u7AF6\u5408 (${result.conflicts.length}) \u2014 \u65E2\u5B58\u3092\u4E0A\u66F8\u304D\u3057\u307E\u305B\u3093\u3002\u624B\u52D5\u3067\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044`
3178
+ );
3179
+ for (const c of result.conflicts) {
3180
+ const detail = c.reason === "mismatch" ? `\u5225\u306E\u5834\u6240\u3092\u6307\u3059 symlink(\u73FE\u5728: ${c.actualTarget ?? "?"})` : c.reason === "occupied" ? "symlink \u3067\u306A\u3044\u5B9F\u30D5\u30A1\u30A4\u30EB/\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA" : "\u691C\u67FB\u3067\u304D\u306A\u3044\u30D1\u30B9(\u89AA\u304C\u975E\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u7B49)";
3181
+ lines.push(`- ${c.repo} \u2014 ${c.file}: ${detail}`);
3182
+ }
3183
+ lines.push("");
3184
+ }
3185
+ if (result.collisions.length > 0) {
3186
+ lines.push(
3187
+ `## canonical \u885D\u7A81 (${result.collisions.length}) \u2014 \u5225 repo \u304C\u540C\u540D canonical \u3092\u5171\u6709(\u81EA\u52D5\u914D\u7DDA\u3057\u307E\u305B\u3093)`
3188
+ );
3189
+ for (const c of result.collisions) {
3190
+ lines.push(`- agents/${c.canonicalName}/AGENTS.md \u2190 ${c.repos.join(", ")}`);
3191
+ }
3192
+ lines.push("");
3193
+ }
3194
+ if (result.missingCanonical.length > 0) {
3195
+ lines.push(
3196
+ `## canonical \u4E0D\u5728 (${result.missingCanonical.length}) \u2014 anchor \u306B agents/<repo>/AGENTS.md \u304C\u7121\u3044\u305F\u3081\u751F\u6210\u3067\u304D\u307E\u305B\u3093`
3197
+ );
3198
+ for (const p of result.missingCanonical) lines.push(`- ${p}`);
3199
+ lines.push("");
3200
+ }
3201
+ if (result.unreachable.length > 0) {
3202
+ lines.push(`## \u5230\u9054\u4E0D\u80FD (${result.unreachable.length}) \u2014 \u30D1\u30B9\u672A\u89E3\u6C7A / git repo \u3067\u306A\u3044`);
3203
+ for (const p of result.unreachable) lines.push(`- ${p}`);
3204
+ lines.push("");
3205
+ }
3206
+ lines.push(
3207
+ "\u6CE8: \u65E2\u5B58\u30D5\u30A1\u30A4\u30EB\u30FB\u5225\u306E\u5834\u6240\u3092\u6307\u3059 symlink \u306F\u4E0A\u66F8\u304D\u305B\u305A\u3001\u4E0D\u8DB3\u5206\u306E\u4F5C\u6210\u306E\u307F\u884C\u3044\u307E\u3059(GEMINI.md \u306F\u5EC3\u6B62\u306E\u305F\u3081\u751F\u6210\u3057\u307E\u305B\u3093)\u3002"
3208
+ );
3209
+ return lines.join("\n");
3210
+ }
3211
+ async function runProjectWorkspace(options, ctx = {}) {
3212
+ try {
3213
+ await doRunProjectWorkspace(options, ctx);
3214
+ } catch (error) {
3215
+ renderCliError(error, { verbose: isVerbose(options) });
3216
+ process.exitCode = 1;
3217
+ }
3218
+ }
3219
+ function resolveViewDir(repositoryRoot, viewPath) {
3220
+ const abs = resolve3(repositoryRoot, viewPath);
3221
+ try {
3222
+ return realpathSync(abs);
3223
+ } catch {
3224
+ try {
3225
+ return join4(realpathSync(dirname(abs)), basename3(abs));
3226
+ } catch {
3227
+ return abs;
3228
+ }
3229
+ }
3230
+ }
3231
+ function gatherViewRepo(repositoryRoot, viewDir, entry) {
3232
+ let repoReal;
3233
+ try {
3234
+ repoReal = realpathSync(resolve3(repositoryRoot, entry.path));
3235
+ } catch {
3236
+ return { path: entry.path, reachable: false };
3237
+ }
3238
+ const expectedTarget = relative2(viewDir, repoReal);
3239
+ if (expectedTarget === "" || expectedTarget === ".") {
3240
+ return { path: entry.path, reachable: false };
3241
+ }
3242
+ const linkName = basename3(repoReal);
3243
+ const { state, actualTarget } = inspectSymlink(join4(viewDir, linkName), expectedTarget);
3244
+ return {
3245
+ path: entry.path,
3246
+ reachable: true,
3247
+ linkName,
3248
+ expectedTarget,
3249
+ state,
3250
+ ...actualTarget !== void 0 ? { actualTarget } : {}
3251
+ };
3252
+ }
3253
+ function applyViewPlan(viewDir, toCreate) {
3254
+ const created = [];
3255
+ const failed = [];
3256
+ for (const { name, target } of toCreate) {
3257
+ const filePath = join4(viewDir, name);
3258
+ try {
3259
+ mkdirSync(dirname(filePath), { recursive: true });
3260
+ symlinkSync(target, filePath);
3261
+ created.push(name);
3262
+ } catch (error) {
3263
+ failed.push({ name, message: failureReason(error) });
3264
+ }
3265
+ }
3266
+ return { created, failed };
3267
+ }
3268
+ var TOP_LEVEL_INSTRUCTION_FILES_LOWER = new Set(
3269
+ INSTRUCTION_FILES.filter((f) => !f.includes("/")).map((f) => f.toLowerCase())
3270
+ );
3271
+ function classifyViewLink(viewDir, name, rosterRealpaths) {
3272
+ const filePath = join4(viewDir, name);
3273
+ let isLink;
3274
+ try {
3275
+ isLink = lstatSync(filePath).isSymbolicLink();
3276
+ } catch {
3277
+ return null;
3278
+ }
3279
+ if (!isLink) return null;
3280
+ let target;
3281
+ try {
3282
+ target = readlinkSync(filePath);
3283
+ } catch {
3284
+ return null;
3285
+ }
3286
+ const resolved = isAbsolute(target) ? target : resolve3(viewDir, target);
3287
+ try {
3288
+ if (rosterRealpaths.has(realpathSync(resolved))) return null;
3289
+ } catch {
3290
+ }
3291
+ if (isAbsolute(target)) return { target, kind: "absolute" };
3292
+ let isDir = false;
3293
+ try {
3294
+ isDir = statSync(resolved).isDirectory();
3295
+ } catch {
3296
+ isDir = false;
3297
+ }
3298
+ if (!isDir) {
3299
+ return { target, kind: existsSync(resolved) ? "non-repo" : "broken" };
3300
+ }
3301
+ return { target, kind: existsSync(join4(resolved, ".git")) ? "repo" : "non-repo" };
3302
+ }
3303
+ function gatherExistingViewLinks(viewDir, rosterRealpaths) {
3304
+ let names;
3305
+ try {
3306
+ names = readdirSync(viewDir);
3307
+ } catch (error) {
3308
+ if (hasErrorCode(error) && error.code === "ENOENT") return [];
3309
+ throw new Error("workspace view \u3092\u8D70\u67FB\u3067\u304D\u307E\u305B\u3093(\u30D1\u30B9/\u7A2E\u5225\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044)", {
3310
+ cause: error
3311
+ });
3312
+ }
3313
+ const links = [];
3314
+ for (const name of names) {
3315
+ if (TOP_LEVEL_INSTRUCTION_FILES_LOWER.has(name.toLowerCase())) continue;
3316
+ const c = classifyViewLink(viewDir, name, rosterRealpaths);
3317
+ if (c === null) continue;
3318
+ links.push({ name, target: c.target, kind: c.kind });
3319
+ }
3320
+ return links;
3321
+ }
3322
+ function pruneViewLinks(viewDir, toPrune, rosterRealpaths) {
3323
+ const pruned = [];
3324
+ const failed = [];
3325
+ for (const { name } of toPrune) {
3326
+ const filePath = join4(viewDir, name);
3327
+ const c = classifyViewLink(viewDir, name, rosterRealpaths);
3328
+ if (c === null || c.kind !== "repo") {
3329
+ failed.push({
3330
+ name,
3331
+ message: "\u64A4\u53BB\u5BFE\u8C61\u304C scan \u6642\u3068\u5909\u308F\u308A\u307E\u3057\u305F(basou \u751F\u6210\u306E stray repo link \u3067\u306F\u306A\u304F\u306A\u3063\u305F/\u518D\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044)"
3332
+ });
3333
+ continue;
3334
+ }
3335
+ try {
3336
+ unlinkSync(filePath);
3337
+ pruned.push(name);
3338
+ } catch (error) {
3339
+ failed.push({ name, message: failureReason(error) });
3340
+ }
3341
+ }
3342
+ return { pruned, failed };
3343
+ }
3344
+ async function doRunProjectWorkspace(options, ctx) {
3345
+ const cwd = ctx.cwd ?? process.cwd();
3346
+ const repositoryRoot = await resolveBasouRootForCommand(cwd, "project workspace");
3347
+ const paths = basouPaths9(repositoryRoot);
3348
+ const manifest = await readManifest5(paths);
3349
+ const viewPath = manifest.workspace.view;
3350
+ const roster = manifest.repos ?? [];
3351
+ let result;
3352
+ if (viewPath === void 0) {
3353
+ result = {
3354
+ toCreate: [],
3355
+ conflicts: [],
3356
+ collisions: [],
3357
+ unreachable: [],
3358
+ toPrune: [],
3359
+ strayUnknown: [],
3360
+ correctCount: 0,
3361
+ ok: true,
3362
+ hasView: false,
3363
+ applied: false,
3364
+ pruned: false,
3365
+ pruneWithheld: false,
3366
+ failures: [],
3367
+ pruneFailures: []
3368
+ };
3369
+ } else {
3370
+ const viewDir = resolveViewDir(repositoryRoot, viewPath);
3371
+ const facts = roster.map((entry) => gatherViewRepo(repositoryRoot, viewDir, entry));
3372
+ const rosterNames = roster.map((entry) => basename3(resolve3(repositoryRoot, entry.path)));
3373
+ const rosterRealpaths = /* @__PURE__ */ new Set();
3374
+ for (const entry of roster) {
3375
+ try {
3376
+ rosterRealpaths.add(realpathSync(resolve3(repositoryRoot, entry.path)));
3377
+ } catch {
3378
+ }
3379
+ }
3380
+ const existing = gatherExistingViewLinks(viewDir, rosterRealpaths);
3381
+ const plan = planWorkspaceView(facts, existing, rosterNames);
3382
+ const failures = [];
3383
+ let createdCount = 0;
3384
+ if (options.apply === true && plan.toCreate.length > 0) {
3385
+ const applied = applyViewPlan(viewDir, plan.toCreate);
3386
+ createdCount = applied.created.length;
3387
+ for (const f of applied.failed) failures.push(f);
3388
+ }
3389
+ const pruneWithheld = options.prune === true && plan.toPrune.length > 0 && plan.unreachable.length > 0;
3390
+ const pruneFailures = [];
3391
+ let prunedCount = 0;
3392
+ if (options.prune === true && plan.toPrune.length > 0 && plan.unreachable.length === 0) {
3393
+ const removed = pruneViewLinks(viewDir, plan.toPrune, rosterRealpaths);
3394
+ prunedCount = removed.pruned.length;
3395
+ for (const f of removed.failed) pruneFailures.push(f);
3396
+ }
3397
+ const createsOutstanding = plan.toCreate.length > 0 && !(options.apply === true && failures.length === 0);
3398
+ const prunesOutstanding = plan.toPrune.length > 0 && !(options.prune === true && !pruneWithheld && pruneFailures.length === 0);
3399
+ const ok = plan.conflicts.length === 0 && plan.collisions.length === 0 && plan.unreachable.length === 0 && plan.strayUnknown.length === 0 && !createsOutstanding && !prunesOutstanding;
3400
+ result = {
3401
+ ...plan,
3402
+ ok,
3403
+ hasView: true,
3404
+ applied: createdCount > 0,
3405
+ pruned: prunedCount > 0,
3406
+ pruneWithheld,
3407
+ failures,
3408
+ pruneFailures
3409
+ };
3410
+ }
3411
+ if (options.json === true) {
3412
+ console.log(JSON.stringify(result));
3413
+ } else {
3414
+ console.log(renderProjectWorkspace(result));
3415
+ }
3416
+ return result;
3417
+ }
3418
+ function renderProjectWorkspace(result) {
3419
+ const lines = [];
3420
+ lines.push("# workspace view \u751F\u6210(roster repo \u3092\u96C6\u7D04)");
3421
+ lines.push("");
3422
+ if (!result.hasView) {
3423
+ lines.push(
3424
+ "\u2139\uFE0F view \u304C\u672A\u5BA3\u8A00\u3067\u3059(manifest \u306E `workspace.view`)\u3002\u96C6\u7D04\u5148\u306E\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3092\u5BA3\u8A00\u3057\u3066\u304B\u3089\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
3425
+ );
3426
+ return lines.join("\n");
3427
+ }
3428
+ if (result.toCreate.length > 0) {
3429
+ const attempted = result.applied || result.failures.length > 0;
3430
+ if (!attempted) {
3431
+ lines.push(
3432
+ `${result.toCreate.length} \u4EF6\u306E repo symlink \u3092 view \u306B\u4F5C\u6210\u4E88\u5B9A(dry-run\u3001\u53CD\u6620\u3059\u308B\u306B\u306F --apply):`
3433
+ );
3434
+ for (const c of result.toCreate) lines.push(` ${c.name} -> ${c.target}`);
3435
+ } else {
3436
+ const failed = new Set(result.failures.map((f) => f.name));
3437
+ const header = result.failures.length === 0 ? "\u2705 view \u306B repo symlink \u3092\u4F5C\u6210\u3057\u307E\u3057\u305F:" : result.applied ? "view \u306B repo symlink \u3092\u4F5C\u6210\u3057\u307E\u3057\u305F(\u4E00\u90E8\u5931\u6557\u3001\u4E0B\u8A18\u53C2\u7167):" : "view \u306B repo symlink \u3092\u4F5C\u6210\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F(\u4E0B\u8A18\u53C2\u7167):";
3438
+ lines.push(header);
3439
+ for (const c of result.toCreate) {
3440
+ if (failed.has(c.name)) continue;
3441
+ lines.push(` ${c.name} -> ${c.target}`);
3442
+ }
3443
+ }
3444
+ } else if (result.ok) {
3445
+ lines.push(
3446
+ `\u2705 view \u306F\u5BA3\u8A00\u3055\u308C\u305F roster \u3092\u3059\u3079\u3066\u96C6\u7D04\u3057\u3066\u3044\u307E\u3059(${result.correctCount} links\u3001\u751F\u6210\u4E0D\u8981)\u3002`
3447
+ );
3448
+ } else {
3449
+ lines.push(
3450
+ "\u2139\uFE0F \u4F5C\u6210\u304C\u5FC5\u8981\u306A symlink \u306F\u3042\u308A\u307E\u305B\u3093\u304C\u3001\u5BFE\u5FDC\u306E\u5FC5\u8981\u306A\u9805\u76EE\u304C\u3042\u308A\u307E\u3059(stray / \u7AF6\u5408 / \u885D\u7A81 / \u5230\u9054\u3067\u304D\u306A\u3044 repo\u3001\u4E0B\u8A18\u53C2\u7167)\u3002"
3451
+ );
3452
+ }
3453
+ lines.push("");
3454
+ if (result.failures.length > 0) {
3455
+ lines.push(`## \u4F5C\u6210\u306B\u5931\u6557 (${result.failures.length}) \u2014 \u4E00\u90E8\u306E symlink \u3092\u4F5C\u6210\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F`);
3456
+ for (const f of result.failures) lines.push(`- ${f.name}: ${f.message}`);
3457
+ lines.push("");
3458
+ }
3459
+ if (result.toPrune.length > 0) {
3460
+ const attempted = result.pruned || result.pruneFailures.length > 0;
3461
+ if (result.pruneWithheld) {
3462
+ lines.push(
3463
+ `${result.toPrune.length} \u4EF6\u306E stray repo symlink \u3092\u64A4\u53BB\u4E88\u5B9A\u3067\u3057\u305F\u304C\u3001\u5230\u9054\u3067\u304D\u306A\u3044 repo \u304C\u3042\u308B\u305F\u3081\u64A4\u53BB\u3092\u4FDD\u7559\u3057\u307E\u3057\u305F(\u5230\u9054\u3067\u304D\u306A\u3044 repo \u306E link \u3068 stray \u3092\u533A\u5225\u3067\u304D\u306A\u3044\u305F\u3081\u3002\u4E0B\u8A18\u306E repo \u3092\u89E3\u6C7A\u3059\u308B\u304B archive \u3057\u3066\u304B\u3089\u518D\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044):`
3464
+ );
3465
+ for (const p of result.toPrune) lines.push(` ${p.name} -> ${p.target}`);
3466
+ } else if (!attempted) {
3467
+ lines.push(
3468
+ `${result.toPrune.length} \u4EF6\u306E stray repo symlink \u3092\u64A4\u53BB\u4E88\u5B9A(dry-run\u3001\u64A4\u53BB\u3059\u308B\u306B\u306F --prune):`
3469
+ );
3470
+ for (const p of result.toPrune) lines.push(` ${p.name} -> ${p.target}`);
3471
+ } else {
3472
+ const failed = new Set(result.pruneFailures.map((f) => f.name));
3473
+ const header = result.pruneFailures.length === 0 ? "\u{1F9F9} stray repo symlink \u3092\u64A4\u53BB\u3057\u307E\u3057\u305F:" : result.pruned ? "stray repo symlink \u3092\u64A4\u53BB\u3057\u307E\u3057\u305F(\u4E00\u90E8\u5931\u6557\u3001\u4E0B\u8A18\u53C2\u7167):" : "stray repo symlink \u3092\u64A4\u53BB\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F(\u4E0B\u8A18\u53C2\u7167):";
3474
+ lines.push(header);
3475
+ for (const p of result.toPrune) {
3476
+ if (failed.has(p.name)) continue;
3477
+ lines.push(` ${p.name} -> ${p.target}`);
3478
+ }
3479
+ }
3480
+ lines.push("");
3481
+ }
3482
+ if (result.pruneFailures.length > 0) {
3483
+ lines.push(
3484
+ `## \u64A4\u53BB\u306B\u5931\u6557 (${result.pruneFailures.length}) \u2014 \u4E00\u90E8\u306E stray symlink \u3092\u64A4\u53BB\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F`
3485
+ );
3486
+ for (const f of result.pruneFailures) lines.push(`- ${f.name}: ${f.message}`);
3487
+ lines.push("");
3488
+ }
3489
+ if (result.conflicts.length > 0) {
3490
+ lines.push(
3491
+ `## \u7AF6\u5408 (${result.conflicts.length}) \u2014 \u65E2\u5B58\u3092\u4E0A\u66F8\u304D\u3057\u307E\u305B\u3093\u3002\u624B\u52D5\u3067\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044`
3492
+ );
3493
+ for (const c of result.conflicts) {
3494
+ const detail = c.reason === "mismatch" ? `\u5225\u306E\u5834\u6240\u3092\u6307\u3059 symlink(\u73FE\u5728: ${c.actualTarget ?? "?"})` : c.reason === "occupied" ? "symlink \u3067\u306A\u3044\u5B9F\u30D5\u30A1\u30A4\u30EB/\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA" : "\u691C\u67FB\u3067\u304D\u306A\u3044\u30D1\u30B9(\u89AA\u304C\u975E\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u7B49)";
3495
+ lines.push(`- ${c.name}: ${detail}`);
3496
+ }
3497
+ lines.push("");
3498
+ }
3499
+ if (result.collisions.length > 0) {
3500
+ lines.push(
3501
+ `## basename \u885D\u7A81 (${result.collisions.length}) \u2014 \u5225 repo \u304C\u540C\u3058 view \u540D\u3092\u53D6\u308A\u5408\u3044(\u81EA\u52D5\u914D\u7DDA\u3057\u307E\u305B\u3093)`
3502
+ );
3503
+ for (const c of result.collisions) lines.push(`- ${c.linkName} \u2190 ${c.repos.join(", ")}`);
3504
+ lines.push("");
3505
+ }
3506
+ if (result.unreachable.length > 0) {
3507
+ lines.push(
3508
+ `## \u5230\u9054\u4E0D\u80FD (${result.unreachable.length}) \u2014 \u30D1\u30B9\u672A\u89E3\u6C7A\u3001\u307E\u305F\u306F view \u81EA\u8EAB\u306B\u89E3\u6C7A\u3059\u308B\u305F\u3081\u96C6\u7D04\u3067\u304D\u307E\u305B\u3093`
3509
+ );
3510
+ for (const p of result.unreachable) lines.push(`- ${p}`);
3511
+ lines.push("");
3512
+ }
3513
+ if (result.strayUnknown.length > 0) {
3514
+ lines.push(
3515
+ `## \u672A\u64A4\u53BB\u306E stray (${result.strayUnknown.length}) \u2014 basou \u751F\u6210\u306E repo link \u3068\u78BA\u8A8D\u3067\u304D\u306A\u3044\u305F\u3081\u64A4\u53BB\u3057\u307E\u305B\u3093\u3002\u624B\u52D5\u3067\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044`
3516
+ );
3517
+ for (const s of result.strayUnknown) {
3518
+ const detail = s.reason === "broken" ? "\u30EA\u30F3\u30AF\u5207\u308C(\u30BF\u30FC\u30B2\u30C3\u30C8\u304C\u89E3\u6C7A\u3067\u304D\u307E\u305B\u3093)" : s.reason === "non-repo" ? "git repo \u3067\u306A\u3044\u30BF\u30FC\u30B2\u30C3\u30C8(\u30D5\u30A1\u30A4\u30EB\u3001\u307E\u305F\u306F .git \u306E\u7121\u3044\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA)" : "\u7D76\u5BFE\u30D1\u30B9\u306E\u30BF\u30FC\u30B2\u30C3\u30C8(basou \u306F\u76F8\u5BFE\u30EA\u30F3\u30AF\u306E\u307F\u751F\u6210\u3057\u307E\u3059)";
3519
+ lines.push(`- ${s.name} -> ${s.target}: ${detail}`);
3520
+ }
3521
+ lines.push("");
3522
+ }
3523
+ lines.push(
3524
+ "\u6CE8: \u4F5C\u6210(--apply)\u306F\u65E2\u5B58\u30A8\u30F3\u30C8\u30EA\u3092\u4E0A\u66F8\u304D\u3057\u307E\u305B\u3093\u3002stray repo link \u306E\u64A4\u53BB\u306F --prune \u3067\u884C\u3044\u307E\u3059(symlink \u306E\u307F\u524A\u9664\u3057\u3001\u53C2\u7167\u5148 repo \u306F\u524A\u9664\u3057\u307E\u305B\u3093)\u3002basou \u751F\u6210\u3068\u78BA\u8A8D\u3067\u304D\u306A\u3044 stray(\u30EA\u30F3\u30AF\u5207\u308C / \u975E repo / \u7D76\u5BFE\u30D1\u30B9)\u306F\u64A4\u53BB\u3057\u307E\u305B\u3093\u3002"
3525
+ );
3526
+ return lines.join("\n");
3527
+ }
3528
+ async function runProjectPreset(options, ctx = {}) {
3529
+ try {
3530
+ await doRunProjectPreset(options, ctx);
3531
+ } catch (error) {
3532
+ renderCliError(error, { verbose: isVerbose(options) });
3533
+ process.exitCode = 1;
3534
+ }
3535
+ }
3536
+ function canonicalFileFor(anchorReal, canonicalName) {
3537
+ return join4(anchorReal, "agents", canonicalName, CANONICAL_FILE);
3538
+ }
3539
+ function canonicalLabelFor(canonicalName) {
3540
+ return join4("agents", canonicalName, CANONICAL_FILE);
3541
+ }
3542
+ async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
3543
+ const declared = {
3544
+ path: entry.path,
3545
+ ...entry.visibility !== void 0 ? { visibility: entry.visibility } : {},
3546
+ ...entry.language !== void 0 ? { language: entry.language } : {},
3547
+ ...entry.publishes !== void 0 ? { publishes: entry.publishes } : {}
3548
+ };
3549
+ let real;
3550
+ try {
3551
+ real = realpathSync(resolve3(repositoryRoot, entry.path));
3552
+ } catch {
3553
+ return { ...declared, isAnchor: false, reachable: false, canonicalPresent: false };
3554
+ }
3555
+ if (real === anchorReal) {
3556
+ return { ...declared, isAnchor: true, reachable: true, canonicalPresent: false };
3557
+ }
3558
+ if (!existsSync(join4(real, ".git"))) {
3559
+ return { ...declared, isAnchor: false, reachable: false, canonicalPresent: false };
3560
+ }
3561
+ const canonicalName = basename3(real);
3562
+ let content;
3563
+ try {
3564
+ content = await readMarkdownFile4(canonicalFileFor(anchorReal, canonicalName));
3565
+ } catch {
3566
+ return {
3567
+ ...declared,
3568
+ isAnchor: false,
3569
+ reachable: true,
3570
+ canonicalName,
3571
+ canonicalPresent: true,
3572
+ canonicalReadable: false
3573
+ };
3574
+ }
3575
+ if (content === null) {
3576
+ return {
3577
+ ...declared,
3578
+ isAnchor: false,
3579
+ reachable: true,
3580
+ canonicalName,
3581
+ canonicalPresent: false
3582
+ };
3583
+ }
3584
+ const section = parseMarkers(content);
3585
+ return {
3586
+ ...declared,
3587
+ isAnchor: false,
3588
+ reachable: true,
3589
+ canonicalName,
3590
+ canonicalPresent: true,
3591
+ canonicalReadable: true,
3592
+ markerKind: section.kind,
3593
+ ...section.kind === "ok" ? { currentBlock: section.generated } : {}
3594
+ };
3595
+ }
3596
+ async function applyPresetPlan(anchorReal, plan) {
3597
+ const file = canonicalFileFor(anchorReal, plan.canonicalName);
3598
+ const label = canonicalLabelFor(plan.canonicalName);
3599
+ let isLink = false;
3600
+ try {
3601
+ isLink = lstatSync(file).isSymbolicLink();
3602
+ } catch {
3603
+ isLink = false;
3604
+ }
3605
+ if (isLink) throw new Error(`Canonical is a symlink in ${label}`);
3606
+ if (plan.action === "create") mkdirSync(dirname(file), { recursive: true });
3607
+ const existing = await readMarkdownFile4(file);
3608
+ await writeMarkdownFile5(file, renderWithMarkers4(existing, plan.desiredBlock, label));
3609
+ }
3610
+ function presetFailureReason(error) {
3611
+ if (error instanceof Error && (error.message.startsWith("Markers") || error.message.startsWith("Canonical"))) {
3612
+ return error.message;
3613
+ }
3614
+ const cause = error instanceof Error ? error.cause : void 0;
3615
+ if (hasErrorCode(cause)) return cause.code;
3616
+ if (hasErrorCode(error)) return error.code;
3617
+ return "unknown error";
3618
+ }
3619
+ async function doRunProjectPreset(options, ctx) {
3620
+ const cwd = ctx.cwd ?? process.cwd();
3621
+ const repositoryRoot = await resolveBasouRootForCommand(cwd, "project preset");
3622
+ const paths = basouPaths9(repositoryRoot);
3623
+ const manifest = await readManifest5(paths);
3624
+ const roster = manifest.repos ?? [];
3625
+ const anchorReal = realpathSync(repositoryRoot);
3626
+ const facts = [];
3627
+ for (const entry of roster) facts.push(await gatherRepoPreset(repositoryRoot, anchorReal, entry));
3628
+ const summary = summarizePresetPlan(facts);
3629
+ const failures = [];
3630
+ let writtenCount = 0;
3631
+ if (options.apply === true && summary.plans.length > 0) {
3632
+ for (const plan of summary.plans) {
3633
+ try {
3634
+ await applyPresetPlan(anchorReal, plan);
3635
+ writtenCount += 1;
3636
+ } catch (error) {
3637
+ failures.push({ repo: plan.path, message: presetFailureReason(error) });
3638
+ }
3639
+ }
3640
+ }
3641
+ const result = {
3642
+ ...summary,
3643
+ hasRoster: roster.length > 0,
3644
+ applied: writtenCount > 0,
3645
+ failures
3646
+ };
3647
+ if (options.json === true) {
3648
+ console.log(JSON.stringify(result));
3649
+ } else {
3650
+ console.log(renderProjectPreset(result));
3651
+ }
3652
+ return result;
3653
+ }
3654
+ function presetActionLabel(action) {
3655
+ return action === "create" ? "\u65B0\u898F\u4F5C\u6210" : "\u66F4\u65B0";
3656
+ }
3657
+ function renderProjectPreset(result) {
3658
+ const lines = [];
3659
+ lines.push("# \u6307\u793A\u66F8 A \u30D7\u30EA\u30BB\u30C3\u30C8\u751F\u6210(\u5BA3\u8A00 \u2192 canonical \u306E\u751F\u6210\u9818\u57DF)");
3660
+ lines.push("");
3661
+ if (!result.hasRoster) {
3662
+ lines.push(
3663
+ "\u2139\uFE0F repo \u30ED\u30FC\u30B9\u30BF\u30FC\u304C\u672A\u5BA3\u8A00\u3067\u3059(manifest \u306E `repos`)\u3002`basou project adopt` \u3067\u5BA3\u8A00\u3057\u3066\u304B\u3089\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
3664
+ );
3665
+ return lines.join("\n");
3666
+ }
3667
+ if (result.plans.length > 0) {
3668
+ const attempted = result.applied || result.failures.length > 0;
3669
+ if (!attempted) {
3670
+ lines.push(
3671
+ `${result.plans.length} repo \u306E canonical \u306B A \u30D7\u30EA\u30BB\u30C3\u30C8\u3092\u751F\u6210\u4E88\u5B9A(dry-run\u3001\u53CD\u6620\u3059\u308B\u306B\u306F --apply):`
3672
+ );
3673
+ for (const p of result.plans) {
3674
+ lines.push(
3675
+ `- ${p.path} [${presetActionLabel(p.action)}] \u2192 ${canonicalLabelFor(p.canonicalName)}`
3676
+ );
3677
+ for (const bl of p.desiredBlock.split("\n")) lines.push(` ${bl}`);
3678
+ }
3679
+ } else {
3680
+ const failed = new Set(result.failures.map((f) => f.repo));
3681
+ const header = result.failures.length === 0 ? "\u2705 canonical \u306B A \u30D7\u30EA\u30BB\u30C3\u30C8\u3092\u751F\u6210\u3057\u307E\u3057\u305F:" : result.applied ? "A \u30D7\u30EA\u30BB\u30C3\u30C8\u3092\u751F\u6210\u3057\u307E\u3057\u305F(\u4E00\u90E8\u5931\u6557\u3001\u4E0B\u8A18\u53C2\u7167):" : "A \u30D7\u30EA\u30BB\u30C3\u30C8\u3092\u751F\u6210\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F(\u4E0B\u8A18\u53C2\u7167):";
3682
+ lines.push(header);
3683
+ for (const p of result.plans) {
3684
+ if (failed.has(p.path)) continue;
3685
+ lines.push(
3686
+ `- ${p.path} [${presetActionLabel(p.action)}] \u2192 ${canonicalLabelFor(p.canonicalName)}`
3687
+ );
3688
+ }
3689
+ }
3690
+ } else if (result.ok) {
3691
+ lines.push("\u2705 \u5BA3\u8A00\u3055\u308C\u305F\u5168 repo \u306E A \u30D7\u30EA\u30BB\u30C3\u30C8\u306F canonical \u3068\u540C\u671F\u6E08\u307F\u3067\u3059(\u751F\u6210\u4E0D\u8981)\u3002");
3692
+ } else {
3693
+ lines.push(
3694
+ "\u2139\uFE0F \u751F\u6210\u304C\u5FC5\u8981\u306A repo \u306F\u3042\u308A\u307E\u305B\u3093\u304C\u3001\u30DE\u30FC\u30AB\u30FC\u7AF6\u5408 / \u885D\u7A81 / \u672A\u5BA3\u8A00 / \u5230\u9054\u3067\u304D\u306A\u3044 repo \u304C\u3042\u308A\u307E\u3059(\u4E0B\u8A18\u53C2\u7167)\u3002"
3695
+ );
3696
+ }
3697
+ lines.push("");
3698
+ if (result.inSync.length > 0) {
3699
+ lines.push(`\u540C\u671F\u6E08\u307F (${result.inSync.length}): ${result.inSync.join(", ")}`);
3700
+ lines.push("");
3701
+ }
3702
+ if (result.failures.length > 0) {
3703
+ lines.push(
3704
+ `## \u66F8\u304D\u8FBC\u307F\u306B\u5931\u6557 (${result.failures.length}) \u2014 \u4E00\u90E8\u306E canonical \u3092\u66F8\u3051\u307E\u305B\u3093\u3067\u3057\u305F`
3705
+ );
3706
+ for (const f of result.failures) lines.push(`- ${f.repo}: ${f.message}`);
3707
+ lines.push("");
3708
+ }
3709
+ if (result.markerConflicts.length > 0) {
3710
+ lines.push(
3711
+ `## \u30DE\u30FC\u30AB\u30FC\u7AF6\u5408 (${result.markerConflicts.length}) \u2014 canonical \u306E\u30DE\u30FC\u30AB\u30FC\u304C\u7121\u3044/\u58CA\u308C\u3066\u3044\u308B\u305F\u3081\u4E0A\u66F8\u304D\u3057\u307E\u305B\u3093`
3712
+ );
3713
+ for (const c of result.markerConflicts) {
3714
+ const detail = c.reason === "no_markers" ? "\u30DE\u30FC\u30AB\u30FC\u9818\u57DF\u304C\u7121\u3044" : `\u30DE\u30FC\u30AB\u30FC\u4E0D\u6574\u5408(${c.reason})`;
3715
+ lines.push(`- ${c.repo}: ${detail}`);
3716
+ }
3717
+ lines.push(
3718
+ ` \u5BFE\u51E6: A \u30D7\u30EA\u30BB\u30C3\u30C8\u3092\u5165\u308C\u305F\u3044\u4F4D\u7F6E\u306B\u6B21\u306E2\u884C\u3092\u8FFD\u52A0\u3057\u3066\u304F\u3060\u3055\u3044 \u2014 \`${GENERATED_START}\` \u3068 \`${GENERATED_END}\`(\u7121\u3051\u308C\u3070 basou \u304C\u65B0\u898F canonical \u3092\u4F5C\u308A\u307E\u3059)\u3002`
3719
+ );
3720
+ lines.push("");
3721
+ }
3722
+ if (result.unreadable.length > 0) {
3723
+ lines.push(
3724
+ `## canonical \u8AAD\u307F\u53D6\u308A\u4E0D\u80FD (${result.unreadable.length}) \u2014 \u30C7\u30A3\u30EC\u30AF\u30C8\u30EA/\u6A29\u9650\u7B49\u3067\u8AAD\u3081\u307E\u305B\u3093`
3725
+ );
3726
+ for (const p of result.unreadable) lines.push(`- ${p}`);
3727
+ lines.push("");
3728
+ }
3729
+ if (result.collisions.length > 0) {
3730
+ lines.push(
3731
+ `## canonical \u885D\u7A81 (${result.collisions.length}) \u2014 \u5225 repo \u304C\u540C\u540D canonical \u3092\u5171\u6709(\u81EA\u52D5\u751F\u6210\u3057\u307E\u305B\u3093)`
3732
+ );
3733
+ for (const c of result.collisions) {
3734
+ lines.push(`- agents/${c.canonicalName}/AGENTS.md \u2190 ${c.repos.join(", ")}`);
3735
+ }
3736
+ lines.push("");
3737
+ }
3738
+ if (result.undeclared.length > 0) {
3739
+ lines.push(
3740
+ `## \u5BA3\u8A00\u306A\u3057 (${result.undeclared.length}) \u2014 visibility / language / publishes \u304C\u672A\u8A2D\u5B9A\u306E\u305F\u3081\u751F\u6210\u3057\u307E\u305B\u3093`
3741
+ );
3742
+ for (const p of result.undeclared) lines.push(`- ${p}`);
3743
+ lines.push("");
3744
+ }
3745
+ if (result.anchors.length > 0) {
3746
+ lines.push(
3747
+ `## anchor (${result.anchors.length}) \u2014 \u81EA\u8EAB\u306E AGENTS.md \u306F\u624B\u3067\u7DAD\u6301\u3059\u308B\u305F\u3081\u30B9\u30AD\u30C3\u30D7`
3748
+ );
3749
+ for (const p of result.anchors) lines.push(`- ${p}`);
3750
+ lines.push("");
3751
+ }
3752
+ if (result.unreachable.length > 0) {
3753
+ lines.push(`## \u5230\u9054\u4E0D\u80FD (${result.unreachable.length}) \u2014 \u30D1\u30B9\u672A\u89E3\u6C7A / git repo \u3067\u306A\u3044`);
3754
+ for (const p of result.unreachable) lines.push(`- ${p}`);
3755
+ lines.push("");
3756
+ }
3757
+ lines.push(
3758
+ "\u6CE8: \u30DE\u30FC\u30AB\u30FC\u9818\u57DF\u306E\u307F\u3092\u751F\u6210\u3057\u3001canonical \u306E\u624B\u66F8\u304D\u90E8\u5206(\u30DE\u30FC\u30AB\u30FC\u5916)\u306F\u4FDD\u6301\u3057\u307E\u3059\u3002\u751F\u6210\u5185\u5BB9\u306F manifest \u306E\u5BA3\u8A00\u304B\u3089\u5C0E\u51FA\u3055\u308C\u307E\u3059\u3002"
3759
+ );
3760
+ return lines.join("\n");
3761
+ }
3762
+ async function runProjectArchive(target, options, ctx = {}) {
3763
+ try {
3764
+ await doRunProjectArchive(target, options, ctx);
3765
+ } catch (error) {
3766
+ renderCliError(error, { verbose: isVerbose(options) });
3767
+ process.exitCode = 1;
3768
+ }
3769
+ }
3770
+ function gatherArchiveTeardown(repositoryRoot, manifest, target) {
3771
+ const empty = {
3772
+ inspected: false,
3773
+ viewLink: false,
3774
+ instructionFiles: [],
3775
+ gitignorePatterns: [],
3776
+ canonical: false
3777
+ };
3778
+ let real;
3779
+ try {
3780
+ real = realpathSync(resolve3(repositoryRoot, target));
3781
+ } catch {
3782
+ return empty;
3783
+ }
3784
+ const anchorReal = realpathSync(repositoryRoot);
3785
+ const canonicalName = basename3(real);
3786
+ const instructionFiles = [];
3787
+ for (const name of INSTRUCTION_FILES) {
3788
+ try {
3789
+ lstatSync(join4(real, name));
3790
+ instructionFiles.push(name);
3791
+ } catch {
3792
+ }
3793
+ }
3794
+ let ignored;
3795
+ try {
3796
+ ignored = new Set(readGitignoreLines(join4(real, ".gitignore")).map((l) => l.trim()));
3797
+ } catch {
3798
+ ignored = /* @__PURE__ */ new Set();
3799
+ }
3800
+ const gitignorePatterns = INSTRUCTION_FILES.filter((p) => ignored.has(p) || ignored.has(`/${p}`));
3801
+ const canonical2 = existsSync(join4(anchorReal, "agents", canonicalName, CANONICAL_FILE));
3802
+ let viewLink = false;
3803
+ const viewPath = manifest.workspace.view;
3804
+ if (viewPath !== void 0) {
3805
+ try {
3806
+ lstatSync(join4(resolveViewDir(repositoryRoot, viewPath), canonicalName));
3807
+ viewLink = true;
3808
+ } catch {
3809
+ }
3810
+ }
3811
+ return {
3812
+ inspected: true,
3813
+ viewLink,
3814
+ instructionFiles,
3815
+ gitignorePatterns: [...gitignorePatterns],
3816
+ canonical: canonical2
3817
+ };
3818
+ }
3819
+ function omitKey(obj, key) {
3820
+ const clone = { ...obj };
3821
+ delete clone[key];
3822
+ return clone;
3823
+ }
3824
+ function buildArchivedManifest(manifest, plan, updatedAt) {
3825
+ let next = { ...manifest, workspace: { ...manifest.workspace, updated_at: updatedAt } };
3826
+ next = plan.reposEmptied ? omitKey(next, "repos") : { ...next, repos: plan.nextRepos };
3827
+ if (plan.nextSourceRoots !== void 0) {
3828
+ if (plan.nextSourceRoots.length === 0) {
3829
+ const prunedImport = manifest.import !== void 0 ? omitKey(manifest.import, "source_roots") : {};
3830
+ next = Object.keys(prunedImport).length === 0 ? omitKey(next, "import") : { ...next, import: prunedImport };
3831
+ } else {
3832
+ next = {
3833
+ ...next,
3834
+ import: { ...manifest.import ?? {}, source_roots: plan.nextSourceRoots }
3835
+ };
3836
+ }
3837
+ }
3838
+ return next;
3839
+ }
3840
+ async function doRunProjectArchive(target, options, ctx) {
3841
+ const cwd = ctx.cwd ?? process.cwd();
3842
+ const repositoryRoot = await resolveBasouRootForCommand(cwd, "project archive");
3843
+ const paths = basouPaths9(repositoryRoot);
3844
+ const manifest = await readManifest5(paths);
3845
+ const roster = manifest.repos ?? [];
3846
+ let targetIsAnchor = false;
3847
+ try {
3848
+ targetIsAnchor = realpathSync(resolve3(repositoryRoot, target)) === realpathSync(repositoryRoot);
3849
+ } catch {
3850
+ targetIsAnchor = false;
3851
+ }
3852
+ const plan = planArchive({
3853
+ ...manifest.repos !== void 0 ? { repos: manifest.repos } : {},
3854
+ ...manifest.import?.source_roots !== void 0 ? { sourceRoots: manifest.import.source_roots } : {},
3855
+ target,
3856
+ targetIsAnchor
3857
+ });
3858
+ const teardown = plan.found && !plan.isAnchor ? gatherArchiveTeardown(repositoryRoot, manifest, target) : {
3859
+ inspected: false,
3860
+ viewLink: false,
3861
+ instructionFiles: [],
3862
+ gitignorePatterns: [],
3863
+ canonical: false
3864
+ };
3865
+ const applied = options.apply === true && plan.found && !plan.isAnchor;
3866
+ if (applied) {
3867
+ const now = ctx.now ?? (() => /* @__PURE__ */ new Date());
3868
+ await writeManifest2(paths, buildArchivedManifest(manifest, plan, now().toISOString()), {
3869
+ force: true
3870
+ });
3871
+ }
3872
+ const result = {
3873
+ ...plan,
3874
+ hasRoster: roster.length > 0,
3875
+ applied,
3876
+ teardown,
3877
+ preservedUnknownFields: unknownManifestKeys(manifest)
3878
+ };
3879
+ if (options.json === true) {
3880
+ console.log(JSON.stringify(result));
3881
+ } else {
3882
+ console.log(renderProjectArchive(result));
3883
+ }
3884
+ return result;
3885
+ }
3886
+ function renderProjectArchive(result) {
3887
+ const lines = [];
3888
+ lines.push("# repo \u306E archive(roster \u304B\u3089\u7573\u3080)");
3889
+ lines.push("");
3890
+ lines.push(...preservedUnknownLines(result.preservedUnknownFields));
3891
+ if (!result.hasRoster) {
3892
+ lines.push("\u2139\uFE0F repo \u30ED\u30FC\u30B9\u30BF\u30FC\u304C\u672A\u5BA3\u8A00\u3067\u3059(manifest \u306E `repos`)\u3002archive \u5BFE\u8C61\u304C\u3042\u308A\u307E\u305B\u3093\u3002");
3893
+ return lines.join("\n");
3894
+ }
3895
+ if (result.isAnchor) {
3896
+ lines.push(
3897
+ `\u26A0\uFE0F \`${result.target}\` \u306F anchor(\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E root)\u3067\u3059\u3002anchor \u306F archive \u3067\u304D\u307E\u305B\u3093(manifest \u306E\u5BB6\u306E\u305F\u3081)\u3002`
3898
+ );
3899
+ return lines.join("\n");
3900
+ }
3901
+ if (!result.found) {
3902
+ lines.push(`\u2139\uFE0F \`${result.target}\` \u306F roster \u306B\u5BA3\u8A00\u3055\u308C\u3066\u3044\u307E\u305B\u3093(archive \u5BFE\u8C61\u306A\u3057)\u3002`);
3903
+ return lines.join("\n");
3904
+ }
3905
+ if (result.applied) {
3906
+ lines.push(`\u2705 \`${result.target}\` \u3092 roster \u304B\u3089\u524A\u9664\u3057\u307E\u3057\u305F\u3002`);
3907
+ } else {
3908
+ lines.push(`\`${result.target}\` \u3092 roster \u304B\u3089\u524A\u9664\u4E88\u5B9A(dry-run\u3001\u53CD\u6620\u3059\u308B\u306B\u306F --apply):`);
3909
+ }
3910
+ if (result.sourceRootRemoval !== void 0) {
3911
+ lines.push(
3912
+ `- source_roots \u304B\u3089 ${result.sourceRootRemoval} \u3092 prune${result.applied ? "\u3057\u307E\u3057\u305F" : "\u3057\u307E\u3059"}(\u4EE5\u5F8C refresh \u306E\u5BFE\u8C61\u5916)\u3002`
3913
+ );
3914
+ } else {
3915
+ lines.push("- source_roots \u306B\u8A72\u5F53\u30A8\u30F3\u30C8\u30EA\u306F\u3042\u308A\u307E\u305B\u3093(prune \u4E0D\u8981)\u3002");
3916
+ }
3917
+ if (result.reposEmptied) {
3918
+ lines.push(
3919
+ "- \u3053\u308C\u304C\u6700\u5F8C\u306E\u30E1\u30F3\u30D0\u30FC\u3067\u3059 \u2192 roster \u306F\u7A7A\u306B\u306A\u308A `repos` \u5BA3\u8A00\u306F\u9664\u53BB\u3055\u308C\u307E\u3059(\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3092\u7573\u3080)\u3002"
3920
+ );
3921
+ } else if (result.becomesSolo) {
3922
+ lines.push(
3923
+ "- \u6B8B\u308A 1 repo(solo)\u306B\u306A\u308A\u307E\u3059 \u2192 workspace view \u306F\u4E0D\u8981\u3067\u3059(view \u5BA3\u8A00/\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306E\u64A4\u53BB\u3092\u691C\u8A0E)\u3002"
3924
+ );
3925
+ }
3926
+ lines.push("");
3927
+ const t = result.teardown;
3928
+ const items = [];
3929
+ if (t.viewLink) items.push("workspace view \u306E symlink \u30A8\u30F3\u30C8\u30EA");
3930
+ if (t.instructionFiles.length > 0) items.push(`\u6307\u793A\u66F8(${t.instructionFiles.join(", ")})`);
3931
+ if (t.gitignorePatterns.length > 0)
3932
+ items.push(`.gitignore \u306E\u6307\u793A\u66F8\u30D1\u30BF\u30FC\u30F3(${t.gitignorePatterns.join(", ")})`);
3933
+ if (t.canonical) items.push(`anchor \u306E canonical(agents/${basename3(result.target)}/AGENTS.md)`);
3934
+ if (!t.inspected) {
3935
+ lines.push("## \u624B\u52D5 teardown(repo \u304C\u30C7\u30A3\u30B9\u30AF\u4E0A\u306B\u89E3\u6C7A\u3067\u304D\u306A\u3044\u305F\u3081\u672A\u691C\u67FB)");
3936
+ lines.push(
3937
+ "- repo \u306F\u65E2\u306B\u524A\u9664\u6E08\u307F\u306E\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002view symlink / \u6307\u793A\u66F8 symlink / .gitignore / canonical \u304C\u6B8B\u3063\u3066\u3044\u306A\u3044\u304B\u624B\u52D5\u3067\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
3938
+ );
3939
+ lines.push("");
3940
+ } else if (items.length > 0) {
3941
+ lines.push("## \u624B\u52D5 teardown(--apply \u306F\u89E6\u308C\u307E\u305B\u3093\u3002\u6B8B\u3063\u3066\u3044\u308B wiring \u3092\u624B\u3067\u64A4\u53BB\u3057\u3066\u304F\u3060\u3055\u3044)");
3942
+ for (const i of items) lines.push(`- ${i}`);
3943
+ lines.push("");
3944
+ } else {
3945
+ lines.push("repo \u5074\u306E wiring(view/\u6307\u793A\u66F8/.gitignore/canonical)\u306F\u6B8B\u3063\u3066\u3044\u307E\u305B\u3093\u3002");
3946
+ lines.push("");
3947
+ }
3948
+ lines.push(
3949
+ "\u6CE8: archive \u306F manifest(.basou\u3001git \u8FFD\u8DE1=\u53EF\u9006)\u306E\u307F\u3092\u5909\u66F4\u3057\u307E\u3059\u3002repo\u30FB\u6355\u6349\u5C65\u6B74\u30FBon-disk \u306E wiring \u306F\u524A\u9664\u3057\u307E\u305B\u3093\u3002"
3950
+ );
3951
+ return lines.join("\n");
3952
+ }
3953
+ async function runProjectRename(oldPath, newPath, options, ctx = {}) {
3954
+ try {
3955
+ await doRunProjectRename(oldPath, newPath, options, ctx);
3956
+ } catch (error) {
3957
+ renderCliError(error, { verbose: isVerbose(options) });
3958
+ process.exitCode = 1;
3959
+ }
3960
+ }
3961
+ function gatherRenameWiring(repositoryRoot, manifest, oldBasename) {
3962
+ let anchorReal;
3963
+ try {
3964
+ anchorReal = realpathSync(repositoryRoot);
3965
+ } catch {
3966
+ return { canonicalDirOld: false, viewLinkOld: false };
3967
+ }
3968
+ const canonicalDirOld = existsSync(join4(anchorReal, "agents", oldBasename));
3969
+ let viewLinkOld = false;
3970
+ const viewPath = manifest.workspace.view;
3971
+ if (viewPath !== void 0) {
3972
+ try {
3973
+ lstatSync(join4(resolveViewDir(repositoryRoot, viewPath), oldBasename));
3974
+ viewLinkOld = true;
3975
+ } catch {
3976
+ }
3977
+ }
3978
+ return { canonicalDirOld, viewLinkOld };
3979
+ }
3980
+ function buildRenamedManifest(manifest, plan, updatedAt) {
3981
+ const next = {
3982
+ ...manifest,
3983
+ workspace: { ...manifest.workspace, updated_at: updatedAt },
3984
+ repos: plan.nextRepos
3985
+ };
3986
+ if (plan.nextSourceRoots !== void 0) {
3987
+ return { ...next, import: { ...manifest.import ?? {}, source_roots: plan.nextSourceRoots } };
3988
+ }
3989
+ return next;
3990
+ }
3991
+ async function doRunProjectRename(oldPath, newPath, options, ctx) {
3992
+ const cwd = ctx.cwd ?? process.cwd();
3993
+ const repositoryRoot = await resolveBasouRootForCommand(cwd, "project rename");
3994
+ const paths = basouPaths9(repositoryRoot);
3995
+ const manifest = await readManifest5(paths);
3996
+ const roster = manifest.repos ?? [];
3997
+ let oldIsAnchor = false;
3998
+ try {
3999
+ oldIsAnchor = realpathSync(resolve3(repositoryRoot, oldPath)) === realpathSync(repositoryRoot);
4000
+ } catch {
4001
+ oldIsAnchor = false;
4002
+ }
4003
+ const plan = planRename({
4004
+ ...manifest.repos !== void 0 ? { repos: manifest.repos } : {},
4005
+ ...manifest.import?.source_roots !== void 0 ? { sourceRoots: manifest.import.source_roots } : {},
4006
+ oldPath,
4007
+ newPath,
4008
+ oldIsAnchor
4009
+ });
4010
+ const actionable = plan.found && !plan.isAnchor && !plan.collision && !plan.noop;
4011
+ const wiring = actionable && plan.basenameChanged ? gatherRenameWiring(repositoryRoot, manifest, pathBasename(plan.oldTarget)) : { canonicalDirOld: false, viewLinkOld: false };
4012
+ const applied = options.apply === true && actionable;
4013
+ if (applied) {
4014
+ const now = ctx.now ?? (() => /* @__PURE__ */ new Date());
4015
+ await writeManifest2(paths, buildRenamedManifest(manifest, plan, now().toISOString()), {
4016
+ force: true
4017
+ });
4018
+ }
4019
+ const result = {
4020
+ ...plan,
4021
+ hasRoster: roster.length > 0,
4022
+ applied,
4023
+ wiring,
4024
+ preservedUnknownFields: unknownManifestKeys(manifest)
4025
+ };
4026
+ if (options.json === true) {
4027
+ console.log(JSON.stringify(result));
4028
+ } else {
4029
+ console.log(renderProjectRename(result));
4030
+ }
4031
+ return result;
4032
+ }
4033
+ function renderProjectRename(result) {
4034
+ const lines = [];
4035
+ lines.push("# repo \u306E rename(roster \u306E\u30D1\u30B9\u66F4\u65B0)");
4036
+ lines.push("");
4037
+ lines.push(...preservedUnknownLines(result.preservedUnknownFields));
4038
+ if (!result.hasRoster) {
4039
+ lines.push("\u2139\uFE0F repo \u30ED\u30FC\u30B9\u30BF\u30FC\u304C\u672A\u5BA3\u8A00\u3067\u3059(manifest \u306E `repos`)\u3002rename \u5BFE\u8C61\u304C\u3042\u308A\u307E\u305B\u3093\u3002");
4040
+ return lines.join("\n");
4041
+ }
4042
+ if (result.noop) {
4043
+ lines.push(`\u2139\uFE0F \`${result.oldTarget}\` \u3068 \`${result.newTarget}\` \u306F\u540C\u4E00\u3067\u3059(\u5909\u66F4\u306A\u3057)\u3002`);
4044
+ return lines.join("\n");
4045
+ }
4046
+ if (result.isAnchor) {
4047
+ lines.push(
4048
+ `\u26A0\uFE0F \`${result.oldTarget}\` \u306F anchor(\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E root)\u3067\u3059\u3002anchor \u306F rename \u3067\u304D\u307E\u305B\u3093\u3002`
4049
+ );
4050
+ return lines.join("\n");
4051
+ }
4052
+ if (!result.found) {
4053
+ lines.push(`\u2139\uFE0F \`${result.oldTarget}\` \u306F roster \u306B\u5BA3\u8A00\u3055\u308C\u3066\u3044\u307E\u305B\u3093(rename \u5BFE\u8C61\u306A\u3057)\u3002`);
4054
+ return lines.join("\n");
4055
+ }
4056
+ if (result.collision) {
4057
+ lines.push(
4058
+ `\u26A0\uFE0F \`${result.newTarget}\` \u306F\u65E2\u306B roster \u306B\u5BA3\u8A00\u3055\u308C\u3066\u3044\u307E\u3059\u3002\u91CD\u8907\u3092\u907F\u3051\u308B\u305F\u3081 rename \u3057\u307E\u305B\u3093\u3002`
4059
+ );
4060
+ return lines.join("\n");
4061
+ }
4062
+ if (result.applied) {
4063
+ lines.push(`\u2705 \`${result.oldTarget}\` \u3092 \`${result.newTarget}\` \u306B rename \u3057\u307E\u3057\u305F\u3002`);
4064
+ } else {
4065
+ lines.push(
4066
+ `\`${result.oldTarget}\` \u3092 \`${result.newTarget}\` \u306B rename \u4E88\u5B9A(dry-run\u3001\u53CD\u6620\u3059\u308B\u306B\u306F --apply):`
4067
+ );
4068
+ }
4069
+ if (result.sourceRootRenamed !== void 0) {
4070
+ lines.push(
4071
+ `- source_roots \u306E ${result.sourceRootRenamed} \u3092 ${result.newTarget} \u306B\u66F4\u65B0${result.applied ? "\u3057\u307E\u3057\u305F" : "\u3057\u307E\u3059"}\u3002`
4072
+ );
4073
+ } else {
4074
+ lines.push("- source_roots \u306B\u8A72\u5F53\u30A8\u30F3\u30C8\u30EA\u306F\u3042\u308A\u307E\u305B\u3093(\u66F4\u65B0\u4E0D\u8981)\u3002");
4075
+ }
4076
+ lines.push("");
4077
+ if (result.basenameChanged) {
4078
+ const oldName = pathBasename(result.oldTarget);
4079
+ const newName = pathBasename(result.newTarget);
4080
+ const items = [];
4081
+ if (result.wiring.canonicalDirOld)
4082
+ items.push(`anchor canonical: agents/${oldName}/ \u2192 agents/${newName}/`);
4083
+ if (result.wiring.viewLinkOld) items.push(`workspace view \u306E symlink: ${oldName} \u2192 ${newName}`);
4084
+ if (items.length > 0) {
4085
+ lines.push(
4086
+ "## \u624B\u52D5\u30EA\u30CD\u30FC\u30E0(--apply \u306F\u89E6\u308C\u307E\u305B\u3093\u3002basename \u304C\u5909\u308F\u308B\u305F\u3081\u624B\u3067\u66F4\u65B0\u3057\u3066\u304F\u3060\u3055\u3044)"
4087
+ );
4088
+ for (const i of items) lines.push(`- ${i}`);
4089
+ } else {
4090
+ lines.push(
4091
+ `basename \u304C ${oldName} \u2192 ${newName} \u306B\u5909\u308F\u308A\u307E\u3059\u304C\u3001anchor canonical / view symlink \u306F\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F\u3002`
4092
+ );
4093
+ }
4094
+ lines.push(
4095
+ " \u53CD\u6620\u5F8C\u306F `basou project symlinks` / `basou project workspace` \u3067\u6307\u793A\u66F8 symlink \u3068 view \u3092\u518D\u751F\u6210\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
4096
+ );
4097
+ } else {
4098
+ lines.push(
4099
+ "\u6CE8: basename \u306F\u4E0D\u5909\u3067\u3059\u3002repo \u3092\u5225\u306E\u5834\u6240\u3078\u79FB\u52D5\u3057\u305F\u5834\u5408\u306F `basou project symlinks` / `basou project workspace` \u3067\u76F8\u5BFE\u30BF\u30FC\u30B2\u30C3\u30C8\u3092\u518D\u751F\u6210\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
4100
+ );
4101
+ }
4102
+ lines.push("");
4103
+ lines.push(
4104
+ "\u6CE8: rename \u306F manifest(.basou\u3001git \u8FFD\u8DE1=\u53EF\u9006)\u306E\u307F\u3092\u5909\u66F4\u3057\u307E\u3059\u3002repo \u306E\u79FB\u52D5\u30FBon-disk \u306E wiring \u66F4\u65B0\u306F\u884C\u3044\u307E\u305B\u3093\u3002"
4105
+ );
4106
+ return lines.join("\n");
4107
+ }
4108
+
4109
+ // src/commands/refresh.ts
4110
+ import { assertBasouRootSafe as assertBasouRootSafe9, basouPaths as basouPaths10, findErrorCode as findErrorCode9 } from "@basou/core";
4111
+ import { InvalidArgumentError as InvalidArgumentError3 } from "commander";
4112
+
4113
+ // src/lib/portfolio-config.ts
4114
+ import { homedir as homedir3 } from "os";
4115
+ import { isAbsolute as isAbsolute2, join as join5, resolve as resolve4 } from "path";
4116
+ import { readYamlFile as readYamlFile3 } from "@basou/core";
4117
+ var DEFAULT_PORTFOLIO_CONFIG_PATH = join5(homedir3(), ".basou", "portfolio.yaml");
4118
+ function expandTilde(p) {
4119
+ if (p === "~") return homedir3();
4120
+ if (p.startsWith("~/")) return join5(homedir3(), p.slice(2));
4121
+ return p;
4122
+ }
4123
+ function isRecord(value) {
4124
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4125
+ }
4126
+ async function loadPortfolioConfig(configPath = DEFAULT_PORTFOLIO_CONFIG_PATH) {
4127
+ let raw;
4128
+ try {
4129
+ raw = await readYamlFile3(configPath);
4130
+ } catch (error) {
4131
+ if (error instanceof Error && error.message === "YAML file not found") {
4132
+ throw new Error(
4133
+ "No portfolio config at ~/.basou/portfolio.yaml. Create one (a 'workspaces:' list of repo paths) or pass --workspace <path>."
4134
+ );
4135
+ }
4136
+ if (error instanceof Error && error.message === "Failed to parse YAML content") {
4137
+ throw new Error("~/.basou/portfolio.yaml is not valid YAML.");
4138
+ }
4139
+ throw error;
4140
+ }
4141
+ if (!isRecord(raw) || !Array.isArray(raw.workspaces)) {
4142
+ throw new Error("~/.basou/portfolio.yaml must contain a 'workspaces:' list.");
4143
+ }
4144
+ const seen = /* @__PURE__ */ new Set();
4145
+ const result = [];
4146
+ for (const entry of raw.workspaces) {
4147
+ if (!isRecord(entry) || typeof entry.path !== "string" || entry.path.trim().length === 0) {
4148
+ throw new Error("Each portfolio workspace needs a non-empty string 'path'.");
4149
+ }
4150
+ if (entry.label !== void 0 && typeof entry.label !== "string") {
4151
+ throw new Error("A portfolio workspace 'label' must be a string when present.");
4152
+ }
4153
+ const expanded = expandTilde(entry.path.trim());
4154
+ if (!isAbsolute2(expanded)) {
4155
+ throw new Error(
4156
+ "Portfolio workspace paths must be absolute (or start with '~'); use --workspace for relative ad-hoc paths."
4157
+ );
4158
+ }
4159
+ const abs = resolve4(expanded);
4160
+ if (seen.has(abs)) continue;
4161
+ seen.add(abs);
4162
+ result.push(entry.label !== void 0 ? { path: abs, label: entry.label } : { path: abs });
4163
+ }
4164
+ if (result.length === 0) {
4165
+ throw new Error("~/.basou/portfolio.yaml has no workspaces.");
4166
+ }
4167
+ return result;
4168
+ }
4169
+
4170
+ // src/commands/refresh-watch.ts
4171
+ import { readdir as readdir2, stat as stat2 } from "fs/promises";
4172
+ import { homedir as homedir4 } from "os";
4173
+ import { join as join6 } from "path";
4174
+ import { findErrorCode as findErrorCode8 } from "@basou/core";
4175
+ var DEFAULT_WATCH_INTERVAL_SEC = 30;
4176
+ var MIN_WATCH_INTERVAL_SEC = 5;
4177
+ var MAX_WATCH_INTERVAL_SEC = 86400;
4178
+ function watchedRoots(ctx) {
4179
+ return [
4180
+ ctx.codexSessionsDir ?? join6(homedir4(), ".codex", "sessions"),
4181
+ ctx.claudeProjectsDir ?? join6(homedir4(), ".claude", "projects")
4182
+ ];
4183
+ }
4184
+ async function scanSourceLogs(roots) {
4185
+ const out = /* @__PURE__ */ new Map();
4186
+ const walk = async (dir) => {
4187
+ let entries;
2353
4188
  try {
2354
4189
  entries = await readdir2(dir, { withFileTypes: true });
2355
4190
  } catch (error) {
2356
- if (findErrorCode7(error, "ENOENT") || findErrorCode7(error, "ENOTDIR")) return;
4191
+ if (findErrorCode8(error, "ENOENT") || findErrorCode8(error, "ENOTDIR")) return;
2357
4192
  throw new Error("Failed to read a source log directory", { cause: error });
2358
4193
  }
2359
4194
  for (const entry of entries) {
2360
- const full = join5(dir, entry.name);
4195
+ const full = join6(dir, entry.name);
2361
4196
  if (entry.isDirectory()) {
2362
4197
  await walk(full);
2363
4198
  } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
@@ -2365,7 +4200,7 @@ async function scanSourceLogs(roots) {
2365
4200
  const info = await stat2(full);
2366
4201
  out.set(full, { mtimeMs: info.mtimeMs, size: info.size });
2367
4202
  } catch (error) {
2368
- if (findErrorCode7(error, "ENOENT")) continue;
4203
+ if (findErrorCode8(error, "ENOENT")) continue;
2369
4204
  throw new Error("Failed to stat a source log file", { cause: error });
2370
4205
  }
2371
4206
  }
@@ -2457,26 +4292,26 @@ function collectPath2(value, previous) {
2457
4292
  function parseInterval(value) {
2458
4293
  const seconds = Number(value);
2459
4294
  if (!Number.isInteger(seconds) || seconds < MIN_WATCH_INTERVAL_SEC || seconds > MAX_WATCH_INTERVAL_SEC) {
2460
- throw new InvalidArgumentError2(
4295
+ throw new InvalidArgumentError3(
2461
4296
  `--interval must be an integer between ${MIN_WATCH_INTERVAL_SEC} and ${MAX_WATCH_INTERVAL_SEC} (seconds).`
2462
4297
  );
2463
4298
  }
2464
4299
  return seconds;
2465
4300
  }
2466
4301
  function abortableSleep(ms, signal) {
2467
- return new Promise((resolve7) => {
4302
+ return new Promise((resolve8) => {
2468
4303
  if (signal.aborted) {
2469
- resolve7();
4304
+ resolve8();
2470
4305
  return;
2471
4306
  }
2472
4307
  let timer;
2473
4308
  const onAbort = () => {
2474
4309
  clearTimeout(timer);
2475
- resolve7();
4310
+ resolve8();
2476
4311
  };
2477
4312
  timer = setTimeout(() => {
2478
4313
  signal.removeEventListener("abort", onAbort);
2479
- resolve7();
4314
+ resolve8();
2480
4315
  }, ms);
2481
4316
  signal.addEventListener("abort", onAbort, { once: true });
2482
4317
  });
@@ -2567,8 +4402,8 @@ async function doRunRefreshWatch(options, ctx) {
2567
4402
  if (options.force === true) throw new Error("--watch cannot be combined with --force.");
2568
4403
  const cwd = ctx.cwd ?? process.cwd();
2569
4404
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "refresh");
2570
- const paths = basouPaths8(repositoryRoot);
2571
- await assertWorkspaceInitialized7(paths.root);
4405
+ const paths = basouPaths10(repositoryRoot);
4406
+ await assertWorkspaceInitialized8(paths.root);
2572
4407
  const intervalMs = (options.interval ?? DEFAULT_WATCH_INTERVAL_SEC) * 1e3;
2573
4408
  const controller = new AbortController();
2574
4409
  const onSignal = () => controller.abort();
@@ -2595,8 +4430,8 @@ async function doRunRefreshWatch(options, ctx) {
2595
4430
  async function computeRefresh(options, ctx) {
2596
4431
  const cwd = ctx.cwd ?? process.cwd();
2597
4432
  const repositoryRoot = await resolveBasouRootForCommand(cwd, "refresh");
2598
- const paths = basouPaths8(repositoryRoot);
2599
- await assertWorkspaceInitialized7(paths.root);
4433
+ const paths = basouPaths10(repositoryRoot);
4434
+ await assertWorkspaceInitialized8(paths.root);
2600
4435
  const nowIso = (ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()).toISOString();
2601
4436
  return refreshAll({
2602
4437
  options: {
@@ -2644,7 +4479,14 @@ function printRefreshSummary(result) {
2644
4479
  console.log(`handoff: skipped (${result.handoff.reason})`);
2645
4480
  }
2646
4481
  if (result.decisions.status === "generated") {
2647
- console.log(`decisions: regenerated (${result.decisions.decisionCount})`);
4482
+ if (result.decisions.decisionCount === 0) {
4483
+ const hasSessions = result.handoff.status === "generated" && result.handoff.sessionCount > 0;
4484
+ console.log(
4485
+ hasSessions ? "decisions: 0 (none auto-recorded from these sessions; record any made with 'basou decision record')" : "decisions: 0"
4486
+ );
4487
+ } else {
4488
+ console.log(`decisions: regenerated (${result.decisions.decisionCount})`);
4489
+ }
2648
4490
  } else {
2649
4491
  console.log(`decisions: skipped (${result.decisions.reason})`);
2650
4492
  }
@@ -2656,11 +4498,11 @@ function printRefreshSummary(result) {
2656
4498
  console.log(`orientation: skipped (${result.orientation.reason})`);
2657
4499
  }
2658
4500
  }
2659
- async function assertWorkspaceInitialized7(basouRoot) {
4501
+ async function assertWorkspaceInitialized8(basouRoot) {
2660
4502
  try {
2661
- await assertBasouRootSafe8(basouRoot);
4503
+ await assertBasouRootSafe9(basouRoot);
2662
4504
  } catch (error) {
2663
- if (findErrorCode8(error, "ENOENT")) {
4505
+ if (findErrorCode9(error, "ENOENT")) {
2664
4506
  throw new Error("Workspace not initialized. Run 'basou init' first.");
2665
4507
  }
2666
4508
  throw error;
@@ -2668,14 +4510,14 @@ async function assertWorkspaceInitialized7(basouRoot) {
2668
4510
  }
2669
4511
 
2670
4512
  // src/commands/report.ts
2671
- import { isAbsolute as isAbsolute2, resolve as resolve4 } from "path";
4513
+ import { isAbsolute as isAbsolute3, resolve as resolve5 } from "path";
2672
4514
  import {
2673
- assertBasouRootSafe as assertBasouRootSafe9,
2674
- basouPaths as basouPaths9,
2675
- findErrorCode as findErrorCode9,
4515
+ assertBasouRootSafe as assertBasouRootSafe10,
4516
+ basouPaths as basouPaths11,
4517
+ findErrorCode as findErrorCode10,
2676
4518
  renderReport,
2677
4519
  resolveRepositoryRoot as resolveRepositoryRoot8,
2678
- writeMarkdownFile as writeMarkdownFile5
4520
+ writeMarkdownFile as writeMarkdownFile6
2679
4521
  } from "@basou/core";
2680
4522
  function registerReportCommand(program2) {
2681
4523
  const report = program2.command("report").description(
@@ -2696,8 +4538,8 @@ async function runReportGenerate(options, ctx = {}) {
2696
4538
  async function doRunReportGenerate(options, ctx) {
2697
4539
  const cwd = ctx.cwd ?? process.cwd();
2698
4540
  const repositoryRoot = await resolveRepositoryRootForReport(cwd);
2699
- const paths = basouPaths9(repositoryRoot);
2700
- await assertWorkspaceInitialized8(paths.root);
4541
+ const paths = basouPaths11(repositoryRoot);
4542
+ await assertWorkspaceInitialized9(paths.root);
2701
4543
  const nowIso = (ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()).toISOString();
2702
4544
  const result = await renderReport({
2703
4545
  paths,
@@ -2708,8 +4550,8 @@ async function doRunReportGenerate(options, ctx) {
2708
4550
  onTaskSkip: (taskId, reason) => printTaskSkip(taskId, reason)
2709
4551
  });
2710
4552
  if (options.out !== void 0) {
2711
- const outPath = isAbsolute2(options.out) ? options.out : resolve4(cwd, options.out);
2712
- await writeMarkdownFile5(outPath, result.body);
4553
+ const outPath = isAbsolute3(options.out) ? options.out : resolve5(cwd, options.out);
4554
+ await writeMarkdownFile6(outPath, result.body);
2713
4555
  const { sessions, decisions, tasks } = result.data;
2714
4556
  console.error(
2715
4557
  `Wrote report to ${options.out} (sessions: ${sessions.total}, decisions: ${decisions.count}, tasks: ${tasks.total})`
@@ -2734,25 +4576,149 @@ async function resolveRepositoryRootForReport(cwd) {
2734
4576
  throw error;
2735
4577
  }
2736
4578
  }
2737
- async function assertWorkspaceInitialized8(basouRoot) {
4579
+ async function assertWorkspaceInitialized9(basouRoot) {
2738
4580
  try {
2739
- await assertBasouRootSafe9(basouRoot);
4581
+ await assertBasouRootSafe10(basouRoot);
2740
4582
  } catch (error) {
2741
- if (findErrorCode9(error, "ENOENT")) {
4583
+ if (findErrorCode10(error, "ENOENT")) {
2742
4584
  throw new Error("Workspace not initialized. Run 'basou init' first.");
2743
4585
  }
2744
4586
  throw error;
2745
4587
  }
2746
4588
  }
2747
4589
 
4590
+ // src/commands/review-gaps.ts
4591
+ import {
4592
+ basouPaths as basouPaths12,
4593
+ findReviewGaps
4594
+ } from "@basou/core";
4595
+ import { InvalidArgumentError as InvalidArgumentError4 } from "commander";
4596
+ function collectRepo(value, previous) {
4597
+ return [...previous, value];
4598
+ }
4599
+ function parseWindow(value) {
4600
+ const hours = Number(value);
4601
+ if (!Number.isInteger(hours) || hours <= 0) {
4602
+ throw new InvalidArgumentError4("--window must be a positive integer (hours).");
4603
+ }
4604
+ return hours;
4605
+ }
4606
+ function registerReviewGapsCommand(program2) {
4607
+ program2.command("review-gaps").description(
4608
+ "Surface units of work committed without a bound cross-model review trail (read-only, advisory)"
4609
+ ).option(
4610
+ "--repo <name>",
4611
+ "Restrict to a repo by name (repeatable; default: every repo with captured commits)",
4612
+ collectRepo,
4613
+ []
4614
+ ).option(
4615
+ "--window <hours>",
4616
+ "Hours before a commit to look for a review (default 24)",
4617
+ parseWindow
4618
+ ).option("--json", "Output the result as JSON").option("-v, --verbose", "Show error causes").action(async (opts) => {
4619
+ await runReviewGaps(opts);
4620
+ });
4621
+ }
4622
+ async function runReviewGaps(options, ctx = {}) {
4623
+ try {
4624
+ await doRunReviewGaps(options, ctx);
4625
+ } catch (error) {
4626
+ renderCliError(error, { verbose: isVerbose(options) });
4627
+ process.exitCode = 1;
4628
+ }
4629
+ }
4630
+ async function doRunReviewGaps(options, ctx) {
4631
+ const cwd = ctx.cwd ?? process.cwd();
4632
+ const repositoryRoot = await resolveBasouRootForCommand(cwd, "review-gaps");
4633
+ const paths = basouPaths12(repositoryRoot);
4634
+ const nowIso = (ctx.nowProvider?.() ?? /* @__PURE__ */ new Date()).toISOString();
4635
+ const summary = await findReviewGaps({
4636
+ paths,
4637
+ nowIso,
4638
+ ...options.repo !== void 0 && options.repo.length > 0 ? { scope: options.repo } : {},
4639
+ ...options.window !== void 0 ? { windowHours: options.window } : {},
4640
+ onWarning: (w, sid) => printReplayWarning(w, sid),
4641
+ onSessionSkip: (sid, reason) => printSessionSkip(sid, reason)
4642
+ });
4643
+ if (options.json === true) {
4644
+ console.log(JSON.stringify(summary));
4645
+ } else {
4646
+ console.log(renderReviewGaps(summary));
4647
+ }
4648
+ return summary;
4649
+ }
4650
+ function relAge(iso, now) {
4651
+ if (iso === null) return "(\u4E0D\u660E)";
4652
+ const ms = now.getTime() - Date.parse(iso);
4653
+ if (!Number.isFinite(ms) || ms < 0) return "\u305F\u3063\u305F\u4ECA";
4654
+ const days = Math.floor(ms / 864e5);
4655
+ if (days >= 1) return `${days}\u65E5\u524D`;
4656
+ const hours = Math.floor(ms / 36e5);
4657
+ if (hours >= 1) return `${hours}\u6642\u9593\u524D`;
4658
+ return `${Math.max(1, Math.floor(ms / 6e4))}\u5206\u524D`;
4659
+ }
4660
+ function unitLine(u, now) {
4661
+ const when = relAge(u.lastCommitAt, now);
4662
+ const head = `- ${u.repo} ${when} (${u.commitCount} commit${u.commitCount === 1 ? "" : "s"})`;
4663
+ if (u.verdict === "near_unbound") {
4664
+ const ids = u.reviews.map((r) => r.sessionId.slice(0, 14)).join(", ");
4665
+ return `${head} \u2014 \u8FD1\u63A5\u30EC\u30D3\u30E5\u30FC\u306F\u3042\u308B\u304C diff/\u5909\u66F4\u30D5\u30A1\u30A4\u30EB\u3092\u78BA\u8A8D\u3057\u3066\u3044\u306A\u3044 [${ids}]`;
4666
+ }
4667
+ return `${head} \u2014 \u7D10\u3065\u304F\u30AF\u30ED\u30B9\u30E2\u30C7\u30EB\u30EC\u30D3\u30E5\u30FC\u306A\u3057`;
4668
+ }
4669
+ function candidateLine(u, now) {
4670
+ const when = relAge(u.lastCommitAt, now);
4671
+ const cite = u.reviews.map((r) => `${r.sessionId.slice(0, 14)}${r.examinedDiff ? "(diff)" : ""}`).join(", ");
4672
+ return `- ${u.repo} ${when} (${u.commitCount} commit${u.commitCount === 1 ? "" : "s"}) \u2014 \u30EC\u30D3\u30E5\u30FC\u5F62\u8DE1: ${cite}`;
4673
+ }
4674
+ function renderReviewGaps(summary) {
4675
+ const now = new Date(summary.generatedAt);
4676
+ const lines = [];
4677
+ const scope = summary.scope ? summary.scope.join(", ") : "\u5168\u30EA\u30DD\u30B8\u30C8\u30EA";
4678
+ lines.push(`# \u30EC\u30D3\u30E5\u30FC\u8A3C\u8DE1\u306E\u30AE\u30E3\u30C3\u30D7 (${scope})`);
4679
+ lines.push("");
4680
+ if (summary.gaps.length === 0) {
4681
+ lines.push("\u2705 \u53D6\u308A\u8FBC\u307F\u6E08\u307F\u306E\u7BC4\u56F2\u3067\u306F\u3001\u30EC\u30D3\u30E5\u30FC\u8A3C\u8DE1\u306A\u3057\u3067\u7740\u5730\u3057\u305F\u4F5C\u696D\u5358\u4F4D\u306F\u3042\u308A\u307E\u305B\u3093\u3002");
4682
+ } else {
4683
+ lines.push(`\u26A0\uFE0F \u30EC\u30D3\u30E5\u30FC\u8A3C\u8DE1\u306A\u3057\u3067\u7740\u5730\u3057\u305F\u4F5C\u696D\u5358\u4F4D: ${summary.gaps.length}`);
4684
+ for (const u of summary.gaps) lines.push(unitLine(u, now));
4685
+ }
4686
+ lines.push("");
4687
+ if (summary.candidates.length > 0) {
4688
+ lines.push(
4689
+ `## \u78BA\u8A8D\u5F85\u3061 (${summary.candidates.length}) \u2014 \u30AF\u30ED\u30B9\u30E2\u30C7\u30EB\u304C\u30EC\u30D3\u30E5\u30FC\u3057\u305F\u5F62\u8DE1\u3042\u308A\u3002\u3053\u306E\u5909\u66F4\u3092\u672C\u5F53\u306B\u898B\u305F\u304B\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044`
4690
+ );
4691
+ for (const u of summary.candidates) lines.push(candidateLine(u, now));
4692
+ lines.push("");
4693
+ }
4694
+ if (summary.unknowns.length > 0) {
4695
+ const n = summary.unknowns.reduce((sum, u) => sum + u.commitCount, 0);
4696
+ lines.push(
4697
+ `## \u5C0E\u51FA\u4E0D\u53EF (${summary.unknowns.length} \u5358\u4F4D / ${n} commit) \u2014 repo \u304B\u6642\u523B\u3092\u6355\u6349\u304B\u3089\u5C0E\u3051\u305A\u3001\u5224\u5B9A\u3092\u4FDD\u7559(clear \u3067\u306F\u3042\u308A\u307E\u305B\u3093)`
4698
+ );
4699
+ lines.push("");
4700
+ }
4701
+ lines.push("## \u30EA\u30DD\u30B8\u30C8\u30EA\u5225");
4702
+ for (const r of summary.repos) {
4703
+ lines.push(
4704
+ `- ${r.repo}: ${r.units} \u5358\u4F4D (\u8A3C\u8DE1\u306A\u3057 ${r.omissionUnits} / \u8FD1\u63A5\u306E\u307F ${r.nearUnboundUnits} / \u78BA\u8A8D\u5F85\u3061 ${r.candidateUnits}${r.unknownUnits > 0 ? ` / \u4E0D\u660E ${r.unknownUnits}` : ""})`
4705
+ );
4706
+ }
4707
+ lines.push("");
4708
+ lines.push(
4709
+ `\u6CE8: read-only \u306E advisory \u3067\u3059\u3002\u53D6\u308A\u8FBC\u307F\u6E08\u307F\u306E commit \u306E\u307F\u304C\u5BFE\u8C61\uFF08\u6700\u65B0\u53D6\u8FBC commit: ${summary.newestCommitAt === null ? "\u306A\u3057" : relAge(summary.newestCommitAt, now)}\uFF09\u3002\u30EC\u30D3\u30E5\u30FC\u306E\u300C\u5B9F\u65BD\u300D\u306F\u81EA\u52D5\u5224\u5B9A\u305B\u305A\u3001\u6642\u9593\u7684\u8FD1\u63A5\u3060\u3051\u3067\u306F\u5408\u683C\u306B\u3057\u307E\u305B\u3093\u3002enforce \u306F\u3057\u307E\u305B\u3093\u3002`
4710
+ );
4711
+ return lines.join("\n");
4712
+ }
4713
+
2748
4714
  // src/commands/run.ts
2749
4715
  import { mkdir as mkdir2 } from "fs/promises";
2750
4716
  import { homedir as homedir5 } from "os";
2751
- import { join as join6 } from "path";
4717
+ import { join as join7 } from "path";
2752
4718
  import {
2753
- acquireLock as acquireLock4,
2754
- assertBasouRootSafe as assertBasouRootSafe10,
2755
- basouPaths as basouPaths10,
4719
+ acquireLock as acquireLock5,
4720
+ assertBasouRootSafe as assertBasouRootSafe11,
4721
+ basouPaths as basouPaths13,
2756
4722
  ChildProcessRunner as ChildProcessRunner2,
2757
4723
  claudeCodeAdapterMetadata,
2758
4724
  appendChainedEvent as coreAppendChainedEvent2,
@@ -2761,7 +4727,7 @@ import {
2761
4727
  getSnapshot as getSnapshot2,
2762
4728
  overwriteYamlFile as overwriteYamlFile2,
2763
4729
  prefixedUlid as prefixedUlid4,
2764
- readManifest as readManifest4,
4730
+ readManifest as readManifest6,
2765
4731
  readYamlFile as readYamlFile4,
2766
4732
  resolveClaudeCodeCommand,
2767
4733
  resolveRepositoryRoot as resolveRepositoryRoot9,
@@ -2797,17 +4763,17 @@ async function runClaudeCode(args, options, ctx = {}) {
2797
4763
  const { command } = await resolveCommand();
2798
4764
  const cwd = options.cwd ?? process.cwd();
2799
4765
  const repoRoot = await resolveRepositoryRootForRun(cwd);
2800
- const paths = basouPaths10(repoRoot);
2801
- await assertBasouRootSafe10(paths.root);
2802
- const manifest = await readManifest4(paths);
4766
+ const paths = basouPaths13(repoRoot);
4767
+ await assertBasouRootSafe11(paths.root);
4768
+ const manifest = await readManifest6(paths);
2803
4769
  const sessionId = prefixedUlid4("ses");
2804
- const sessionDir = join6(paths.sessions, sessionId);
4770
+ const sessionDir = join7(paths.sessions, sessionId);
2805
4771
  await mkdir2(sessionDir, { recursive: true });
2806
4772
  const appendEvent = ctx.appendEvent ?? (async (_sessionDir, event) => {
2807
4773
  await coreAppendChainedEvent2(paths, sessionId, event);
2808
4774
  });
2809
4775
  const startedAt = now().toISOString();
2810
- const sessionYamlPath = join6(sessionDir, "session.yaml");
4776
+ const sessionYamlPath = join7(sessionDir, "session.yaml");
2811
4777
  const session = buildInitialSession2({
2812
4778
  id: sessionId,
2813
4779
  command,
@@ -2840,7 +4806,7 @@ async function runClaudeCode(args, options, ctx = {}) {
2840
4806
  from: "initialized",
2841
4807
  to: "running"
2842
4808
  });
2843
- const runningLock = await acquireLock4(paths, "session", sessionId);
4809
+ const runningLock = await acquireLock5(paths, "session", sessionId);
2844
4810
  try {
2845
4811
  await mutateSessionYaml2(sessionYamlPath, (s) => {
2846
4812
  s.session.status = "running";
@@ -3150,29 +5116,28 @@ async function resolveRepositoryRootForRun(cwd) {
3150
5116
 
3151
5117
  // src/commands/session.ts
3152
5118
  import { readFile as readFile2 } from "fs/promises";
3153
- import { basename as basename3, isAbsolute as isAbsolute3, join as join7, relative as relative2 } from "path";
5119
+ import { basename as basename4, isAbsolute as isAbsolute4, join as join8, relative as relative3 } from "path";
3154
5120
  import {
3155
- acquireLock as acquireLock5,
3156
- appendEventToExistingSession as appendEventToExistingSession2,
3157
- assertBasouRootSafe as assertBasouRootSafe11,
3158
- basouPaths as basouPaths11,
5121
+ acquireLock as acquireLock6,
5122
+ appendEventToExistingSession as appendEventToExistingSession3,
5123
+ assertBasouRootSafe as assertBasouRootSafe12,
5124
+ basouPaths as basouPaths14,
3159
5125
  enumerateSessionDirs as enumerateSessionDirs2,
3160
- findErrorCode as findErrorCode10,
5126
+ findErrorCode as findErrorCode11,
3161
5127
  importSessionFromJson as importSessionFromJson2,
3162
5128
  loadSessionEntries,
3163
5129
  readAllEvents,
3164
- readManifest as readManifest5,
5130
+ readManifest as readManifest7,
3165
5131
  readYamlFile as readYamlFile5,
3166
5132
  rechainSessionInPlace,
3167
- resolveRepositoryRoot as resolveRepositoryRoot10,
3168
- resolveSessionId as resolveSessionId2,
5133
+ resolveSessionId as resolveSessionId3,
3169
5134
  resolveTaskId,
3170
5135
  SessionImportPayloadSchema as SessionImportPayloadSchema2,
3171
5136
  SessionSchema as SessionSchema3,
3172
5137
  SessionStatusSchema,
3173
5138
  sessionWorkStatsFromEvents
3174
5139
  } from "@basou/core";
3175
- import { InvalidArgumentError as InvalidArgumentError3 } from "commander";
5140
+ import { InvalidArgumentError as InvalidArgumentError5 } from "commander";
3176
5141
 
3177
5142
  // src/lib/format-duration.ts
3178
5143
  import { formatDurationMs } from "@basou/core";
@@ -3221,8 +5186,8 @@ async function runSessionList(options, ctx = {}) {
3221
5186
  async function doRunSessionList(options, ctx) {
3222
5187
  const cwd = ctx.cwd ?? process.cwd();
3223
5188
  const repositoryRoot = await resolveRepositoryRootForSession(cwd, "list");
3224
- const paths = basouPaths11(repositoryRoot);
3225
- await assertWorkspaceInitialized9(paths.root);
5189
+ const paths = basouPaths14(repositoryRoot);
5190
+ await assertWorkspaceInitialized10(paths.root);
3226
5191
  const now = /* @__PURE__ */ new Date();
3227
5192
  const records = (await loadSessionEntries(paths, {
3228
5193
  now,
@@ -3273,17 +5238,17 @@ async function runSessionShow(idInput, options, ctx = {}) {
3273
5238
  async function doRunSessionShow(idInput, options, ctx) {
3274
5239
  const cwd = ctx.cwd ?? process.cwd();
3275
5240
  const repositoryRoot = await resolveRepositoryRootForSession(cwd, "show");
3276
- const paths = basouPaths11(repositoryRoot);
3277
- await assertWorkspaceInitialized9(paths.root);
3278
- const sessionId = await resolveSessionId2(paths, idInput);
3279
- const sessionDir = join7(paths.sessions, sessionId);
3280
- const sessionYamlPath = join7(sessionDir, "session.yaml");
5241
+ const paths = basouPaths14(repositoryRoot);
5242
+ await assertWorkspaceInitialized10(paths.root);
5243
+ const sessionId = await resolveSessionId3(paths, idInput);
5244
+ const sessionDir = join8(paths.sessions, sessionId);
5245
+ const sessionYamlPath = join8(sessionDir, "session.yaml");
3281
5246
  let session;
3282
5247
  try {
3283
5248
  const raw = await readYamlFile5(sessionYamlPath);
3284
5249
  session = SessionSchema3.parse(raw);
3285
5250
  } catch (error) {
3286
- if (findErrorCode10(error, "ENOENT")) {
5251
+ if (findErrorCode11(error, "ENOENT")) {
3287
5252
  throw new Error(`Session not found: ${idInput}`);
3288
5253
  }
3289
5254
  throw new Error("Failed to read session", { cause: error });
@@ -3398,12 +5363,12 @@ function formatSessionWork(session, events, now) {
3398
5363
  }
3399
5364
  function formatWorkingDir(workingDir, repositoryRoot, options) {
3400
5365
  if (options.fullPath === true) return workingDir;
3401
- if (!isAbsolute3(workingDir)) {
5366
+ if (!isAbsolute4(workingDir)) {
3402
5367
  if (workingDir === ".") return "<repository_root>";
3403
5368
  return workingDir;
3404
5369
  }
3405
5370
  if (workingDir === repositoryRoot) return "<repository_root>";
3406
- const rel = relative2(repositoryRoot, workingDir);
5371
+ const rel = relative3(repositoryRoot, workingDir);
3407
5372
  if (rel.length === 0 || rel === ".") return "<repository_root>";
3408
5373
  if (rel.startsWith("..")) return rel;
3409
5374
  return `./${rel}`;
@@ -3517,23 +5482,13 @@ function maxLen2(values, floor) {
3517
5482
  return max;
3518
5483
  }
3519
5484
  async function resolveRepositoryRootForSession(cwd, subcmd) {
3520
- try {
3521
- return await resolveRepositoryRoot10(cwd);
3522
- } catch (error) {
3523
- if (error instanceof Error && error.message === "Not a git repository") {
3524
- throw new Error(
3525
- `Not a git repository. Run 'git init' first, then re-run 'basou session ${subcmd}'.`,
3526
- { cause: error }
3527
- );
3528
- }
3529
- throw error;
3530
- }
5485
+ return resolveBasouRootForCommand(cwd, `session ${subcmd}`);
3531
5486
  }
3532
- async function assertWorkspaceInitialized9(basouRoot) {
5487
+ async function assertWorkspaceInitialized10(basouRoot) {
3533
5488
  try {
3534
- await assertBasouRootSafe11(basouRoot);
5489
+ await assertBasouRootSafe12(basouRoot);
3535
5490
  } catch (error) {
3536
- if (findErrorCode10(error, "ENOENT")) {
5491
+ if (findErrorCode11(error, "ENOENT")) {
3537
5492
  throw new Error("Workspace not initialized. Run 'basou init' first.");
3538
5493
  }
3539
5494
  throw error;
@@ -3571,9 +5526,9 @@ async function runSessionImport(options, ctx = {}) {
3571
5526
  async function doRunSessionImport(options, ctx) {
3572
5527
  const cwd = ctx.cwd ?? process.cwd();
3573
5528
  const repositoryRoot = await resolveRepositoryRootForSession(cwd, "import");
3574
- const paths = basouPaths11(repositoryRoot);
3575
- await assertWorkspaceInitialized9(paths.root);
3576
- const manifest = await readManifest5(paths);
5529
+ const paths = basouPaths14(repositoryRoot);
5530
+ await assertWorkspaceInitialized10(paths.root);
5531
+ const manifest = await readManifest7(paths);
3577
5532
  const rawBody = await readInputFile(options.from);
3578
5533
  const json = parseJsonStrict(rawBody);
3579
5534
  const parsed = SessionImportPayloadSchema2.safeParse(json);
@@ -3602,10 +5557,10 @@ async function readInputFile(path) {
3602
5557
  try {
3603
5558
  return await readFile2(path, "utf8");
3604
5559
  } catch (error) {
3605
- if (findErrorCode10(error, "ENOENT")) {
5560
+ if (findErrorCode11(error, "ENOENT")) {
3606
5561
  throw new Error("Import source not found", { cause: error });
3607
5562
  }
3608
- if (findErrorCode10(error, "EISDIR")) {
5563
+ if (findErrorCode11(error, "EISDIR")) {
3609
5564
  throw new Error("Import source is not a file", { cause: error });
3610
5565
  }
3611
5566
  throw new Error("Failed to read import source", { cause: error });
@@ -3620,19 +5575,19 @@ function parseJsonStrict(body) {
3620
5575
  }
3621
5576
  function parseImportFormat(raw) {
3622
5577
  if (raw !== "json") {
3623
- throw new InvalidArgumentError3(`Unsupported format: ${raw}. Valid values: json`);
5578
+ throw new InvalidArgumentError5(`Unsupported format: ${raw}. Valid values: json`);
3624
5579
  }
3625
5580
  return "json";
3626
5581
  }
3627
5582
  function parseLabelOverride(raw) {
3628
5583
  if (raw.length === 0) {
3629
- throw new InvalidArgumentError3("Label must not be empty");
5584
+ throw new InvalidArgumentError5("Label must not be empty");
3630
5585
  }
3631
5586
  return raw;
3632
5587
  }
3633
5588
  function parseTaskIdOverride(raw) {
3634
5589
  if (raw.length === 0) {
3635
- throw new InvalidArgumentError3("Task id is empty");
5590
+ throw new InvalidArgumentError5("Task id is empty");
3636
5591
  }
3637
5592
  return raw;
3638
5593
  }
@@ -3658,7 +5613,7 @@ function printSessionImportResult(options, result) {
3658
5613
  return;
3659
5614
  }
3660
5615
  console.log(
3661
- `Imported session ${sid} (${result.eventCount} events) from ${basename3(options.from)}`
5616
+ `Imported session ${sid} (${result.eventCount} events) from ${basename4(options.from)}`
3662
5617
  );
3663
5618
  }
3664
5619
  var NOTE_BODY_PREVIEW_LIMIT = 80;
@@ -3685,19 +5640,19 @@ async function doRunSessionNote(sessionIdInput, options, ctx) {
3685
5640
  }
3686
5641
  const cwd = ctx.cwd ?? process.cwd();
3687
5642
  const repositoryRoot = await resolveRepositoryRootForSession(cwd, "note");
3688
- const paths = basouPaths11(repositoryRoot);
3689
- await assertWorkspaceInitialized9(paths.root);
3690
- const sessionId = await resolveSessionId2(paths, sessionIdInput);
5643
+ const paths = basouPaths14(repositoryRoot);
5644
+ await assertWorkspaceInitialized10(paths.root);
5645
+ const sessionId = await resolveSessionId3(paths, sessionIdInput);
3691
5646
  const body = hasBody ? options.body : await readNoteFile(options.fromFile);
3692
5647
  if (body.length === 0) {
3693
5648
  throw new Error("Note body is empty");
3694
5649
  }
3695
5650
  const occurredAt = (/* @__PURE__ */ new Date()).toISOString();
3696
5651
  const sesId = sessionId;
3697
- const sessionLock = await acquireLock5(paths, "session", sesId);
5652
+ const sessionLock = await acquireLock6(paths, "session", sesId);
3698
5653
  let result;
3699
5654
  try {
3700
- result = await appendEventToExistingSession2({
5655
+ result = await appendEventToExistingSession3({
3701
5656
  paths,
3702
5657
  sessionId: sesId,
3703
5658
  eventBuilder: (eventId) => ({
@@ -3719,10 +5674,10 @@ async function readNoteFile(path) {
3719
5674
  try {
3720
5675
  return await readFile2(path, "utf8");
3721
5676
  } catch (error) {
3722
- if (findErrorCode10(error, "ENOENT")) {
5677
+ if (findErrorCode11(error, "ENOENT")) {
3723
5678
  throw new Error("Note source not found", { cause: error });
3724
5679
  }
3725
- if (findErrorCode10(error, "EISDIR")) {
5680
+ if (findErrorCode11(error, "EISDIR")) {
3726
5681
  throw new Error("Note source is not a file", { cause: error });
3727
5682
  }
3728
5683
  throw new Error("Failed to read note source", { cause: error });
@@ -3730,7 +5685,7 @@ async function readNoteFile(path) {
3730
5685
  }
3731
5686
  function parseNoteBodyOption(raw) {
3732
5687
  if (raw.length === 0) {
3733
- throw new InvalidArgumentError3("--body must not be empty");
5688
+ throw new InvalidArgumentError5("--body must not be empty");
3734
5689
  }
3735
5690
  return raw;
3736
5691
  }
@@ -3767,9 +5722,9 @@ async function doRunSessionRechain(options, ctx) {
3767
5722
  }
3768
5723
  const cwd = ctx.cwd ?? process.cwd();
3769
5724
  const repositoryRoot = await resolveRepositoryRootForSession(cwd, "rechain");
3770
- const paths = basouPaths11(repositoryRoot);
3771
- await assertWorkspaceInitialized9(paths.root);
3772
- const sessionIds = options.session !== void 0 ? [await resolveSessionId2(paths, options.session)] : await enumerateSessionDirs2(paths);
5725
+ const paths = basouPaths14(repositoryRoot);
5726
+ await assertWorkspaceInitialized10(paths.root);
5727
+ const sessionIds = options.session !== void 0 ? [await resolveSessionId3(paths, options.session)] : await enumerateSessionDirs2(paths);
3773
5728
  const dryRun = options.dryRun === true;
3774
5729
  const rows = [];
3775
5730
  for (const sessionId of sessionIds) {
@@ -3821,11 +5776,11 @@ function renderRechainRow(row, dryRun) {
3821
5776
 
3822
5777
  // src/commands/stats.ts
3823
5778
  import {
3824
- assertBasouRootSafe as assertBasouRootSafe12,
3825
- basouPaths as basouPaths12,
5779
+ assertBasouRootSafe as assertBasouRootSafe13,
5780
+ basouPaths as basouPaths15,
3826
5781
  computeWorkStats,
3827
- findErrorCode as findErrorCode11,
3828
- resolveRepositoryRoot as resolveRepositoryRoot11
5782
+ findErrorCode as findErrorCode12,
5783
+ resolveRepositoryRoot as resolveRepositoryRoot10
3829
5784
  } from "@basou/core";
3830
5785
  function registerStatsCommand(program2) {
3831
5786
  program2.command("stats").description("Report how much the AI worked (output volume + time proxies) across sessions").option("--by-source", "Break the totals down by session source kind").option("--by-day", "Break billable time and volume down by calendar day").option("--json", "Output the full stats as JSON").option("-v, --verbose", "Show error causes").action(async (options) => {
@@ -3843,8 +5798,8 @@ async function runStats(options, ctx = {}) {
3843
5798
  async function doRunStats(options, ctx) {
3844
5799
  const cwd = ctx.cwd ?? process.cwd();
3845
5800
  const repositoryRoot = await resolveRepositoryRootForStats(cwd);
3846
- const paths = basouPaths12(repositoryRoot);
3847
- await assertWorkspaceInitialized10(paths.root);
5801
+ const paths = basouPaths15(repositoryRoot);
5802
+ await assertWorkspaceInitialized11(paths.root);
3848
5803
  const now = ctx.nowProvider?.() ?? /* @__PURE__ */ new Date();
3849
5804
  const result = await computeWorkStats({
3850
5805
  paths,
@@ -3928,7 +5883,7 @@ function formatInt(n) {
3928
5883
  }
3929
5884
  async function resolveRepositoryRootForStats(cwd) {
3930
5885
  try {
3931
- return await resolveRepositoryRoot11(cwd);
5886
+ return await resolveRepositoryRoot10(cwd);
3932
5887
  } catch (error) {
3933
5888
  if (error instanceof Error && error.message === "Not a git repository") {
3934
5889
  throw new Error("Not a git repository. Run 'git init' first, then re-run 'basou stats'.", {
@@ -3938,11 +5893,11 @@ async function resolveRepositoryRootForStats(cwd) {
3938
5893
  throw error;
3939
5894
  }
3940
5895
  }
3941
- async function assertWorkspaceInitialized10(basouRoot) {
5896
+ async function assertWorkspaceInitialized11(basouRoot) {
3942
5897
  try {
3943
- await assertBasouRootSafe12(basouRoot);
5898
+ await assertBasouRootSafe13(basouRoot);
3944
5899
  } catch (error) {
3945
- if (findErrorCode11(error, "ENOENT")) {
5900
+ if (findErrorCode12(error, "ENOENT")) {
3946
5901
  throw new Error("Workspace not initialized. Run 'basou init' first.");
3947
5902
  }
3948
5903
  throw error;
@@ -3951,12 +5906,12 @@ async function assertWorkspaceInitialized10(basouRoot) {
3951
5906
 
3952
5907
  // src/commands/status.ts
3953
5908
  import {
3954
- assertBasouRootSafe as assertBasouRootSafe13,
3955
- basouPaths as basouPaths13,
5909
+ assertBasouRootSafe as assertBasouRootSafe14,
5910
+ basouPaths as basouPaths16,
3956
5911
  buildStatusSnapshot,
3957
- findErrorCode as findErrorCode12,
3958
- readManifest as readManifest6,
3959
- resolveRepositoryRoot as resolveRepositoryRoot12,
5912
+ findErrorCode as findErrorCode13,
5913
+ readManifest as readManifest8,
5914
+ resolveRepositoryRoot as resolveRepositoryRoot11,
3960
5915
  writeStatus
3961
5916
  } from "@basou/core";
3962
5917
  function registerStatusCommand(program2) {
@@ -3975,20 +5930,20 @@ async function runStatus(options, ctx = {}) {
3975
5930
  async function doRunStatus(options, ctx) {
3976
5931
  const cwd = ctx.cwd ?? process.cwd();
3977
5932
  const repositoryRoot = await resolveRepositoryRootForStatus(cwd);
3978
- const paths = basouPaths13(repositoryRoot);
5933
+ const paths = basouPaths16(repositoryRoot);
3979
5934
  try {
3980
- await assertBasouRootSafe13(paths.root);
5935
+ await assertBasouRootSafe14(paths.root);
3981
5936
  } catch (error) {
3982
- if (findErrorCode12(error, "ENOENT")) {
5937
+ if (findErrorCode13(error, "ENOENT")) {
3983
5938
  throw new Error("Workspace not initialized. Run 'basou init' first.");
3984
5939
  }
3985
5940
  throw error;
3986
5941
  }
3987
5942
  let manifest;
3988
5943
  try {
3989
- manifest = await readManifest6(paths);
5944
+ manifest = await readManifest8(paths);
3990
5945
  } catch (error) {
3991
- if (findErrorCode12(error, "ENOENT")) {
5946
+ if (findErrorCode13(error, "ENOENT")) {
3992
5947
  throw new Error("Workspace not initialized. Run 'basou init' first.");
3993
5948
  }
3994
5949
  throw new Error("Failed to read workspace manifest", { cause: error });
@@ -4012,7 +5967,7 @@ function renderTextStatus(s) {
4012
5967
  }
4013
5968
  async function resolveRepositoryRootForStatus(cwd) {
4014
5969
  try {
4015
- return await resolveRepositoryRoot12(cwd);
5970
+ return await resolveRepositoryRoot11(cwd);
4016
5971
  } catch (error) {
4017
5972
  if (error instanceof Error && error.message === "Not a git repository") {
4018
5973
  throw new Error("Not a git repository. Run 'git init' first, then re-run 'basou status'.", {
@@ -4025,34 +5980,34 @@ async function resolveRepositoryRootForStatus(cwd) {
4025
5980
 
4026
5981
  // src/commands/task.ts
4027
5982
  import { readFile as readFile3 } from "fs/promises";
4028
- import { join as join8 } from "path";
5983
+ import { join as join9 } from "path";
4029
5984
  import {
4030
5985
  archiveTask,
4031
- assertBasouRootSafe as assertBasouRootSafe14,
4032
- basouPaths as basouPaths14,
5986
+ assertBasouRootSafe as assertBasouRootSafe15,
5987
+ basouPaths as basouPaths17,
4033
5988
  createTaskWithEvent,
4034
5989
  deleteTask,
4035
5990
  editTask,
4036
5991
  enumerateArchivedTaskIds,
4037
- findErrorCode as findErrorCode13,
5992
+ findErrorCode as findErrorCode14,
4038
5993
  loadSessionEntries as loadSessionEntries2,
4039
5994
  loadTaskEntries,
4040
5995
  prefixedUlid as prefixedUlid5,
4041
- readManifest as readManifest7,
5996
+ readManifest as readManifest9,
4042
5997
  readTaskFile,
4043
5998
  readTaskFileWithArchiveFallback,
4044
5999
  reconcileAllTasks,
4045
6000
  reconcileTask,
4046
6001
  refreshTaskLinkedSessions,
4047
6002
  replayEvents as replayEvents2,
4048
- resolveRepositoryRoot as resolveRepositoryRoot13,
4049
- resolveSessionId as resolveSessionId3,
6003
+ resolveRepositoryRoot as resolveRepositoryRoot12,
6004
+ resolveSessionId as resolveSessionId4,
4050
6005
  resolveTaskId as resolveTaskId2,
4051
6006
  TaskStatusSchema,
4052
6007
  TaskWriteAfterEventError,
4053
6008
  updateTaskStatusWithEvent
4054
6009
  } from "@basou/core";
4055
- import { InvalidArgumentError as InvalidArgumentError4 } from "commander";
6010
+ import { InvalidArgumentError as InvalidArgumentError6 } from "commander";
4056
6011
  var STATUS_VALUES3 = TaskStatusSchema.options;
4057
6012
  function registerTaskCommand(program2) {
4058
6013
  const task = program2.command("task").description("Manage Basou tasks (purpose units that span sessions)");
@@ -4131,14 +6086,14 @@ async function doRunTaskNew(options, ctx) {
4131
6086
  }
4132
6087
  const cwd = ctx.cwd ?? process.cwd();
4133
6088
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "new");
4134
- const paths = basouPaths14(repositoryRoot);
4135
- await assertWorkspaceInitialized11(paths.root);
6089
+ const paths = basouPaths17(repositoryRoot);
6090
+ await assertWorkspaceInitialized12(paths.root);
4136
6091
  const description = options.description !== void 0 ? options.description : options.fromFile !== void 0 ? await readDescriptionFile(options.fromFile) : "";
4137
6092
  const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
4138
6093
  const occurredAt = now.toISOString();
4139
6094
  const taskId = prefixedUlid5("task");
4140
6095
  if (options.session !== void 0) {
4141
- const sessionId = await resolveSessionId3(paths, options.session);
6096
+ const sessionId = await resolveSessionId4(paths, options.session);
4142
6097
  const result2 = await createTaskWithEvent({
4143
6098
  mode: "attach",
4144
6099
  paths,
@@ -4166,7 +6121,7 @@ async function doRunTaskNew(options, ctx) {
4166
6121
  });
4167
6122
  return;
4168
6123
  }
4169
- const manifest = await readManifest7(paths);
6124
+ const manifest = await readManifest9(paths);
4170
6125
  const result = await createTaskWithEvent({
4171
6126
  mode: "ad-hoc",
4172
6127
  paths,
@@ -4240,8 +6195,8 @@ async function runTaskList(options, ctx = {}) {
4240
6195
  async function doRunTaskList(options, ctx) {
4241
6196
  const cwd = ctx.cwd ?? process.cwd();
4242
6197
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "list");
4243
- const paths = basouPaths14(repositoryRoot);
4244
- await assertWorkspaceInitialized11(paths.root);
6198
+ const paths = basouPaths17(repositoryRoot);
6199
+ await assertWorkspaceInitialized12(paths.root);
4245
6200
  const entries = await loadTaskEntries(paths, {
4246
6201
  onSkip: (id, reason) => printTaskSkip(id, reason)
4247
6202
  });
@@ -4344,15 +6299,15 @@ async function runTaskShow(idInput, options, ctx = {}) {
4344
6299
  async function doRunTaskShow(idInput, options, ctx) {
4345
6300
  const cwd = ctx.cwd ?? process.cwd();
4346
6301
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "show");
4347
- const paths = basouPaths14(repositoryRoot);
4348
- await assertWorkspaceInitialized11(paths.root);
6302
+ const paths = basouPaths17(repositoryRoot);
6303
+ await assertWorkspaceInitialized12(paths.root);
4349
6304
  const taskId = await resolveTaskId2(paths, idInput, { includeArchived: true });
4350
6305
  const { doc, archived } = await readTaskFileWithArchiveFallback(paths, taskId);
4351
6306
  const sessions = await loadSessionEntries2(paths, { now: /* @__PURE__ */ new Date() });
4352
6307
  const events = [];
4353
6308
  const linkedSessionIds = new Set(doc.task.task.linked_sessions);
4354
6309
  for (const s of sessions) {
4355
- const sessionDir = join8(paths.sessions, s.sessionId);
6310
+ const sessionDir = join9(paths.sessions, s.sessionId);
4356
6311
  try {
4357
6312
  for await (const ev of replayEvents2(sessionDir, {
4358
6313
  onWarning: (w) => printReplayWarning(w, s.sessionId)
@@ -4488,13 +6443,13 @@ async function doRunTaskStatus(taskIdInput, newStatusInput, options, ctx) {
4488
6443
  const newStatus = parseTaskStatusPositional(newStatusInput);
4489
6444
  const cwd = ctx.cwd ?? process.cwd();
4490
6445
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "status");
4491
- const paths = basouPaths14(repositoryRoot);
4492
- await assertWorkspaceInitialized11(paths.root);
6446
+ const paths = basouPaths17(repositoryRoot);
6447
+ await assertWorkspaceInitialized12(paths.root);
4493
6448
  const taskId = await resolveTaskId2(paths, taskIdInput);
4494
6449
  const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
4495
6450
  const occurredAt = now.toISOString();
4496
6451
  if (options.session !== void 0) {
4497
- const sessionId = await resolveSessionId3(paths, options.session);
6452
+ const sessionId = await resolveSessionId4(paths, options.session);
4498
6453
  const result2 = await updateTaskStatusWithEvent({
4499
6454
  mode: "attach",
4500
6455
  paths,
@@ -4514,7 +6469,7 @@ async function doRunTaskStatus(taskIdInput, newStatusInput, options, ctx) {
4514
6469
  });
4515
6470
  return;
4516
6471
  }
4517
- const manifest = await readManifest7(paths);
6472
+ const manifest = await readManifest9(paths);
4518
6473
  const result = await updateTaskStatusWithEvent({
4519
6474
  mode: "ad-hoc",
4520
6475
  paths,
@@ -4565,9 +6520,9 @@ async function runTaskReconcile(options, ctx = {}) {
4565
6520
  async function doRunTaskReconcile(options, ctx) {
4566
6521
  const cwd = ctx.cwd ?? process.cwd();
4567
6522
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "reconcile");
4568
- const paths = basouPaths14(repositoryRoot);
4569
- await assertWorkspaceInitialized11(paths.root);
4570
- const manifest = await readManifest7(paths);
6523
+ const paths = basouPaths17(repositoryRoot);
6524
+ await assertWorkspaceInitialized12(paths.root);
6525
+ const manifest = await readManifest9(paths);
4571
6526
  const nowProvider = ctx.nowProvider ?? (() => /* @__PURE__ */ new Date());
4572
6527
  const write = options.write === true;
4573
6528
  const verbose = isVerbose(options);
@@ -4745,9 +6700,9 @@ async function doRunTaskRefreshLinkage(taskIdInput, options, ctx) {
4745
6700
  }
4746
6701
  const cwd = ctx.cwd ?? process.cwd();
4747
6702
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "refresh-linkage");
4748
- const paths = basouPaths14(repositoryRoot);
4749
- await assertWorkspaceInitialized11(paths.root);
4750
- const manifest = await readManifest7(paths);
6703
+ const paths = basouPaths17(repositoryRoot);
6704
+ await assertWorkspaceInitialized12(paths.root);
6705
+ const manifest = await readManifest9(paths);
4751
6706
  const taskId = await resolveTaskId2(paths, taskIdInput);
4752
6707
  const nowProvider = ctx.nowProvider ?? (() => /* @__PURE__ */ new Date());
4753
6708
  const write = options.write === true;
@@ -4825,9 +6780,9 @@ async function doRunTaskEdit(taskIdInput, options, ctx) {
4825
6780
  }
4826
6781
  const cwd = ctx.cwd ?? process.cwd();
4827
6782
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "edit");
4828
- const paths = basouPaths14(repositoryRoot);
4829
- await assertWorkspaceInitialized11(paths.root);
4830
- const manifest = await readManifest7(paths);
6783
+ const paths = basouPaths17(repositoryRoot);
6784
+ await assertWorkspaceInitialized12(paths.root);
6785
+ const manifest = await readManifest9(paths);
4831
6786
  const taskId = await resolveTaskId2(paths, taskIdInput);
4832
6787
  const now = ctx.nowProvider !== void 0 ? ctx.nowProvider() : /* @__PURE__ */ new Date();
4833
6788
  const occurredAt = now.toISOString();
@@ -4881,9 +6836,9 @@ async function doRunTaskDelete(taskIdInput, options, ctx) {
4881
6836
  }
4882
6837
  const cwd = ctx.cwd ?? process.cwd();
4883
6838
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "delete");
4884
- const paths = basouPaths14(repositoryRoot);
4885
- await assertWorkspaceInitialized11(paths.root);
4886
- const manifest = await readManifest7(paths);
6839
+ const paths = basouPaths17(repositoryRoot);
6840
+ await assertWorkspaceInitialized12(paths.root);
6841
+ const manifest = await readManifest9(paths);
4887
6842
  const taskId = await resolveTaskId2(paths, taskIdInput);
4888
6843
  if (options.yes !== true) {
4889
6844
  await confirmDestructiveAction("delete", taskId);
@@ -4926,9 +6881,9 @@ async function doRunTaskArchive(taskIdInput, options, ctx) {
4926
6881
  }
4927
6882
  const cwd = ctx.cwd ?? process.cwd();
4928
6883
  const repositoryRoot = await resolveRepositoryRootForTask(cwd, "archive");
4929
- const paths = basouPaths14(repositoryRoot);
4930
- await assertWorkspaceInitialized11(paths.root);
4931
- const manifest = await readManifest7(paths);
6884
+ const paths = basouPaths17(repositoryRoot);
6885
+ await assertWorkspaceInitialized12(paths.root);
6886
+ const manifest = await readManifest9(paths);
4932
6887
  const taskId = await resolveTaskId2(paths, taskIdInput);
4933
6888
  if (options.yes !== true) {
4934
6889
  await confirmDestructiveAction("archive", taskId);
@@ -4981,20 +6936,20 @@ async function readSingleLineFromStdin() {
4981
6936
  }
4982
6937
  function parseTitle2(raw) {
4983
6938
  if (raw.length === 0) {
4984
- throw new InvalidArgumentError4("Title must not be empty");
6939
+ throw new InvalidArgumentError6("Title must not be empty");
4985
6940
  }
4986
6941
  return raw;
4987
6942
  }
4988
6943
  function parseLabel(raw) {
4989
6944
  if (raw.length === 0) {
4990
- throw new InvalidArgumentError4("Label must not be empty");
6945
+ throw new InvalidArgumentError6("Label must not be empty");
4991
6946
  }
4992
6947
  return raw;
4993
6948
  }
4994
6949
  function parseInitialTaskStatus(raw) {
4995
6950
  const result = TaskStatusSchema.safeParse(raw);
4996
6951
  if (!result.success) {
4997
- throw new InvalidArgumentError4(
6952
+ throw new InvalidArgumentError6(
4998
6953
  `Initial task status must be one of: ${STATUS_VALUES3.join(", ")}`
4999
6954
  );
5000
6955
  }
@@ -5003,7 +6958,7 @@ function parseInitialTaskStatus(raw) {
5003
6958
  var ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
5004
6959
  function parseIsoTimestampOption(raw) {
5005
6960
  if (!ISO_DATE_RE.test(raw) || Number.isNaN(Date.parse(raw))) {
5006
- throw new InvalidArgumentError4(
6961
+ throw new InvalidArgumentError6(
5007
6962
  "Invalid --completed-at value; expected ISO-8601 timestamp like 2026-05-10T12:34:56+09:00"
5008
6963
  );
5009
6964
  }
@@ -5012,7 +6967,7 @@ function parseIsoTimestampOption(raw) {
5012
6967
  function parseTaskStatusFilter(raw) {
5013
6968
  const result = TaskStatusSchema.safeParse(raw);
5014
6969
  if (!result.success) {
5015
- throw new InvalidArgumentError4(
6970
+ throw new InvalidArgumentError6(
5016
6971
  `Invalid task status: ${raw}. Valid values: ${STATUS_VALUES3.join(", ")}`
5017
6972
  );
5018
6973
  }
@@ -5027,14 +6982,14 @@ function parseTaskStatusPositional(raw) {
5027
6982
  }
5028
6983
  function parseDescriptionOption(raw) {
5029
6984
  if (raw.length === 0) {
5030
- throw new InvalidArgumentError4("Description must not be empty");
6985
+ throw new InvalidArgumentError6("Description must not be empty");
5031
6986
  }
5032
6987
  return raw;
5033
6988
  }
5034
6989
  function parsePositiveInt2(raw) {
5035
6990
  const n = Number.parseInt(raw, 10);
5036
6991
  if (!Number.isInteger(n) || n < 1 || raw.trim() !== String(n)) {
5037
- throw new InvalidArgumentError4(`Invalid number: ${raw}`);
6992
+ throw new InvalidArgumentError6(`Invalid number: ${raw}`);
5038
6993
  }
5039
6994
  return n;
5040
6995
  }
@@ -5042,10 +6997,10 @@ async function readDescriptionFile(path) {
5042
6997
  try {
5043
6998
  return await readFile3(path, "utf8");
5044
6999
  } catch (error) {
5045
- if (findErrorCode13(error, "ENOENT")) {
7000
+ if (findErrorCode14(error, "ENOENT")) {
5046
7001
  throw new Error("Description source not found", { cause: error });
5047
7002
  }
5048
- if (findErrorCode13(error, "EISDIR")) {
7003
+ if (findErrorCode14(error, "EISDIR")) {
5049
7004
  throw new Error("Description source is not a file", { cause: error });
5050
7005
  }
5051
7006
  throw new Error("Failed to read description source", { cause: error });
@@ -5053,7 +7008,7 @@ async function readDescriptionFile(path) {
5053
7008
  }
5054
7009
  async function resolveRepositoryRootForTask(cwd, subcmd) {
5055
7010
  try {
5056
- return await resolveRepositoryRoot13(cwd);
7011
+ return await resolveRepositoryRoot12(cwd);
5057
7012
  } catch (error) {
5058
7013
  if (error instanceof Error && error.message === "Not a git repository") {
5059
7014
  throw new Error(
@@ -5064,11 +7019,11 @@ async function resolveRepositoryRootForTask(cwd, subcmd) {
5064
7019
  throw error;
5065
7020
  }
5066
7021
  }
5067
- async function assertWorkspaceInitialized11(basouRoot) {
7022
+ async function assertWorkspaceInitialized12(basouRoot) {
5068
7023
  try {
5069
- await assertBasouRootSafe14(basouRoot);
7024
+ await assertBasouRootSafe15(basouRoot);
5070
7025
  } catch (error) {
5071
- if (findErrorCode13(error, "ENOENT")) {
7026
+ if (findErrorCode14(error, "ENOENT")) {
5072
7027
  throw new Error("Workspace not initialized. Run 'basou init' first.");
5073
7028
  }
5074
7029
  throw error;
@@ -5156,12 +7111,12 @@ function maxLen3(values, floor) {
5156
7111
 
5157
7112
  // src/commands/verify.ts
5158
7113
  import {
5159
- assertBasouRootSafe as assertBasouRootSafe15,
5160
- basouPaths as basouPaths15,
7114
+ assertBasouRootSafe as assertBasouRootSafe16,
7115
+ basouPaths as basouPaths18,
5161
7116
  enumerateSessionDirs as enumerateSessionDirs3,
5162
- findErrorCode as findErrorCode14,
5163
- resolveRepositoryRoot as resolveRepositoryRoot14,
5164
- resolveSessionId as resolveSessionId4,
7117
+ findErrorCode as findErrorCode15,
7118
+ resolveRepositoryRoot as resolveRepositoryRoot13,
7119
+ resolveSessionId as resolveSessionId5,
5165
7120
  verifyEventsChain
5166
7121
  } from "@basou/core";
5167
7122
  function registerVerifyCommand(program2) {
@@ -5183,9 +7138,9 @@ async function doRunVerify(options, ctx) {
5183
7138
  }
5184
7139
  const cwd = ctx.cwd ?? process.cwd();
5185
7140
  const repositoryRoot = await resolveRepositoryRootForVerify(cwd);
5186
- const paths = basouPaths15(repositoryRoot);
5187
- await assertWorkspaceInitialized12(paths.root);
5188
- const sessionIds = options.session !== void 0 ? [await resolveSessionId4(paths, options.session)] : await enumerateSessionDirs3(paths);
7141
+ const paths = basouPaths18(repositoryRoot);
7142
+ await assertWorkspaceInitialized13(paths.root);
7143
+ const sessionIds = options.session !== void 0 ? [await resolveSessionId5(paths, options.session)] : await enumerateSessionDirs3(paths);
5189
7144
  const rows = [];
5190
7145
  for (const sessionId of sessionIds) {
5191
7146
  const verdict = await verifyEventsChain(paths, sessionId);
@@ -5231,7 +7186,7 @@ function renderVerdict(row) {
5231
7186
  }
5232
7187
  async function resolveRepositoryRootForVerify(cwd) {
5233
7188
  try {
5234
- return await resolveRepositoryRoot14(cwd);
7189
+ return await resolveRepositoryRoot13(cwd);
5235
7190
  } catch (error) {
5236
7191
  if (error instanceof Error && error.message === "Not a git repository") {
5237
7192
  throw new Error("Not a git repository. Run 'git init' first, then re-run 'basou verify'.", {
@@ -5241,11 +7196,11 @@ async function resolveRepositoryRootForVerify(cwd) {
5241
7196
  throw error;
5242
7197
  }
5243
7198
  }
5244
- async function assertWorkspaceInitialized12(basouRoot) {
7199
+ async function assertWorkspaceInitialized13(basouRoot) {
5245
7200
  try {
5246
- await assertBasouRootSafe15(basouRoot);
7201
+ await assertBasouRootSafe16(basouRoot);
5247
7202
  } catch (error) {
5248
- if (findErrorCode14(error, "ENOENT")) {
7203
+ if (findErrorCode15(error, "ENOENT")) {
5249
7204
  throw new Error("Workspace not initialized. Run 'basou init' first.");
5250
7205
  }
5251
7206
  throw error;
@@ -5255,22 +7210,22 @@ async function assertWorkspaceInitialized12(basouRoot) {
5255
7210
  // src/commands/view.ts
5256
7211
  import { spawn } from "child_process";
5257
7212
  import { createHash } from "crypto";
5258
- import { basename as basename4, resolve as resolve6 } from "path";
7213
+ import { basename as basename5, resolve as resolve7 } from "path";
5259
7214
  import {
5260
- assertBasouRootSafe as assertBasouRootSafe16,
5261
- basouPaths as basouPaths16,
5262
- findErrorCode as findErrorCode16,
5263
- readManifest as readManifest10,
5264
- resolveRepositoryRoot as resolveRepositoryRoot15
7215
+ assertBasouRootSafe as assertBasouRootSafe17,
7216
+ basouPaths as basouPaths19,
7217
+ findErrorCode as findErrorCode17,
7218
+ readManifest as readManifest12,
7219
+ resolveRepositoryRoot as resolveRepositoryRoot14
5265
7220
  } from "@basou/core";
5266
- import { InvalidArgumentError as InvalidArgumentError5 } from "commander";
7221
+ import { InvalidArgumentError as InvalidArgumentError7 } from "commander";
5267
7222
 
5268
7223
  // src/lib/portfolio-safety.ts
5269
7224
  import { execFile } from "child_process";
5270
7225
  import { lstat, realpath } from "fs/promises";
5271
- import { isAbsolute as isAbsolute4, join as join9, relative as relative3, resolve as resolve5 } from "path";
7226
+ import { isAbsolute as isAbsolute5, join as join10, relative as relative4, resolve as resolve6 } from "path";
5272
7227
  import { promisify } from "util";
5273
- import { readManifest as readManifest8 } from "@basou/core";
7228
+ import { readManifest as readManifest10 } from "@basou/core";
5274
7229
  var execFileAsync = promisify(execFile);
5275
7230
  function errorCode(error) {
5276
7231
  return error instanceof Error ? error.code : void 0;
@@ -5279,12 +7234,12 @@ async function canonical(p) {
5279
7234
  try {
5280
7235
  return await realpath(p);
5281
7236
  } catch {
5282
- return resolve5(p);
7237
+ return resolve6(p);
5283
7238
  }
5284
7239
  }
5285
7240
  function isInside(child, parent) {
5286
- const rel = relative3(parent, child);
5287
- return rel === "" || !rel.startsWith("..") && !isAbsolute4(rel);
7241
+ const rel = relative4(parent, child);
7242
+ return rel === "" || !rel.startsWith("..") && !isAbsolute5(rel);
5288
7243
  }
5289
7244
  function isBasouPath(p) {
5290
7245
  return p === ".basou" || p.startsWith(".basou/") || p.includes("/.basou/") || p.endsWith("/.basou");
@@ -5292,7 +7247,7 @@ function isBasouPath(p) {
5292
7247
  async function inspectRepo(repoPath) {
5293
7248
  let hasEntry = false;
5294
7249
  try {
5295
- await lstat(join9(repoPath, ".basou"));
7250
+ await lstat(join10(repoPath, ".basou"));
5296
7251
  hasEntry = true;
5297
7252
  } catch (error) {
5298
7253
  if (errorCode(error) !== "ENOENT") {
@@ -5323,7 +7278,7 @@ async function checkPortfolioSafety(workspaces) {
5323
7278
  const wsReal = await canonical(ws.repoRoot);
5324
7279
  let sourceRoots = [];
5325
7280
  try {
5326
- const manifest = await readManifest8(ws.paths);
7281
+ const manifest = await readManifest10(ws.paths);
5327
7282
  sourceRoots = manifest.import?.source_roots ?? [];
5328
7283
  } catch (error) {
5329
7284
  if (error instanceof Error && error.message === "YAML file not found") {
@@ -5341,7 +7296,7 @@ async function checkPortfolioSafety(workspaces) {
5341
7296
  }
5342
7297
  const monitored = /* @__PURE__ */ new Map();
5343
7298
  for (const root of sourceRoots) {
5344
- const display = resolve5(ws.repoRoot, root);
7299
+ const display = resolve6(ws.repoRoot, root);
5345
7300
  const real = await canonical(display);
5346
7301
  if (real !== wsReal) monitored.set(real, display);
5347
7302
  }
@@ -5393,18 +7348,18 @@ function formatSafetyReport(result) {
5393
7348
 
5394
7349
  // src/lib/view-server.ts
5395
7350
  import { createServer } from "http";
5396
- import { join as join10 } from "path";
7351
+ import { join as join11 } from "path";
5397
7352
  import {
5398
7353
  computeWorkStats as computeWorkStats2,
5399
7354
  enumerateApprovals as enumerateApprovals2,
5400
- findErrorCode as findErrorCode15,
7355
+ findErrorCode as findErrorCode16,
5401
7356
  isLazyExpired as isLazyExpired2,
5402
7357
  loadApproval as loadApproval2,
5403
7358
  loadSessionEntries as loadSessionEntries3,
5404
7359
  loadTaskEntries as loadTaskEntries2,
5405
7360
  readAllEvents as readAllEvents2,
5406
- readManifest as readManifest9,
5407
- readMarkdownFile as readMarkdownFile4,
7361
+ readManifest as readManifest11,
7362
+ readMarkdownFile as readMarkdownFile5,
5408
7363
  readSessionYaml as readSessionYaml3,
5409
7364
  readTaskFile as readTaskFile2,
5410
7365
  renderDecisions as renderDecisions3,
@@ -5591,7 +7546,9 @@ var VIEW_HTML = `<!doctype html>
5591
7546
  if (!data) return 'ok';
5592
7547
  if (data.claudeCode || data.codex) {
5593
7548
  return 'claude-code ' + imp(data.claudeCode) + ', codex ' + imp(data.codex)
5594
- + (data.handoff && data.handoff.status === 'generated' ? '; handoff+decisions regenerated' : '');
7549
+ + (data.handoff && data.handoff.status === 'generated'
7550
+ ? '; handoff regenerated, decisions: ' + (data.decisions ? data.decisions.decisionCount : 0)
7551
+ : '');
5595
7552
  }
5596
7553
  if (data.status === 'ran') return imp(data);
5597
7554
  if (data.status === 'skipped') return 'skipped (' + data.reason + ')';
@@ -6048,7 +8005,7 @@ function startViewServer(opts) {
6048
8005
  };
6049
8006
  let boundPort = port;
6050
8007
  const getPort = () => boundPort;
6051
- return new Promise((resolve7, reject) => {
8008
+ return new Promise((resolve8, reject) => {
6052
8009
  const server = createServer((req, res) => {
6053
8010
  handleRequest(req, res, deps, getPort, runExclusive).catch((error) => {
6054
8011
  sendError(res, error instanceof HttpError ? error.status : 500, pathlessMessage(error));
@@ -6059,7 +8016,7 @@ function startViewServer(opts) {
6059
8016
  const address = server.address();
6060
8017
  boundPort = isAddressInfo(address) ? address.port : port;
6061
8018
  server.off("error", reject);
6062
- resolve7({
8019
+ resolve8({
6063
8020
  url: `http://${host}:${boundPort}`,
6064
8021
  port: boundPort,
6065
8022
  close: () => closeServer(server)
@@ -6071,8 +8028,8 @@ function isAddressInfo(value) {
6071
8028
  return value !== null && typeof value === "object";
6072
8029
  }
6073
8030
  function closeServer(server) {
6074
- return new Promise((resolve7) => {
6075
- server.close(() => resolve7());
8031
+ return new Promise((resolve8) => {
8032
+ server.close(() => resolve8());
6076
8033
  server.closeAllConnections();
6077
8034
  });
6078
8035
  }
@@ -6289,9 +8246,9 @@ async function captureStaleness(ws, nowIso) {
6289
8246
  async function overview(ws, nowProvider) {
6290
8247
  let manifest;
6291
8248
  try {
6292
- manifest = await readManifest9(ws.paths);
8249
+ manifest = await readManifest11(ws.paths);
6293
8250
  } catch (error) {
6294
- if (findErrorCode15(error, "ENOENT")) {
8251
+ if (findErrorCode16(error, "ENOENT")) {
6295
8252
  return { initialized: false, repoRoot: ws.repoRoot };
6296
8253
  }
6297
8254
  throw error;
@@ -6346,7 +8303,7 @@ async function sessionDetail(ws, sessionId) {
6346
8303
  throw error;
6347
8304
  }
6348
8305
  try {
6349
- const events = await readAllEvents2(join10(ws.paths.sessions, sessionId));
8306
+ const events = await readAllEvents2(join11(ws.paths.sessions, sessionId));
6350
8307
  return { session, events };
6351
8308
  } catch {
6352
8309
  return { session, events: [], degraded: true };
@@ -6368,7 +8325,7 @@ async function taskDetail(ws, taskId) {
6368
8325
  }
6369
8326
  }
6370
8327
  async function decisionsView(ws, nowProvider) {
6371
- const fromDisk = await readMarkdownFile4(ws.paths.files.decisions);
8328
+ const fromDisk = await readMarkdownFile5(ws.paths.files.decisions);
6372
8329
  if (fromDisk !== null) {
6373
8330
  return { body: fromDisk, fromDisk: true };
6374
8331
  }
@@ -6391,7 +8348,7 @@ async function approvalsView(ws, nowProvider) {
6391
8348
  return { pending: await toViews(ids.pending), resolved: await toViews(ids.resolved) };
6392
8349
  }
6393
8350
  async function handoffView(ws, nowProvider) {
6394
- const fromDisk = await readMarkdownFile4(ws.paths.files.handoff);
8351
+ const fromDisk = await readMarkdownFile5(ws.paths.files.handoff);
6395
8352
  if (fromDisk !== null) {
6396
8353
  return { body: fromDisk, fromDisk: true };
6397
8354
  }
@@ -6486,7 +8443,7 @@ var DEFAULT_PORT = 4319;
6486
8443
  function parsePort(value) {
6487
8444
  const port = Number.parseInt(value, 10);
6488
8445
  if (!Number.isInteger(port) || port < 1 || port > 65535) {
6489
- throw new InvalidArgumentError5("Port must be an integer between 1 and 65535.");
8446
+ throw new InvalidArgumentError7("Port must be an integer between 1 and 65535.");
6490
8447
  }
6491
8448
  return port;
6492
8449
  }
@@ -6560,18 +8517,18 @@ async function doRunView(options, ctx) {
6560
8517
  }
6561
8518
  async function buildSingleDeps(ctx, cwd) {
6562
8519
  const repositoryRoot = await resolveRepositoryRootForView(cwd);
6563
- const paths = basouPaths16(repositoryRoot);
6564
- await assertWorkspaceInitialized13(paths.root);
8520
+ const paths = basouPaths19(repositoryRoot);
8521
+ await assertWorkspaceInitialized14(paths.root);
6565
8522
  const entry = await buildWorkspaceEntry(repositoryRoot, ctx);
6566
8523
  return { workspaces: [entry], mode: "single", nowProvider: nowProviderOf(ctx) };
6567
8524
  }
6568
8525
  async function buildPortfolioDeps(workspaceFlags, ctx, cwd) {
6569
- const specs = workspaceFlags.length > 0 ? workspaceFlags.map((p) => ({ path: resolve6(cwd, p) })) : await loadPortfolioConfig(ctx.portfolioConfigPath);
8526
+ const specs = workspaceFlags.length > 0 ? workspaceFlags.map((p) => ({ path: resolve7(cwd, p) })) : await loadPortfolioConfig(ctx.portfolioConfigPath);
6570
8527
  const entries = [];
6571
8528
  const seenPath = /* @__PURE__ */ new Set();
6572
8529
  const seenKey = /* @__PURE__ */ new Set();
6573
8530
  for (const spec of specs) {
6574
- const repoRoot = resolve6(spec.path);
8531
+ const repoRoot = resolve7(spec.path);
6575
8532
  if (seenPath.has(repoRoot)) continue;
6576
8533
  seenPath.add(repoRoot);
6577
8534
  const entry = await buildWorkspaceEntry(repoRoot, ctx, spec.label);
@@ -6584,14 +8541,14 @@ async function buildPortfolioDeps(workspaceFlags, ctx, cwd) {
6584
8541
  return { workspaces: entries, mode: "portfolio", nowProvider: nowProviderOf(ctx) };
6585
8542
  }
6586
8543
  async function buildWorkspaceEntry(repoRoot, ctx, labelOverride) {
6587
- const paths = basouPaths16(repoRoot);
8544
+ const paths = basouPaths19(repoRoot);
6588
8545
  const importCtx = {
6589
8546
  cwd: repoRoot,
6590
8547
  ...ctx.claudeProjectsDir !== void 0 ? { claudeProjectsDir: ctx.claudeProjectsDir } : {},
6591
8548
  ...ctx.codexSessionsDir !== void 0 ? { codexSessionsDir: ctx.codexSessionsDir } : {}
6592
8549
  };
6593
8550
  try {
6594
- const manifest = await readManifest10(paths);
8551
+ const manifest = await readManifest12(paths);
6595
8552
  return {
6596
8553
  key: manifest.workspace.id,
6597
8554
  label: labelOverride ?? manifest.workspace.name,
@@ -6604,7 +8561,7 @@ async function buildWorkspaceEntry(repoRoot, ctx, labelOverride) {
6604
8561
  const notFound = error instanceof Error && error.message === "YAML file not found";
6605
8562
  return {
6606
8563
  key: `ws-${createHash("sha1").update(repoRoot).digest("hex").slice(0, 12)}`,
6607
- label: labelOverride ?? basename4(repoRoot),
8564
+ label: labelOverride ?? basename5(repoRoot),
6608
8565
  paths,
6609
8566
  repoRoot,
6610
8567
  importCtx,
@@ -6620,7 +8577,7 @@ async function startListening(port, deps) {
6620
8577
  try {
6621
8578
  return await startViewServer({ port, deps });
6622
8579
  } catch (error) {
6623
- if (findErrorCode16(error, "EADDRINUSE")) {
8580
+ if (findErrorCode17(error, "EADDRINUSE")) {
6624
8581
  throw new Error(`Port ${port} is already in use. Pass --port <n> to choose another.`, {
6625
8582
  cause: error
6626
8583
  });
@@ -6643,7 +8600,7 @@ function openInBrowser(url, override) {
6643
8600
  }
6644
8601
  }
6645
8602
  function waitForShutdown(signal) {
6646
- return new Promise((resolve7) => {
8603
+ return new Promise((resolve8) => {
6647
8604
  const cleanup = () => {
6648
8605
  process.off("SIGINT", onSignal);
6649
8606
  process.off("SIGTERM", onSignal);
@@ -6651,18 +8608,18 @@ function waitForShutdown(signal) {
6651
8608
  };
6652
8609
  const onSignal = () => {
6653
8610
  cleanup();
6654
- resolve7();
8611
+ resolve8();
6655
8612
  };
6656
8613
  const onAbort = () => {
6657
8614
  cleanup();
6658
- resolve7();
8615
+ resolve8();
6659
8616
  };
6660
8617
  process.on("SIGINT", onSignal);
6661
8618
  process.on("SIGTERM", onSignal);
6662
8619
  if (signal !== void 0) {
6663
8620
  if (signal.aborted) {
6664
8621
  cleanup();
6665
- resolve7();
8622
+ resolve8();
6666
8623
  return;
6667
8624
  }
6668
8625
  signal.addEventListener("abort", onAbort);
@@ -6671,7 +8628,7 @@ function waitForShutdown(signal) {
6671
8628
  }
6672
8629
  async function resolveRepositoryRootForView(cwd) {
6673
8630
  try {
6674
- return await resolveRepositoryRoot15(cwd);
8631
+ return await resolveRepositoryRoot14(cwd);
6675
8632
  } catch (error) {
6676
8633
  if (error instanceof Error && error.message === "Not a git repository") {
6677
8634
  throw new Error("Not a git repository. Run 'git init' first, then re-run 'basou view'.", {
@@ -6681,11 +8638,11 @@ async function resolveRepositoryRootForView(cwd) {
6681
8638
  throw error;
6682
8639
  }
6683
8640
  }
6684
- async function assertWorkspaceInitialized13(basouRoot) {
8641
+ async function assertWorkspaceInitialized14(basouRoot) {
6685
8642
  try {
6686
- await assertBasouRootSafe16(basouRoot);
8643
+ await assertBasouRootSafe17(basouRoot);
6687
8644
  } catch (error) {
6688
- if (findErrorCode16(error, "ENOENT")) {
8645
+ if (findErrorCode17(error, "ENOENT")) {
6689
8646
  throw new Error("Workspace not initialized. Run 'basou init' first.");
6690
8647
  }
6691
8648
  throw error;
@@ -6711,11 +8668,14 @@ function buildProgram() {
6711
8668
  registerViewCommand(program2);
6712
8669
  registerApprovalCommand(program2);
6713
8670
  registerDecisionCommand(program2);
8671
+ registerNoteCommand(program2);
6714
8672
  registerTaskCommand(program2);
6715
8673
  registerHandoffCommand(program2);
6716
8674
  registerDecisionsCommand(program2);
6717
8675
  registerReportCommand(program2);
6718
8676
  registerOrientCommand(program2);
8677
+ registerReviewGapsCommand(program2);
8678
+ registerProjectCommand(program2);
6719
8679
  return program2;
6720
8680
  }
6721
8681