@basou/core 0.27.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -21,6 +21,118 @@ function summarizeAdapterOutput(_stream, _raw) {
21
21
  throw new Error("adapter_output summary is not implemented in this release");
22
22
  }
23
23
 
24
+ // src/adapters/claude-code/settings-hook.ts
25
+ var STOP_HOOK_TIMEOUT_SECONDS = 20;
26
+ var BASOU_STOP_HOOK = /(?:\bbasou|(?:@basou|packages)\/cli\/dist\/index\.js['"]?)\s+hook\s+stop\b/;
27
+ function isBasouStopHookCommand(command) {
28
+ return BASOU_STOP_HOOK.test(command);
29
+ }
30
+ function shellQuote(value) {
31
+ return `'${value.replace(/'/g, "'\\''")}'`;
32
+ }
33
+ function buildStopHookCommand(options) {
34
+ const flags = [];
35
+ if (options.block === true) flags.push("--block");
36
+ if (options.minEdits !== void 0) flags.push(`--min-edits ${options.minEdits}`);
37
+ const suffix = flags.length > 0 ? ` ${flags.join(" ")}` : "";
38
+ return `node ${shellQuote(options.cliEntry)} hook stop${suffix} 2>/dev/null || true`;
39
+ }
40
+ function isRecord(value) {
41
+ return typeof value === "object" && value !== null && !Array.isArray(value);
42
+ }
43
+ function cloneSettings(settings) {
44
+ if (settings === void 0 || settings === null) return {};
45
+ if (!isRecord(settings)) {
46
+ throw new Error("Claude settings is not a JSON object.");
47
+ }
48
+ return structuredClone(settings);
49
+ }
50
+ function upsertStopHook(settings, command) {
51
+ const root = cloneSettings(settings);
52
+ if (root.hooks === void 0) {
53
+ root.hooks = {};
54
+ } else if (!isRecord(root.hooks)) {
55
+ throw new Error("The 'hooks' key in Claude settings is not an object.");
56
+ }
57
+ const hooks = root.hooks;
58
+ if (hooks.Stop === void 0) {
59
+ hooks.Stop = [];
60
+ } else if (!Array.isArray(hooks.Stop)) {
61
+ throw new Error("The 'hooks.Stop' key in Claude settings is not an array.");
62
+ }
63
+ const stop = hooks.Stop;
64
+ for (const group of stop) {
65
+ if (!isRecord(group) || !Array.isArray(group.hooks)) continue;
66
+ for (const entry of group.hooks) {
67
+ if (!isRecord(entry)) continue;
68
+ if (typeof entry.command === "string" && isBasouStopHookCommand(entry.command)) {
69
+ const unchanged = entry.type === "command" && entry.command === command && entry.timeout === STOP_HOOK_TIMEOUT_SECONDS;
70
+ entry.type = "command";
71
+ entry.command = command;
72
+ entry.timeout = STOP_HOOK_TIMEOUT_SECONDS;
73
+ return { settings: root, action: unchanged ? "unchanged" : "updated" };
74
+ }
75
+ }
76
+ }
77
+ stop.push({ hooks: [{ type: "command", command, timeout: STOP_HOOK_TIMEOUT_SECONDS }] });
78
+ return { settings: root, action: "installed" };
79
+ }
80
+ function removeStopHook(settings) {
81
+ const root = cloneSettings(settings);
82
+ if (!isRecord(root.hooks) || !Array.isArray(root.hooks.Stop)) {
83
+ return { settings: root, action: "absent" };
84
+ }
85
+ const hooks = root.hooks;
86
+ const stop = hooks.Stop;
87
+ let removed = false;
88
+ const newStop = [];
89
+ for (const group of stop) {
90
+ if (!isRecord(group) || !Array.isArray(group.hooks)) {
91
+ newStop.push(group);
92
+ continue;
93
+ }
94
+ const keptHooks = group.hooks.filter((entry) => {
95
+ if (isRecord(entry) && typeof entry.command === "string" && isBasouStopHookCommand(entry.command)) {
96
+ removed = true;
97
+ return false;
98
+ }
99
+ return true;
100
+ });
101
+ if (keptHooks.length === group.hooks.length) {
102
+ newStop.push(group);
103
+ } else if (keptHooks.length > 0) {
104
+ group.hooks = keptHooks;
105
+ newStop.push(group);
106
+ }
107
+ }
108
+ if (!removed) {
109
+ return { settings: root, action: "absent" };
110
+ }
111
+ if (newStop.length === 0) {
112
+ delete hooks.Stop;
113
+ } else {
114
+ hooks.Stop = newStop;
115
+ }
116
+ if (Object.keys(hooks).length === 0) {
117
+ delete root.hooks;
118
+ }
119
+ return { settings: root, action: "removed" };
120
+ }
121
+ function findBasouStopHookCommand(settings) {
122
+ if (!isRecord(settings) || !isRecord(settings.hooks) || !Array.isArray(settings.hooks.Stop)) {
123
+ return null;
124
+ }
125
+ for (const group of settings.hooks.Stop) {
126
+ if (!isRecord(group) || !Array.isArray(group.hooks)) continue;
127
+ for (const entry of group.hooks) {
128
+ if (isRecord(entry) && typeof entry.command === "string" && isBasouStopHookCommand(entry.command)) {
129
+ return entry.command;
130
+ }
131
+ }
132
+ }
133
+ return null;
134
+ }
135
+
24
136
  // src/adapters/claude-code/ask-user-question.ts
25
137
  function readString(value) {
26
138
  return typeof value === "string" && value.length > 0 ? value : void 0;
@@ -4582,6 +4694,7 @@ var ImportConfigSchema = z9.looseObject({
4582
4694
  var RepoVisibilitySchema = z9.enum(["public", "private", "future-public"]);
4583
4695
  var RepoLanguageSchema = z9.enum(["en", "ja", "en+ja"]);
4584
4696
  var PublishKindSchema = z9.enum(["web", "npm"]);
4697
+ var RepoInstructionsSchema = z9.enum(["hub", "self"]);
4585
4698
  var PublishTargetSchema = z9.looseObject({
4586
4699
  kind: PublishKindSchema,
4587
4700
  visibility: RepoVisibilitySchema.optional(),
@@ -4591,7 +4704,8 @@ var RepoEntrySchema = z9.looseObject({
4591
4704
  path: SourceRootSchema,
4592
4705
  visibility: RepoVisibilitySchema.optional(),
4593
4706
  language: RepoLanguageSchema.optional(),
4594
- publishes: z9.array(PublishTargetSchema).optional()
4707
+ publishes: z9.array(PublishTargetSchema).optional(),
4708
+ instructions: RepoInstructionsSchema.optional()
4595
4709
  });
4596
4710
  var WorkspaceMetaSchema = z9.looseObject({
4597
4711
  id: WorkspaceIdSchema,
@@ -5365,12 +5479,17 @@ function isPublicFacing(v) {
5365
5479
  function planGitignore(input) {
5366
5480
  const plans = [];
5367
5481
  const unknown = [];
5482
+ const self = [];
5368
5483
  const unreachable = [];
5369
5484
  for (const repo of input.repos) {
5370
5485
  if (!repo.reachable) {
5371
5486
  unreachable.push(repo.path);
5372
5487
  continue;
5373
5488
  }
5489
+ if (repo.self === true) {
5490
+ self.push(repo.path);
5491
+ continue;
5492
+ }
5374
5493
  if (repo.visibility === void 0) {
5375
5494
  unknown.push(repo.path);
5376
5495
  continue;
@@ -5388,6 +5507,7 @@ function planGitignore(input) {
5388
5507
  return {
5389
5508
  plans,
5390
5509
  unknown,
5510
+ self,
5391
5511
  unreachable,
5392
5512
  ok: plans.length === 0 && unknown.length === 0 && unreachable.length === 0
5393
5513
  };
@@ -5476,7 +5596,8 @@ function summarizePresetPlan(facts) {
5476
5596
  }
5477
5597
  const byCanonical = /* @__PURE__ */ new Map();
5478
5598
  for (const f of deduped) {
5479
- if (f.isAnchor || !f.reachable || f.canonicalName === void 0 || !isRenderable(f)) continue;
5599
+ if (f.isAnchor || f.self === true || !f.reachable || f.canonicalName === void 0 || !isRenderable(f))
5600
+ continue;
5480
5601
  const repos = byCanonical.get(f.canonicalName) ?? [];
5481
5602
  repos.push(f.path);
5482
5603
  byCanonical.set(f.canonicalName, repos);
@@ -5495,12 +5616,17 @@ function summarizePresetPlan(facts) {
5495
5616
  const markerConflicts = [];
5496
5617
  const unreadable = [];
5497
5618
  const anchors = [];
5619
+ const self = [];
5498
5620
  const unreachable = [];
5499
5621
  for (const f of deduped) {
5500
5622
  if (f.isAnchor) {
5501
5623
  anchors.push(f.path);
5502
5624
  continue;
5503
5625
  }
5626
+ if (f.self === true) {
5627
+ self.push(f.path);
5628
+ continue;
5629
+ }
5504
5630
  if (!f.reachable) {
5505
5631
  unreachable.push(f.path);
5506
5632
  continue;
@@ -5546,6 +5672,7 @@ function summarizePresetPlan(facts) {
5546
5672
  unreadable,
5547
5673
  collisions,
5548
5674
  anchors,
5675
+ self,
5549
5676
  unreachable,
5550
5677
  ok: plans.length === 0 && markerConflicts.length === 0 && unreadable.length === 0 && collisions.length === 0 && unreachable.length === 0 && undeclared.length === 0
5551
5678
  };
@@ -5638,6 +5765,7 @@ function classifyRetrofit(facts) {
5638
5765
  };
5639
5766
  if (!facts.declared) return { ...base, action: "refuse", reason: "not-declared" };
5640
5767
  if (facts.isAnchor) return { ...base, action: "refuse", reason: "anchor" };
5768
+ if (facts.self === true) return { ...base, action: "refuse", reason: "self" };
5641
5769
  if (!facts.reachable) return { ...base, action: "refuse", reason: "unreachable" };
5642
5770
  if (facts.agentsState === "blocked") return { ...base, action: "refuse", reason: "blocked" };
5643
5771
  if (facts.agentsState === "symlink")
@@ -5653,6 +5781,9 @@ function classifyRetrofit(facts) {
5653
5781
  }
5654
5782
 
5655
5783
  // src/project/roster.ts
5784
+ function instructionMode(entry) {
5785
+ return entry.instructions ?? "hub";
5786
+ }
5656
5787
  function summarizeRosterDrift(input) {
5657
5788
  const captured = new Set((input.sourceRoots ?? []).map(normalizeRelativePath));
5658
5789
  const declared = /* @__PURE__ */ new Map();
@@ -5715,7 +5846,7 @@ function summarizeSymlinkPlan(facts) {
5715
5846
  }
5716
5847
  const byCanonical = /* @__PURE__ */ new Map();
5717
5848
  for (const f of deduped) {
5718
- if (f.isAnchor || !f.reachable || !f.canonicalPresent || f.canonicalName === void 0) {
5849
+ if (f.isAnchor || f.self === true || !f.reachable || !f.canonicalPresent || f.canonicalName === void 0) {
5719
5850
  continue;
5720
5851
  }
5721
5852
  const repos = byCanonical.get(f.canonicalName) ?? [];
@@ -5733,6 +5864,7 @@ function summarizeSymlinkPlan(facts) {
5733
5864
  const plans = [];
5734
5865
  const conflicts = [];
5735
5866
  const missingCanonical = [];
5867
+ const selfAgentsMissing = [];
5736
5868
  const unreachable = [];
5737
5869
  for (const f of deduped) {
5738
5870
  if (f.isAnchor) continue;
@@ -5741,7 +5873,8 @@ function summarizeSymlinkPlan(facts) {
5741
5873
  continue;
5742
5874
  }
5743
5875
  if (!f.canonicalPresent) {
5744
- missingCanonical.push(f.path);
5876
+ if (f.self === true) selfAgentsMissing.push(f.path);
5877
+ else missingCanonical.push(f.path);
5745
5878
  continue;
5746
5879
  }
5747
5880
  if (collidingPaths.has(f.path)) continue;
@@ -5768,9 +5901,10 @@ function summarizeSymlinkPlan(facts) {
5768
5901
  plans,
5769
5902
  conflicts,
5770
5903
  missingCanonical,
5904
+ selfAgentsMissing,
5771
5905
  unreachable,
5772
5906
  collisions,
5773
- ok: plans.length === 0 && conflicts.length === 0 && missingCanonical.length === 0 && unreachable.length === 0 && collisions.length === 0
5907
+ ok: plans.length === 0 && conflicts.length === 0 && missingCanonical.length === 0 && selfAgentsMissing.length === 0 && unreachable.length === 0 && collisions.length === 0
5774
5908
  };
5775
5909
  }
5776
5910
 
@@ -5781,6 +5915,7 @@ function isPublicFacing2(v) {
5781
5915
  function summarizeWiring(facts) {
5782
5916
  const risks = [];
5783
5917
  const unknown = [];
5918
+ const self = [];
5784
5919
  const incomplete = [];
5785
5920
  const unreachable = [];
5786
5921
  for (const f of facts) {
@@ -5788,7 +5923,9 @@ function summarizeWiring(facts) {
5788
5923
  unreachable.push(f.path);
5789
5924
  continue;
5790
5925
  }
5791
- if (isPublicFacing2(f.visibility)) {
5926
+ if (f.self === true) {
5927
+ self.push(f.path);
5928
+ } else if (isPublicFacing2(f.visibility)) {
5792
5929
  for (const file of f.instructionFiles) {
5793
5930
  if (file.tracked) risks.push({ repo: f.path, visibility: f.visibility, file: file.name });
5794
5931
  }
@@ -5802,6 +5939,7 @@ function summarizeWiring(facts) {
5802
5939
  repos: facts,
5803
5940
  risks,
5804
5941
  unknown,
5942
+ self,
5805
5943
  incomplete,
5806
5944
  unreachable,
5807
5945
  ok: risks.length === 0 && unknown.length === 0 && unreachable.length === 0
@@ -7638,6 +7776,7 @@ export {
7638
7776
  PROTOCOL_END,
7639
7777
  PROTOCOL_START,
7640
7778
  RiskLevelSchema,
7779
+ STOP_HOOK_TIMEOUT_SECONDS,
7641
7780
  STUCK_THRESHOLD_MS,
7642
7781
  SchemaVersionSchema,
7643
7782
  SessionIdSchema,
@@ -7665,6 +7804,7 @@ export {
7665
7804
  basouPaths,
7666
7805
  buildJsonSchemas,
7667
7806
  buildStatusSnapshot,
7807
+ buildStopHookCommand,
7668
7808
  chainEvents,
7669
7809
  chainRawJsonLines,
7670
7810
  classifyFilesBySourceRoot,
@@ -7686,6 +7826,7 @@ export {
7686
7826
  enumerateTaskIds,
7687
7827
  evaluateStopHook,
7688
7828
  finalizeSessionYaml,
7829
+ findBasouStopHookCommand,
7689
7830
  findErrorCode,
7690
7831
  findReviewGaps,
7691
7832
  formatDurationMs,
@@ -7694,6 +7835,8 @@ export {
7694
7835
  getSnapshot,
7695
7836
  importSessionFromJson,
7696
7837
  inspectChainTail,
7838
+ instructionMode,
7839
+ isBasouStopHookCommand,
7697
7840
  isGitNotFound,
7698
7841
  isImportDerivedSource,
7699
7842
  isLazyExpired,
@@ -7732,6 +7875,7 @@ export {
7732
7875
  refreshTaskLinkedSessions,
7733
7876
  reimportPreservingId,
7734
7877
  removeMarkerSection,
7878
+ removeStopHook,
7735
7879
  renderDecisions,
7736
7880
  renderHandoff,
7737
7881
  renderOrientation,
@@ -7761,6 +7905,7 @@ export {
7761
7905
  ulid,
7762
7906
  unknownManifestKeys,
7763
7907
  updateTaskStatusWithEvent,
7908
+ upsertStopHook,
7764
7909
  verifyEventsChain,
7765
7910
  writeEventsBulk,
7766
7911
  writeManifest,