@basou/cli 0.27.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/program.js CHANGED
@@ -3391,6 +3391,7 @@ import {
3391
3391
  ensureBasouDirectory as ensureBasouDirectory2,
3392
3392
  GENERATED_END,
3393
3393
  GENERATED_START,
3394
+ instructionMode,
3394
3395
  isGitNotFound,
3395
3396
  parseMarkers,
3396
3397
  pathBasename,
@@ -3780,7 +3781,8 @@ async function isTrackedByGit(repoRoot, relPath) {
3780
3781
  async function gatherRepoWiring(repositoryRoot, entry) {
3781
3782
  const base = {
3782
3783
  path: entry.path,
3783
- ...entry.visibility !== void 0 ? { visibility: entry.visibility } : {}
3784
+ ...entry.visibility !== void 0 ? { visibility: entry.visibility } : {},
3785
+ ...instructionMode(entry) === "self" ? { self: true } : {}
3784
3786
  };
3785
3787
  let real;
3786
3788
  try {
@@ -3863,6 +3865,13 @@ function renderProjectWiring(result) {
3863
3865
  for (const p of result.unknown) lines.push(`- ${p}`);
3864
3866
  lines.push("");
3865
3867
  }
3868
+ if (result.self.length > 0) {
3869
+ lines.push(
3870
+ `## instructions: self (${result.self.length}) \u2014 committed instruction files are intentional (no leak risk)`
3871
+ );
3872
+ for (const p of result.self) lines.push(`- ${p}`);
3873
+ lines.push("");
3874
+ }
3866
3875
  if (result.incomplete.length > 0) {
3867
3876
  lines.push(
3868
3877
  `## Missing instruction files (${result.incomplete.length}) \u2014 to be filled by a later generation slice`
@@ -3891,7 +3900,8 @@ async function runProjectGitignore(options, ctx = {}) {
3891
3900
  function gatherRepoGitignore(repositoryRoot, entry) {
3892
3901
  const base = {
3893
3902
  path: entry.path,
3894
- ...entry.visibility !== void 0 ? { visibility: entry.visibility } : {}
3903
+ ...entry.visibility !== void 0 ? { visibility: entry.visibility } : {},
3904
+ ...instructionMode(entry) === "self" ? { self: true } : {}
3895
3905
  };
3896
3906
  let real;
3897
3907
  try {
@@ -3986,6 +3996,13 @@ function renderProjectGitignore(result) {
3986
3996
  for (const p of result.unknown) lines.push(`- ${p}`);
3987
3997
  lines.push("");
3988
3998
  }
3999
+ if (result.self.length > 0) {
4000
+ lines.push(
4001
+ `## instructions: self (${result.self.length}) \u2014 skipped by design; their committed instruction files are shared, never gitignored`
4002
+ );
4003
+ for (const p of result.self) lines.push(`- ${p}`);
4004
+ lines.push("");
4005
+ }
3989
4006
  if (result.unreachable.length > 0) {
3990
4007
  lines.push(`## Unreachable (${result.unreachable.length}) \u2014 path unresolved / not a git repo`);
3991
4008
  for (const p of result.unreachable) lines.push(`- ${p}`);
@@ -4007,12 +4024,13 @@ async function runProjectSymlinks(options, ctx = {}) {
4007
4024
  process.exitCode = 1;
4008
4025
  }
4009
4026
  }
4010
- function expectedSymlinkTargets(repoDirReal, canonicalFile) {
4011
- return [
4012
- { name: "AGENTS.md", target: relative2(repoDirReal, canonicalFile) },
4027
+ function expectedSymlinkTargets(repoDirReal, canonicalFile, mode = "hub") {
4028
+ const spokes = [
4013
4029
  { name: "CLAUDE.md", target: CANONICAL_FILE },
4014
4030
  { name: ".github/copilot-instructions.md", target: `../${CANONICAL_FILE}` }
4015
4031
  ];
4032
+ if (mode === "self") return spokes;
4033
+ return [{ name: "AGENTS.md", target: relative2(repoDirReal, canonicalFile) }, ...spokes];
4016
4034
  }
4017
4035
  function inspectSymlink(filePath, expectedTarget) {
4018
4036
  let isLink;
@@ -4027,7 +4045,9 @@ function inspectSymlink(filePath, expectedTarget) {
4027
4045
  return actual === expectedTarget ? { state: "correct" } : { state: "mismatch", actualTarget: actual };
4028
4046
  }
4029
4047
  function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
4030
- const base = { path: entry.path };
4048
+ const mode = instructionMode(entry);
4049
+ const isSelf = mode === "self";
4050
+ const base = { path: entry.path, ...isSelf ? { self: true } : {} };
4031
4051
  let real;
4032
4052
  try {
4033
4053
  real = realpathSync(resolve7(repositoryRoot, entry.path));
@@ -4040,11 +4060,11 @@ function gatherRepoSymlinks(repositoryRoot, anchorReal, entry) {
4040
4060
  if (!existsSync(join7(real, ".git"))) {
4041
4061
  return { ...base, isAnchor: false, reachable: false, canonicalPresent: false, files: [] };
4042
4062
  }
4043
- const canonicalFile = join7(anchorReal, "agents", basename4(real), CANONICAL_FILE);
4063
+ const canonicalFile = isSelf ? join7(real, CANONICAL_FILE) : join7(anchorReal, "agents", basename4(real), CANONICAL_FILE);
4044
4064
  if (!existsSync(canonicalFile)) {
4045
4065
  return { ...base, isAnchor: false, reachable: true, canonicalPresent: false, files: [] };
4046
4066
  }
4047
- const files = expectedSymlinkTargets(real, canonicalFile).map(
4067
+ const files = expectedSymlinkTargets(real, canonicalFile, mode).map(
4048
4068
  (spec) => {
4049
4069
  const { state, actualTarget } = inspectSymlink(join7(real, spec.name), spec.target);
4050
4070
  return {
@@ -4197,6 +4217,13 @@ function renderProjectSymlinks(result) {
4197
4217
  for (const p of result.missingCanonical) lines.push(`- ${p}`);
4198
4218
  lines.push("");
4199
4219
  }
4220
+ if (result.selfAgentsMissing.length > 0) {
4221
+ lines.push(
4222
+ `## AGENTS.md missing (${result.selfAgentsMissing.length}) \u2014 these \`instructions: self\` repos have no committed AGENTS.md yet; author it, then re-run to wire the spokes`
4223
+ );
4224
+ for (const p of result.selfAgentsMissing) lines.push(`- ${p}`);
4225
+ lines.push("");
4226
+ }
4200
4227
  if (result.unreachable.length > 0) {
4201
4228
  lines.push(`## Unreachable (${result.unreachable.length}) \u2014 path unresolved / not a git repo`);
4202
4229
  for (const p of result.unreachable) lines.push(`- ${p}`);
@@ -4547,6 +4574,9 @@ async function gatherRepoPreset(repositoryRoot, anchorReal, entry) {
4547
4574
  ...entry.language !== void 0 ? { language: entry.language } : {},
4548
4575
  ...entry.publishes !== void 0 ? { publishes: entry.publishes } : {}
4549
4576
  };
4577
+ if (instructionMode(entry) === "self") {
4578
+ return { ...declared, self: true, isAnchor: false, reachable: true, canonicalPresent: false };
4579
+ }
4550
4580
  let real;
4551
4581
  try {
4552
4582
  real = realpathSync(resolve7(repositoryRoot, entry.path));
@@ -4754,6 +4784,13 @@ function renderProjectPreset(result) {
4754
4784
  for (const p of result.anchors) lines.push(`- ${p}`);
4755
4785
  lines.push("");
4756
4786
  }
4787
+ if (result.self.length > 0) {
4788
+ lines.push(
4789
+ `## instructions: self (${result.self.length}) \u2014 hands-off; their hand-authored AGENTS.md is never written by basou`
4790
+ );
4791
+ for (const p of result.self) lines.push(`- ${p}`);
4792
+ lines.push("");
4793
+ }
4757
4794
  if (result.unreachable.length > 0) {
4758
4795
  lines.push(`## Unreachable (${result.unreachable.length}) \u2014 path unresolved / not a git repo`);
4759
4796
  for (const p of result.unreachable) lines.push(`- ${p}`);
@@ -4859,13 +4896,15 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
4859
4896
  const targetAbs = resolve7(repositoryRoot, target);
4860
4897
  const canonicalName = basename4(repoReal ?? targetAbs);
4861
4898
  const roster = manifest.repos ?? [];
4862
- const inRoster = roster.some((r) => {
4899
+ const declaredEntry = roster.find((r) => {
4863
4900
  try {
4864
4901
  return realpathSync(resolve7(repositoryRoot, r.path)) === (repoReal ?? "\0");
4865
4902
  } catch {
4866
4903
  return resolve7(repositoryRoot, r.path) === targetAbs;
4867
4904
  }
4868
4905
  });
4906
+ const inRoster = declaredEntry !== void 0;
4907
+ const isSelf = declaredEntry !== void 0 && instructionMode(declaredEntry) === "self";
4869
4908
  const cnFold = canonicalName.toLowerCase();
4870
4909
  const canonicalShared = roster.some((r) => {
4871
4910
  let rReal = null;
@@ -4887,6 +4926,16 @@ function gatherRepoTeardown(repositoryRoot, manifest, target) {
4887
4926
  if (repoReal !== void 0) {
4888
4927
  for (const spec of teardownExpectedTargets(repoReal, anchorReal, canonicalName)) {
4889
4928
  const { state, actualTarget } = inspectSymlink(join7(repoReal, spec.name), spec.target);
4929
+ if (isSelf) {
4930
+ if (state !== "missing")
4931
+ items.push({
4932
+ kind: "instruction-symlink",
4933
+ label: spec.name,
4934
+ state: "foreign",
4935
+ note: "instructions: self \u2014 committed, left untouched"
4936
+ });
4937
+ continue;
4938
+ }
4890
4939
  if (state === "correct")
4891
4940
  items.push({ kind: "instruction-symlink", label: spec.name, state: "removable" });
4892
4941
  else if (state === "mismatch")
@@ -5735,7 +5784,7 @@ function pathPresent(p) {
5735
5784
  }
5736
5785
  }
5737
5786
  function gatherRetrofit(repositoryRoot, anchorReal, roster, argPath, argAbs, argReal) {
5738
- const declared = roster.some((entry) => {
5787
+ const declaredEntry = roster.find((entry) => {
5739
5788
  const entryAbs = resolve7(repositoryRoot, entry.path);
5740
5789
  if (argReal !== void 0) {
5741
5790
  try {
@@ -5745,6 +5794,8 @@ function gatherRetrofit(repositoryRoot, anchorReal, roster, argPath, argAbs, arg
5745
5794
  }
5746
5795
  return entryAbs === argAbs;
5747
5796
  });
5797
+ const declared = declaredEntry !== void 0;
5798
+ const self = declaredEntry !== void 0 && instructionMode(declaredEntry) === "self";
5748
5799
  const displayRel = argReal !== void 0 ? relative2(anchorReal, argReal) : relative2(repositoryRoot, argAbs);
5749
5800
  const path = displayRel === "" ? "." : displayRel;
5750
5801
  const canonicalName = basename4(argReal ?? argAbs);
@@ -5752,6 +5803,7 @@ function gatherRetrofit(repositoryRoot, anchorReal, roster, argPath, argAbs, arg
5752
5803
  return {
5753
5804
  path,
5754
5805
  declared,
5806
+ ...self ? { self: true } : {},
5755
5807
  isAnchor: false,
5756
5808
  reachable: false,
5757
5809
  canonicalName,
@@ -5766,6 +5818,7 @@ function gatherRetrofit(repositoryRoot, anchorReal, roster, argPath, argAbs, arg
5766
5818
  return {
5767
5819
  path,
5768
5820
  declared,
5821
+ ...self ? { self: true } : {},
5769
5822
  isAnchor,
5770
5823
  reachable,
5771
5824
  canonicalName,
@@ -5868,6 +5921,10 @@ function renderProjectRetrofit(result) {
5868
5921
  lines.push(
5869
5922
  `\u2139\uFE0F \`${result.path}\` is not declared in the roster (manifest \`repos\`). Add it first with \`basou project new\` / \`basou project adopt\`, then re-run.`
5870
5923
  );
5924
+ } else if (result.reason === "self") {
5925
+ lines.push(
5926
+ `\u2139\uFE0F \`${result.path}\` declares \`instructions: self\` \u2014 its \`${CANONICAL_FILE}\` is a hand-authored committed file that stays in the repo, so there is no anchor canonical to relocate it to. Retrofit does not apply (its CLAUDE.md / Copilot spokes are wired by \`basou project symlinks\`).`
5927
+ );
5871
5928
  } else if (result.reason === "anchor") {
5872
5929
  lines.push(
5873
5930
  `\u26A0\uFE0F \`${result.path}\` is the anchor (the project root). It owns its canonical directly \u2014 there is nothing to relocate.`