@nanocollective/roster 0.1.0-alpha.50 → 0.1.0-alpha.51

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/cli.js CHANGED
@@ -654,20 +654,276 @@ function parseFlags(argv) {
654
654
  }
655
655
 
656
656
  // src/commands/brief.ts
657
- import { existsSync as existsSync10, readdirSync as readdirSync3, readFileSync as readFileSync9 } from "fs";
658
- import { join as join10 } from "path";
657
+ import { existsSync as existsSync11, readdirSync as readdirSync3, readFileSync as readFileSync10 } from "fs";
658
+ import { join as join11 } from "path";
659
659
 
660
660
  // src/lib/amend.ts
661
+ import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
662
+ import { join as join7 } from "path";
663
+
664
+ // src/lib/pastebrief.ts
661
665
  import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
662
666
  import { join as join6 } from "path";
663
- function amendBrief(ws, view, tokens, want) {
664
- const template = readFileSync5(join6(briefTemplateDir(), "amend.md"), "utf8");
667
+
668
+ // src/lib/stub.ts
669
+ var MARKERS = [
670
+ "**This file is a stub",
671
+ "This file is a stub, and it is the most important file in this repo",
672
+ "This file is a stub, and everything the staff say is downstream of it",
673
+ "The headings below are the shape that has worked; the words are yours",
674
+ "Write it with your own AI",
675
+ "One paragraph. What this role is for, in this business specifically.",
676
+ "Not a segment. The person, what they were doing ten minutes before they arrived",
677
+ "The single thing this staff member is optimising",
678
+ "Or answer these by hand. Short is better than complete."
679
+ ];
680
+ function looksUnwritten(body) {
681
+ if (!body.trim()) return true;
682
+ return MARKERS.some((marker) => body.includes(marker));
683
+ }
684
+
685
+ // src/lib/paste.ts
686
+ var BEGIN = (path) => `<<<ROSTER FILE ${path}>>>`;
687
+ var END = "<<<ROSTER END>>>";
688
+ var BLOCK = /^[ \t]*<<<ROSTER FILE ([^>\n]+)>>>[ \t]*\n([\s\S]*?)\n?[ \t]*<<<ROSTER END>>>/gm;
689
+ var MIN_WORDS = 40;
690
+ function parsePaste(raw, targets) {
691
+ const files = [];
692
+ const problems = [];
693
+ const text = raw.replace(/\r\n/g, "\n");
694
+ if (!text.trim()) {
695
+ return {
696
+ files,
697
+ problems: [{ id: "empty", message: "Nothing was pasted.", retry: "" }]
698
+ };
699
+ }
700
+ const known = new Map(targets.map((t) => [t.path, t]));
701
+ BLOCK.lastIndex = 0;
702
+ for (const m of text.matchAll(BLOCK)) {
703
+ const written = m[1].trim();
704
+ const body = strip(m[2] ?? "");
705
+ const target = resolveTarget(written, known);
706
+ if (!target) {
707
+ problems.push({
708
+ id: "unknown-path",
709
+ path: written,
710
+ message: `The answer writes ${written}, which this brief did not ask for.`,
711
+ retry: `Write only ${[...known.keys()].join(", ")}, using the sentinels exactly as given.`
712
+ });
713
+ continue;
714
+ }
715
+ files.push({
716
+ path: target.path,
717
+ text: ensureNewline(body),
718
+ unchanged: body.trim() === target.before.trim()
719
+ });
720
+ }
721
+ if (!files.length && !problems.length) {
722
+ problems.push({
723
+ id: "no-envelope",
724
+ message: "Your AI answered in prose. The reply has no <<<ROSTER FILE ...>>> block, so there is nothing here that can be saved as a file.",
725
+ retry: `Send that again as the finished file only, wrapped exactly like this and with nothing outside it:
726
+
727
+ ${BEGIN(targets[0]?.path ?? "<path>")}
728
+ ...the whole file...
729
+ ${END}`
730
+ });
731
+ }
732
+ for (const file of files) {
733
+ const words = file.text.split(/\s+/).filter(Boolean).length;
734
+ if (file.unchanged) {
735
+ problems.push({
736
+ id: "brief-echoed",
737
+ path: file.path,
738
+ message: `${file.path} came back byte for byte. Some models restate a long prompt before working.`,
739
+ retry: "That is the file I sent you. Write the new version."
740
+ });
741
+ continue;
742
+ }
743
+ if (words < MIN_WORDS) {
744
+ problems.push({
745
+ id: "too-short",
746
+ path: file.path,
747
+ message: `${file.path} is ${words} words. That is a failed answer, not a short one.`,
748
+ retry: "That is far too short. Interview me properly first, then write the whole file."
749
+ });
750
+ continue;
751
+ }
752
+ if (looksUnwritten(file.text)) {
753
+ problems.push({
754
+ id: "stub-echoed",
755
+ path: file.path,
756
+ message: `${file.path} still carries the scaffold's own headings, so the questions were never answered.`,
757
+ retry: "You returned the template with its instructions still in it. Answer the questions and write the real file, with none of the scaffold's guidance left in it."
758
+ });
759
+ }
760
+ }
761
+ return { files, problems };
762
+ }
763
+ function resolveTarget(written, known) {
764
+ const exact = known.get(written);
765
+ if (exact) return exact;
766
+ const tail2 = written.replace(/^(\.\/)+/, "").replace(/^\/+/, "");
767
+ if (!tail2 || tail2.split("/").includes("..")) return void 0;
768
+ const matches = [...known.values()].filter((t) => t.path === tail2 || t.path.endsWith(`/${tail2}`));
769
+ return matches.length === 1 ? matches[0] : void 0;
770
+ }
771
+ function strip(body) {
772
+ const trimmed = body.replace(/^\n+/, "").replace(/\s+$/, "");
773
+ const fenced = /^(`{3,}|~{3,})[^\n]*\n([\s\S]*)\n\1\s*$/.exec(trimmed);
774
+ return fenced ? fenced[2] : trimmed;
775
+ }
776
+ function ensureNewline(text) {
777
+ return text.endsWith("\n") ? text : `${text}
778
+ `;
779
+ }
780
+
781
+ // src/lib/pastebrief.ts
782
+ var WRITES = {
783
+ discover: (c) => [`${c.opsName}/org/business.md`],
784
+ voice: (c) => [`${c.opsName}/org/voice.md`],
785
+ priorities: (c) => [`${c.opsName}/org/priorities.md`],
786
+ charter: (c) => [`${c.dir}/CHARTER.md`]
787
+ };
788
+ var READS = {
789
+ discover: (c) => [
790
+ `${c.opsName}/org/operating.md`,
791
+ `${c.opsName}/org/voice.md`,
792
+ `${c.opsName}/org/guardrails.md`
793
+ ],
794
+ voice: (c) => [`${c.opsName}/org/business.md`, `${c.opsName}/org/guardrails.md`],
795
+ priorities: (c) => [`${c.opsName}/org/business.md`],
796
+ /* A charter is the difference between this staff member and everyone else, so the peers'
797
+ charters are the most useful thing in the room: without them the model writes a second
798
+ copy of whoever it was shown. */
799
+ charter: (c) => [
800
+ `${c.opsName}/org/business.md`,
801
+ `${c.opsName}/org/operating.md`,
802
+ `${c.opsName}/org/voice.md`,
803
+ `${c.opsName}/org/guardrails.md`,
804
+ ...c.peers.map((p) => `${p}/CHARTER.md`)
805
+ ]
806
+ };
807
+ function pasteable(kind) {
808
+ return kind in WRITES;
809
+ }
810
+ function pasteBrief(ws, kind, rendered, ctx) {
811
+ if (!pasteable(kind)) throw new Error(`there is no paste-mode brief for "${kind}"`);
812
+ const c = { opsName: ws.opsName, dir: ctx.dir ?? "", peers: ctx.peers ?? [] };
813
+ const writes = WRITES[kind](c);
814
+ const reads = READS[kind](c).filter((rel) => !writes.includes(rel));
815
+ const targets = writes.map((rel) => ({ path: rel, before: read(ws, rel) }));
816
+ const parts = [rendered.trimEnd(), "", "---", "", head(writes)];
817
+ parts.push("", "## The files this refers to", "");
818
+ parts.push(
819
+ "You cannot open anything, so everything the brief mentions is below. Do not ask for files;",
820
+ "if something you want is genuinely not here, say what and why, and work without it.",
821
+ ""
822
+ );
823
+ for (const rel of reads) parts.push(...fileBlock(ws, rel, "read this, do not rewrite it"));
824
+ parts.push("## What you are writing", "");
825
+ for (const rel of targets) {
826
+ parts.push(...fileBlock(ws, rel.path, rel.before.trim() ? "replace this" : "currently empty"));
827
+ }
828
+ parts.push(...tail(writes));
829
+ return { kind, text: `${parts.join("\n").replace(/\n{3,}/g, "\n\n")}
830
+ `, targets };
831
+ }
832
+ function head(writes) {
833
+ return [
834
+ "## How to answer",
835
+ "",
836
+ "**You are talking to a person, not to a filesystem.** You cannot read or write anything.",
837
+ "Everything you need is in this message, and your answer comes back by being pasted into a",
838
+ "tool that turns it into " + (writes.length === 1 ? "a file" : "files") + ".",
839
+ "",
840
+ "**Interview first.** Ask one question at a time and wait. Do not produce the file in your",
841
+ "first reply, and do not present a form. When you have enough, say so and write it."
842
+ ].join("\n");
843
+ }
844
+ function handBackAnyOf(paths) {
845
+ return [
846
+ "---",
847
+ "",
848
+ "## How to hand it back",
849
+ "",
850
+ "Wrap each file you changed like this, and only the files you changed:",
851
+ "",
852
+ "```",
853
+ `${BEGIN(paths[0] ?? "path/to/file.md")}
854
+ ...the whole file, exactly as it should be saved...
855
+ ${END}`,
856
+ "```",
857
+ "",
858
+ "**Rules**, because the answer is parsed rather than read:",
859
+ "",
860
+ "- The sentinel lines go on their own lines, spelled exactly as above, with the file's path.",
861
+ "- Between them goes the **whole file**, exactly as it should be saved. No fences around it,",
862
+ " no commentary inside it.",
863
+ "- Anything you want to say goes **outside** the block.",
864
+ "- The files you may write are these, and no others:",
865
+ ...paths.map((p) => ` - \`${p}\``),
866
+ ""
867
+ ].join("\n");
868
+ }
869
+ function tail(writes) {
870
+ const example = writes.map((rel) => `${BEGIN(rel)}
871
+ ...the whole file, exactly as it should be saved...
872
+ ${END}`).join("\n\n");
873
+ return [
874
+ "---",
875
+ "",
876
+ "## How to hand it back",
877
+ "",
878
+ "When the interview is done and you are writing the final version, wrap it like this:",
879
+ "",
880
+ "```",
881
+ example,
882
+ "```",
883
+ "",
884
+ `**Rules**, because the answer is parsed rather than read:`,
885
+ "",
886
+ "- The sentinel lines go on their own lines, spelled exactly as above.",
887
+ "- Between them goes the **whole file**, exactly as it should be saved. No fences around it,",
888
+ " no commentary inside it, no summary of what you changed.",
889
+ "- Anything you want to say to the person goes **outside** the block. That part is ignored by",
890
+ " the tool and read by them, so it is the right place for what you were unsure about.",
891
+ `- Write only ${writes.join(" and ")}. Nothing else.`,
892
+ "",
893
+ "Nothing is saved automatically. They will see a diff and decide."
894
+ ];
895
+ }
896
+ function fileBlock(ws, rel, note) {
897
+ const body = read(ws, rel);
898
+ if (!body.trim()) return [`### \`${rel}\``, "", `*Empty or absent (${note}).*`, ""];
899
+ return [`### \`${rel}\``, "", `*${note}*`, "", fence(body), ""];
900
+ }
901
+ function read(ws, rel) {
902
+ const full = join6(ws.root, rel);
903
+ return existsSync6(full) ? readFileSync5(full, "utf8") : "";
904
+ }
905
+ function fence(text) {
906
+ const longest = (text.match(/`{3,}/g) ?? []).reduce((n, m) => Math.max(n, m.length), 2);
907
+ const bars = "`".repeat(longest + 1);
908
+ return `${bars}
909
+ ${text.trimEnd()}
910
+ ${bars}`;
911
+ }
912
+ function briefTemplate(kind) {
913
+ const path = join6(briefTemplateDir(), `${kind}.md`);
914
+ if (!existsSync6(path)) throw new Error(`no brief template for "${kind}"`);
915
+ return readFileSync5(path, "utf8");
916
+ }
917
+
918
+ // src/lib/amend.ts
919
+ function amendBrief(ws, view, tokens, want, writable = []) {
920
+ const template = readFileSync6(join7(briefTemplateDir(), "amend.md"), "utf8");
665
921
  const head2 = template.replace(/%%([A-Z_]+)%%/g, (m, name) => {
666
922
  if (name === "WANT") return want.trim() || WANT_PLACEHOLDER;
667
923
  return tokens[name] ?? m;
668
924
  });
669
925
  const parts = [head2, "", "---", "", `## The ${view.kind} prompt as it composes today`, ""];
670
- parts.push(fence(view.composed), "");
926
+ parts.push(fence2(view.composed), "");
671
927
  parts.push("---", "", "## Every file it is assembled from, in order", "");
672
928
  for (const layer of view.layers) {
673
929
  if (layer.missing) {
@@ -675,7 +931,7 @@ function amendBrief(ws, view, tokens, want) {
675
931
  continue;
676
932
  }
677
933
  parts.push(`### ${layer.path}`, "", `Reaches ${reach(layer.rel)}.`, "");
678
- parts.push(fence(read(ws, layer.path)), "");
934
+ parts.push(fence2(read2(ws, layer.path)), "");
679
935
  }
680
936
  parts.push("---", "", "## Named by the prompt, not contained in it", "");
681
937
  parts.push(
@@ -685,23 +941,24 @@ function amendBrief(ws, view, tokens, want) {
685
941
  );
686
942
  for (const layer of view.runtime) {
687
943
  if (layer.missing) continue;
688
- parts.push(`- \`${layer.path}\` \u2014 ${size(read(ws, layer.path))}`);
944
+ parts.push(`- \`${layer.path}\` \u2014 ${size(read2(ws, layer.path))}`);
689
945
  }
946
+ if (writable.length) parts.push("", handBackAnyOf(writable));
690
947
  return parts.join("\n").replace(/\n{3,}/g, "\n\n") + "\n";
691
948
  }
692
949
  var WANT_PLACEHOLDER = "**Replace this line with what you want changed**, then send the whole message.";
693
950
  function reach(rel) {
694
951
  return rel.startsWith("staff:") ? "this staff member only" : "every staff member";
695
952
  }
696
- function read(ws, rel) {
697
- const full = join6(ws.root, rel);
698
- return existsSync6(full) ? readFileSync5(full, "utf8") : "";
953
+ function read2(ws, rel) {
954
+ const full = join7(ws.root, rel);
955
+ return existsSync7(full) ? readFileSync6(full, "utf8") : "";
699
956
  }
700
957
  function size(text) {
701
958
  const words = text.split(/\s+/).filter(Boolean).length;
702
959
  return `${words.toLocaleString()} words`;
703
960
  }
704
- function fence(text) {
961
+ function fence2(text) {
705
962
  const longest = (text.match(/`{3,}/g) ?? []).reduce((n, m) => Math.max(n, m.length), 2);
706
963
  const bars = "`".repeat(longest + 1);
707
964
  return `${bars}
@@ -710,27 +967,27 @@ ${bars}`;
710
967
  }
711
968
 
712
969
  // src/lib/examples.ts
713
- import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
714
- import { join as join8 } from "path";
970
+ import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
971
+ import { join as join9 } from "path";
715
972
 
716
973
  // src/lib/docs.ts
717
- import { existsSync as existsSync7, readdirSync as readdirSync2, readFileSync as readFileSync6 } from "fs";
718
- import { dirname as dirname3, join as join7 } from "path";
974
+ import { existsSync as existsSync8, readdirSync as readdirSync2, readFileSync as readFileSync7 } from "fs";
975
+ import { dirname as dirname3, join as join8 } from "path";
719
976
  import { fileURLToPath as fileURLToPath2 } from "url";
720
977
  function docsDir() {
721
978
  const here = dirname3(fileURLToPath2(import.meta.url));
722
- const candidates = [join7(here, "..", "..", "docs"), join7(here, "..", "docs")];
723
- for (const path of candidates) if (existsSync7(path)) return path;
979
+ const candidates = [join8(here, "..", "..", "docs"), join8(here, "..", "docs")];
980
+ for (const path of candidates) if (existsSync8(path)) return path;
724
981
  throw new Error(`docs not found. Looked in:
725
982
  ${candidates.join("\n ")}`);
726
983
  }
727
984
  function docPages() {
728
985
  const dir = docsDir();
729
986
  const all = readdirSync2(dir).filter((f) => f.endsWith(".md"));
730
- const index = readFileSync6(join7(dir, "README.md"), "utf8");
987
+ const index = readFileSync7(join8(dir, "README.md"), "utf8");
731
988
  const ordered = [...index.matchAll(/\[([^\]]+)\]\(([\w.-]+\.md)\)/g)].map((m) => ({ title: m[1], file: m[2] })).filter((d, i, xs) => all.includes(d.file) && xs.findIndex((x) => x.file === d.file) === i);
732
989
  const listed = new Set(ordered.map((d) => d.file));
733
- const rest = all.filter((f) => f !== "README.md" && !listed.has(f)).map((file) => ({ file, title: titleOf(join7(dir, file)) }));
990
+ const rest = all.filter((f) => f !== "README.md" && !listed.has(f)).map((file) => ({ file, title: titleOf(join8(dir, file)) }));
734
991
  return [{ file: "README.md", title: "Overview" }, ...ordered, ...rest];
735
992
  }
736
993
  var ASSET_TYPES = {
@@ -742,13 +999,13 @@ var ASSET_TYPES = {
742
999
  ".webp": "image/webp"
743
1000
  };
744
1001
  function docAsset(name) {
745
- const dir = join7(docsDir(), "images");
746
- if (!existsSync7(dir)) return null;
1002
+ const dir = join8(docsDir(), "images");
1003
+ if (!existsSync8(dir)) return null;
747
1004
  const wanted = String(name ?? "").replace(/^images\//, "");
748
1005
  const found = readdirSync2(dir).find((f) => f === wanted);
749
1006
  if (!found) return null;
750
1007
  const type = ASSET_TYPES[found.slice(found.lastIndexOf(".")).toLowerCase()];
751
- return type ? { path: join7(dir, found), type } : null;
1008
+ return type ? { path: join8(dir, found), type } : null;
752
1009
  }
753
1010
  function searchDocs(query2, snippets = 3) {
754
1011
  const terms = String(query2 ?? "").toLowerCase().split(/\s+/).map((t) => t.trim()).filter(Boolean);
@@ -756,7 +1013,7 @@ function searchDocs(query2, snippets = 3) {
756
1013
  const dir = docsDir();
757
1014
  const out = [];
758
1015
  for (const page2 of docPages()) {
759
- const text = readFileSync6(join7(dir, page2.file), "utf8").replace(/^---\n[\s\S]*?\n---\n/, "");
1016
+ const text = readFileSync7(join8(dir, page2.file), "utf8").replace(/^---\n[\s\S]*?\n---\n/, "");
760
1017
  const haystack = (page2.title + "\n" + text).toLowerCase();
761
1018
  if (!terms.every((t) => haystack.includes(t))) continue;
762
1019
  const title = page2.title.toLowerCase();
@@ -792,7 +1049,7 @@ function snippet(line, terms, width = 150) {
792
1049
  return (from ? "\u2026" : "") + clean.slice(from, from + width).trim() + "\u2026";
793
1050
  }
794
1051
  function titleOf(path) {
795
- const first = readFileSync6(path, "utf8").split("\n").find((l) => l.startsWith("# "));
1052
+ const first = readFileSync7(path, "utf8").split("\n").find((l) => l.startsWith("# "));
796
1053
  return first ? first.slice(2).trim() : path.split("/").pop();
797
1054
  }
798
1055
 
@@ -839,8 +1096,8 @@ function matchExample(handle, name = "") {
839
1096
  return PATTERNS.find(([, re]) => re.test(words))?.[0];
840
1097
  }
841
1098
  function exampleSection(choice) {
842
- const path = join8(docsDir(), "charters", `${choice}.md`);
843
- if (!existsSync8(path)) throw new Error(`there is no example charter called "${choice}"`);
1099
+ const path = join9(docsDir(), "charters", `${choice}.md`);
1100
+ if (!existsSync9(path)) throw new Error(`there is no example charter called "${choice}"`);
844
1101
  return [
845
1102
  "## A worked example, to adapt",
846
1103
  "",
@@ -850,14 +1107,14 @@ function exampleSection(choice) {
850
1107
  "charter built from someone else's specifics is the generic agent this file exists to",
851
1108
  "prevent. The interview still comes first, and everything in the charter comes from it.",
852
1109
  "",
853
- fence2(readFileSync7(path, "utf8")),
1110
+ fence3(readFileSync8(path, "utf8")),
854
1111
  ""
855
1112
  ].join("\n");
856
1113
  }
857
1114
  function charterStarter(choice, business) {
858
- const path = join8(docsDir(), "charters", `${choice}.md`);
859
- if (!existsSync8(path)) throw new Error(`there is no example charter called "${choice}"`);
860
- return readFileSync7(path, "utf8").replace(/^> \*\*An example to adapt[\s\S]*?\n\n/m, "").replace(/\bAcme\b/g, business);
1115
+ const path = join9(docsDir(), "charters", `${choice}.md`);
1116
+ if (!existsSync9(path)) throw new Error(`there is no example charter called "${choice}"`);
1117
+ return readFileSync8(path, "utf8").replace(/^> \*\*An example to adapt[\s\S]*?\n\n/m, "").replace(/\bAcme\b/g, business);
861
1118
  }
862
1119
  function exampleOffer(handle) {
863
1120
  return [
@@ -868,7 +1125,7 @@ function exampleOffer(handle) {
868
1125
  ""
869
1126
  ].join("\n");
870
1127
  }
871
- function fence2(text) {
1128
+ function fence3(text) {
872
1129
  const longest = (text.match(/`{3,}/g) ?? []).reduce((n, m) => Math.max(n, m.length), 2);
873
1130
  const bars = "`".repeat(longest + 1);
874
1131
  return `${bars}markdown
@@ -904,8 +1161,8 @@ function defaultMarker(name) {
904
1161
 
905
1162
  // src/lib/prompt.ts
906
1163
  import { execFileSync } from "child_process";
907
- import { existsSync as existsSync9, readFileSync as readFileSync8, statSync as statSync2, writeFileSync } from "fs";
908
- import { dirname as dirname4, join as join9, relative as relative2, resolve as resolve2 } from "path";
1164
+ import { existsSync as existsSync10, readFileSync as readFileSync9, statSync as statSync2, writeFileSync } from "fs";
1165
+ import { dirname as dirname4, join as join10, relative as relative2, resolve as resolve2 } from "path";
909
1166
  var KINDS = ["daily", "mention"];
910
1167
  function promptView(ws, compose, staffHandle, brainDir, kind) {
911
1168
  const had = process.env.ROSTER_CONTEXT;
@@ -925,7 +1182,7 @@ function promptView(ws, compose, staffHandle, brainDir, kind) {
925
1182
  staff: staffHandle,
926
1183
  kind
927
1184
  });
928
- const entry = join9(ws.opsDir, "prompts", `${kind}.md`);
1185
+ const entry = join10(ws.opsDir, "prompts", `${kind}.md`);
929
1186
  const layers = [describe(ws, brainDir, `prompts/${kind}.md`, false)];
930
1187
  walk(ws, brainDir, entry, layers, /* @__PURE__ */ new Set([resolve2(entry)]));
931
1188
  return {
@@ -941,8 +1198,8 @@ function promptView(ws, compose, staffHandle, brainDir, kind) {
941
1198
  }
942
1199
  var INCLUDE = /\{\{>(\??)\s*([^}\s]+)\s*\}\}/g;
943
1200
  function walk(ws, brainDir, file, out, seen) {
944
- if (!existsSync9(file)) return;
945
- const text = readFileSync8(file, "utf8");
1201
+ if (!existsSync10(file)) return;
1202
+ const text = readFileSync9(file, "utf8");
946
1203
  for (const m of text.matchAll(INCLUDE)) {
947
1204
  const rel = m[2];
948
1205
  const layer = describe(ws, brainDir, rel, m[1] === "?");
@@ -956,8 +1213,8 @@ function walk(ws, brainDir, file, out, seen) {
956
1213
  function describe(ws, brainDir, rel, optional) {
957
1214
  const inBrain = rel.startsWith("staff:");
958
1215
  const base2 = inBrain ? brainDir : ws.opsDir;
959
- const full = join9(base2, inBrain ? rel.slice(6) : rel);
960
- const missing = !existsSync9(full);
1216
+ const full = join10(base2, inBrain ? rel.slice(6) : rel);
1217
+ const missing = !existsSync10(full);
961
1218
  return {
962
1219
  rel,
963
1220
  path: relative2(ws.root, full),
@@ -975,8 +1232,8 @@ function runtimeReads(ws, brainDir) {
975
1232
  [ws.opsDir, "org/business.md"]
976
1233
  ];
977
1234
  return named.map(([base2, rel]) => {
978
- const full = join9(base2, rel);
979
- const missing = !existsSync9(full);
1235
+ const full = join10(base2, rel);
1236
+ const missing = !existsSync10(full);
980
1237
  return {
981
1238
  rel,
982
1239
  path: relative2(ws.root, full),
@@ -1008,7 +1265,7 @@ function saveFile(ws, rel, text, message) {
1008
1265
  const full = resolve2(ws.root, rel);
1009
1266
  const repoDir = repoRootOf(full);
1010
1267
  const repo = relative2(ws.root, repoDir) || ".";
1011
- const before = existsSync9(full) ? readFileSync8(full, "utf8") : null;
1268
+ const before = existsSync10(full) ? readFileSync9(full, "utf8") : null;
1012
1269
  if (before === text)
1013
1270
  return { path: rel, repo, committed: false, pushed: false, note: "no change" };
1014
1271
  writeFileSync(full, text);
@@ -1143,13 +1400,13 @@ async function briefCommand(argv) {
1143
1400
  ws,
1144
1401
  compose,
1145
1402
  handle,
1146
- join10(ws.root, entry.dir ?? entry.handle),
1403
+ join11(ws.root, entry.dir ?? entry.handle),
1147
1404
  opts.kind ?? "daily"
1148
1405
  );
1149
1406
  process.stdout.write(amendBrief(ws, view, tokens, opts.want ?? ""));
1150
1407
  return 0;
1151
1408
  }
1152
- const text = readFileSync9(join10(briefTemplateDir(), `${kind}.md`), "utf8");
1409
+ const text = readFileSync10(join11(briefTemplateDir(), `${kind}.md`), "utf8");
1153
1410
  let out = renderBrief(text, tokens, `briefs/${kind}.md`);
1154
1411
  if (kind === "charter") out = withExample(out, handle, tokens.NAME ?? "", opts.example);
1155
1412
  process.stdout.write(out);
@@ -1162,9 +1419,9 @@ function staffTokens(ws, org, handle, parseYaml) {
1162
1419
  throw new Error(`unknown staff handle "${handle}". org.yaml knows: ${known}`);
1163
1420
  }
1164
1421
  const dir = entry.dir ?? entry.handle;
1165
- const manifestPath = join10(ws.root, dir, "staff.yaml");
1166
- if (!existsSync10(manifestPath)) throw new Error(`no staff.yaml at ${manifestPath}`);
1167
- const manifest = parseYaml(readFileSync9(manifestPath, "utf8"), `${dir}/staff.yaml`);
1422
+ const manifestPath = join11(ws.root, dir, "staff.yaml");
1423
+ if (!existsSync11(manifestPath)) throw new Error(`no staff.yaml at ${manifestPath}`);
1424
+ const manifest = parseYaml(readFileSync10(manifestPath, "utf8"), `${dir}/staff.yaml`);
1168
1425
  return tokensFor(spec(org, ws), specFromManifest(manifest, dir));
1169
1426
  }
1170
1427
  function spec(org, ws) {
@@ -1295,27 +1552,8 @@ function repoName(full) {
1295
1552
 
1296
1553
  // src/lib/setup.ts
1297
1554
  import { execFile as execFile3 } from "child_process";
1298
- import { existsSync as existsSync11, mkdirSync, readFileSync as readFileSync10 } from "fs";
1299
- import { join as join11 } from "path";
1300
-
1301
- // src/lib/stub.ts
1302
- var MARKERS = [
1303
- "**This file is a stub",
1304
- "This file is a stub, and it is the most important file in this repo",
1305
- "This file is a stub, and everything the staff say is downstream of it",
1306
- "The headings below are the shape that has worked; the words are yours",
1307
- "Write it with your own AI",
1308
- "One paragraph. What this role is for, in this business specifically.",
1309
- "Not a segment. The person, what they were doing ten minutes before they arrived",
1310
- "The single thing this staff member is optimising",
1311
- "Or answer these by hand. Short is better than complete."
1312
- ];
1313
- function looksUnwritten(body) {
1314
- if (!body.trim()) return true;
1315
- return MARKERS.some((marker) => body.includes(marker));
1316
- }
1317
-
1318
- // src/lib/setup.ts
1555
+ import { existsSync as existsSync12, mkdirSync, readFileSync as readFileSync11 } from "fs";
1556
+ import { join as join12 } from "path";
1319
1557
  var AGENTS = [
1320
1558
  {
1321
1559
  id: "claude-code-action",
@@ -1381,17 +1619,17 @@ async function setupStatus(startedIn) {
1381
1619
  };
1382
1620
  }
1383
1621
  function readOrgName(opsDir) {
1384
- const path = join11(opsDir, "org.yaml");
1385
- if (!existsSync11(path)) return void 0;
1386
- const m = /^org:\s*(\S+)\s*$/m.exec(readFileSync10(path, "utf8"));
1622
+ const path = join12(opsDir, "org.yaml");
1623
+ if (!existsSync12(path)) return void 0;
1624
+ const m = /^org:\s*(\S+)\s*$/m.exec(readFileSync11(path, "utf8"));
1387
1625
  return m?.[1];
1388
1626
  }
1389
1627
  function readProducts(opsDir) {
1390
- const path = join11(opsDir, "org.yaml");
1391
- if (!existsSync11(path)) return [];
1628
+ const path = join12(opsDir, "org.yaml");
1629
+ if (!existsSync12(path)) return [];
1392
1630
  const out = [];
1393
1631
  let inside = false;
1394
- for (const line of readFileSync10(path, "utf8").split("\n")) {
1632
+ for (const line of readFileSync11(path, "utf8").split("\n")) {
1395
1633
  if (/^repos:\s*$/.test(line)) {
1396
1634
  inside = true;
1397
1635
  continue;
@@ -1404,12 +1642,12 @@ function readProducts(opsDir) {
1404
1642
  return out;
1405
1643
  }
1406
1644
  function readBlockNames(opsDir, block, key) {
1407
- const path = join11(opsDir, "org.yaml");
1408
- if (!existsSync11(path)) return [];
1645
+ const path = join12(opsDir, "org.yaml");
1646
+ if (!existsSync12(path)) return [];
1409
1647
  const names = [];
1410
1648
  const pattern = new RegExp(`\\b${key}:\\s*([\\w.-]+)`);
1411
1649
  let inside = false;
1412
- for (const line of readFileSync10(path, "utf8").split("\n")) {
1650
+ for (const line of readFileSync11(path, "utf8").split("\n")) {
1413
1651
  if (new RegExp(`^${block}:\\s*$`).test(line)) {
1414
1652
  inside = true;
1415
1653
  continue;
@@ -1428,35 +1666,35 @@ function unfinished(opsDir, staffCount) {
1428
1666
  ["business", "org/business.md"],
1429
1667
  ["priorities", "org/priorities.md"]
1430
1668
  ]) {
1431
- const path = join11(opsDir, rel);
1432
- if (!existsSync11(path) || looksUnwritten(readFileSync10(path, "utf8"))) out.push(id);
1669
+ const path = join12(opsDir, rel);
1670
+ if (!existsSync12(path) || looksUnwritten(readFileSync11(path, "utf8"))) out.push(id);
1433
1671
  }
1434
1672
  return out;
1435
1673
  }
1436
1674
  async function loadFrameworkComposer() {
1437
- const path = join11(templatesRoot(), "ops", "compose.mjs");
1675
+ const path = join12(templatesRoot(), "ops", "compose.mjs");
1438
1676
  return await import(`file://${path}`);
1439
1677
  }
1440
1678
  async function joinTenant(root, org) {
1441
1679
  mkdirSync(root, { recursive: true });
1442
1680
  const cloned = [];
1443
- const opsDir = join11(root, "roster-ops");
1444
- if (!existsSync11(opsDir)) {
1681
+ const opsDir = join12(root, "roster-ops");
1682
+ if (!existsSync12(opsDir)) {
1445
1683
  await clone(`https://github.com/${org}/roster-ops.git`, opsDir);
1446
1684
  cloned.push("roster-ops");
1447
1685
  }
1448
- if (!existsSync11(join11(opsDir, "org.yaml"))) {
1686
+ if (!existsSync12(join12(opsDir, "org.yaml"))) {
1449
1687
  throw new Error(`${org}/roster-ops has no org.yaml, so it is not a roster tenant`);
1450
1688
  }
1451
- const { parseYaml } = await import(`file://${join11(opsDir, "compose.mjs")}`).then(
1689
+ const { parseYaml } = await import(`file://${join12(opsDir, "compose.mjs")}`).then(
1452
1690
  (m) => m
1453
1691
  );
1454
- const orgFile = parseYaml(readFileSync10(join11(opsDir, "org.yaml"), "utf8"), "org.yaml");
1692
+ const orgFile = parseYaml(readFileSync11(join12(opsDir, "org.yaml"), "utf8"), "org.yaml");
1455
1693
  for (const person of orgFile.staff ?? []) {
1456
1694
  const dir = person.dir ?? person.handle;
1457
- if (existsSync11(join11(root, dir))) continue;
1695
+ if (existsSync12(join12(root, dir))) continue;
1458
1696
  try {
1459
- await clone(`https://github.com/${org}/${dir}.git`, join11(root, dir));
1697
+ await clone(`https://github.com/${org}/${dir}.git`, join12(root, dir));
1460
1698
  cloned.push(dir);
1461
1699
  } catch {
1462
1700
  }
@@ -1471,8 +1709,8 @@ function clone(url, into) {
1471
1709
 
1472
1710
  // src/commands/hire.ts
1473
1711
  import { execFileSync as execFileSync3 } from "child_process";
1474
- import { existsSync as existsSync12, mkdirSync as mkdirSync2, readFileSync as readFileSync11, writeFileSync as writeFileSync3 } from "fs";
1475
- import { dirname as dirname5, join as join13 } from "path";
1712
+ import { existsSync as existsSync13, mkdirSync as mkdirSync2, readFileSync as readFileSync12, writeFileSync as writeFileSync3 } from "fs";
1713
+ import { dirname as dirname5, join as join14 } from "path";
1476
1714
 
1477
1715
  // src/lib/asks.ts
1478
1716
  var ASK_KINDS = ["decision", "review", "chore"];
@@ -1515,7 +1753,7 @@ function short2(e) {
1515
1753
  // src/lib/reviewgate.ts
1516
1754
  import { mkdtempSync, rmSync, writeFileSync as writeFileSync2 } from "fs";
1517
1755
  import { tmpdir } from "os";
1518
- import { join as join12 } from "path";
1756
+ import { join as join13 } from "path";
1519
1757
  var PLAN_LIMIT = /upgrade to github (pro|team)|make this repository public/i;
1520
1758
  function isPlanLimit(error) {
1521
1759
  return PLAN_LIMIT.test(error ?? "");
@@ -1635,8 +1873,8 @@ var GATE_RULESET = {
1635
1873
  bypass_actors: [{ actor_id: 5, actor_type: "RepositoryRole", bypass_mode: "pull_request" }]
1636
1874
  };
1637
1875
  async function addGate(repo) {
1638
- const dir = mkdtempSync(join12(tmpdir(), "roster-gate-"));
1639
- const body = join12(dir, "ruleset.json");
1876
+ const dir = mkdtempSync(join13(tmpdir(), "roster-gate-"));
1877
+ const body = join13(dir, "ruleset.json");
1640
1878
  try {
1641
1879
  writeFileSync2(body, JSON.stringify(GATE_RULESET));
1642
1880
  const res = await api(`repos/${repo}/rulesets`, ["-X", "POST", "--input", body]);
@@ -1719,7 +1957,7 @@ function buildPlan(ws, org, handle, opts, parseYaml) {
1719
1957
  const dir = opts.dir ?? handle;
1720
1958
  const siblings = (org.staff ?? []).map((s) => ({
1721
1959
  entry: s,
1722
- manifest: readManifest(join13(ws.root, s.dir ?? s.handle), parseYaml)
1960
+ manifest: readManifest(join14(ws.root, s.dir ?? s.handle), parseYaml)
1723
1961
  })).filter((s) => s.manifest);
1724
1962
  const model = opts.model ?? org.defaults?.model ?? "claude-opus-5-5";
1725
1963
  const schedule = opts.schedule ?? nextSlot(
@@ -1880,10 +2118,10 @@ function inferAppSlug(sampleApp, sampleHandle, handle) {
1880
2118
  return sampleApp.slice(0, sampleApp.length - sampleHandle.length) + handle;
1881
2119
  }
1882
2120
  function readManifest(root, parseYaml) {
1883
- const path = join13(root, "staff.yaml");
1884
- if (!existsSync12(path)) return null;
2121
+ const path = join14(root, "staff.yaml");
2122
+ if (!existsSync13(path)) return null;
1885
2123
  try {
1886
- return parseYaml(readFileSync11(path, "utf8"), "staff.yaml");
2124
+ return parseYaml(readFileSync12(path, "utf8"), "staff.yaml");
1887
2125
  } catch {
1888
2126
  return null;
1889
2127
  }
@@ -1896,7 +2134,7 @@ function printPlan(plan2, ws) {
1896
2134
  `);
1897
2135
  process.stdout.write(` repo ${staff.brain}
1898
2136
  `);
1899
- process.stdout.write(` directory ${join13(ws.root, plan2.dir)}
2137
+ process.stdout.write(` directory ${join14(ws.root, plan2.dir)}
1900
2138
  `);
1901
2139
  process.stdout.write(` schedule ${staff.schedule} (${staff.timeout}m ceiling)
1902
2140
  `);
@@ -1998,14 +2236,14 @@ async function applyPlan(ws, plan2, opts) {
1998
2236
  `);
1999
2237
  return 1;
2000
2238
  }
2001
- const root = join13(ws.root, plan2.dir);
2002
- if (existsSync12(root)) {
2239
+ const root = join14(ws.root, plan2.dir);
2240
+ if (existsSync13(root)) {
2003
2241
  process.stderr.write(`roster: ${root} already exists. Move it aside first.
2004
2242
  `);
2005
2243
  return 1;
2006
2244
  }
2007
2245
  for (const [rel, text] of plan2.files) {
2008
- const dest = join13(root, rel);
2246
+ const dest = join14(root, rel);
2009
2247
  mkdirSync2(dirname5(dest), { recursive: true });
2010
2248
  writeFileSync3(dest, text);
2011
2249
  }
@@ -2090,9 +2328,9 @@ async function applyPlan(ws, plan2, opts) {
2090
2328
  ]).catch(() => void 0);
2091
2329
  const withIssue = { ...plan2.staff, statusIssue: issue.data.number };
2092
2330
  writeFileSync3(
2093
- join13(root, "staff.yaml"),
2331
+ join14(root, "staff.yaml"),
2094
2332
  render(
2095
- readFileSync11(join13(brainTemplateDir(), "staff.yaml"), "utf8"),
2333
+ readFileSync12(join14(brainTemplateDir(), "staff.yaml"), "utf8"),
2096
2334
  tokensFor(plan2.org, withIssue),
2097
2335
  "staff.yaml"
2098
2336
  )
@@ -2107,7 +2345,7 @@ async function applyPlan(ws, plan2, opts) {
2107
2345
  process.stdout.write(` added ${staff.handle} to org.yaml
2108
2346
  `);
2109
2347
  for (const c of plan2.commits) {
2110
- const done = commitAndPush(join13(ws.root, c.dir), [c.file], c.message);
2348
+ const done = commitAndPush(join14(ws.root, c.dir), [c.file], c.message);
2111
2349
  process.stdout.write(
2112
2350
  done.pushed ? ` pushed ${c.repo}: ${c.file} (${done.sha})
2113
2351
  ` : done.committed ? ` committed ${c.repo}: ${c.file} but could not push: ${done.note}. Push it yourself.
@@ -2143,8 +2381,8 @@ async function ensureGate(repo, apps) {
2143
2381
  }
2144
2382
  function wirePeers(ws, plan2, root) {
2145
2383
  const { staff } = plan2;
2146
- const manifest = join13(root, "staff.yaml");
2147
- let mineText = readFileSync11(manifest, "utf8");
2384
+ const manifest = join14(root, "staff.yaml");
2385
+ let mineText = readFileSync12(manifest, "utf8");
2148
2386
  for (const p of plan2.peers) {
2149
2387
  mineText = insertUnder(
2150
2388
  mineText,
@@ -2154,17 +2392,17 @@ function wirePeers(ws, plan2, root) {
2154
2392
  }
2155
2393
  writeFileSync3(manifest, mineText);
2156
2394
  for (const p of plan2.peers) {
2157
- const path = join13(ws.root, p.dir, "staff.yaml");
2158
- if (!existsSync12(path)) continue;
2159
- const text = readFileSync11(path, "utf8");
2395
+ const path = join14(ws.root, p.dir, "staff.yaml");
2396
+ if (!existsSync13(path)) continue;
2397
+ const text = readFileSync12(path, "utf8");
2160
2398
  if (new RegExp(`handle: ${staff.handle}[,\\s}]`).test(text)) continue;
2161
2399
  const line = ` - { handle: ${staff.handle}, brain: ${staff.brain}, label: from-${p.handle} }`;
2162
2400
  writeFileSync3(path, insertUnder(text, "peers", line));
2163
2401
  }
2164
2402
  }
2165
2403
  function addToOrgYaml(ws, plan2) {
2166
- const path = join13(ws.opsDir, "org.yaml");
2167
- const text = readFileSync11(path, "utf8");
2404
+ const path = join14(ws.opsDir, "org.yaml");
2405
+ const text = readFileSync12(path, "utf8");
2168
2406
  const { staff } = plan2;
2169
2407
  const staffLine = ` - { handle: ${staff.handle}, dir: ${plan2.dir}, name: ${staff.name}, schedule: "${staff.schedule}" }
2170
2408
  `;
@@ -2403,8 +2641,8 @@ function parseFlags4(argv) {
2403
2641
  }
2404
2642
 
2405
2643
  // src/commands/doctor.ts
2406
- import { existsSync as existsSync16, readdirSync as readdirSync6, readFileSync as readFileSync15 } from "fs";
2407
- import { join as join18 } from "path";
2644
+ import { existsSync as existsSync17, readdirSync as readdirSync6, readFileSync as readFileSync16 } from "fs";
2645
+ import { join as join19 } from "path";
2408
2646
 
2409
2647
  // src/lib/callable.ts
2410
2648
  function accessLink(org, repo) {
@@ -2429,16 +2667,16 @@ function refusal(res) {
2429
2667
  }
2430
2668
 
2431
2669
  // src/lib/memory.ts
2432
- import { existsSync as existsSync13, readdirSync as readdirSync4, readFileSync as readFileSync12, statSync as statSync3 } from "fs";
2433
- import { join as join14 } from "path";
2670
+ import { existsSync as existsSync14, readdirSync as readdirSync4, readFileSync as readFileSync13, statSync as statSync3 } from "fs";
2671
+ import { join as join15 } from "path";
2434
2672
  var FACT_RE = /^- \*\*`([^`]+)`\*\*\s*·\s*(.*)$/;
2435
2673
  var PROVENANCE_RE = /^\[([a-z][a-z0-9-]*)\]\s*/;
2436
2674
  var NOTE_RE = /\s*·?\s*\[note\]\(([^)]+)\)\s*$/;
2437
2675
  var SO_RE = /\*\*So:\*\*\s*/;
2438
2676
  function parseMemory(dir) {
2439
- const indexPath = join14(dir, "INDEX.md");
2440
- if (!existsSync13(indexPath)) throw new Error(`no memory index at ${indexPath}`);
2441
- const lines = readFileSync12(indexPath, "utf8").split("\n");
2677
+ const indexPath = join15(dir, "INDEX.md");
2678
+ if (!existsSync14(indexPath)) throw new Error(`no memory index at ${indexPath}`);
2679
+ const lines = readFileSync13(indexPath, "utf8").split("\n");
2442
2680
  const facts = [];
2443
2681
  const sections = [];
2444
2682
  const preamble = [];
@@ -2483,8 +2721,8 @@ function parseMemory(dir) {
2483
2721
  raw
2484
2722
  });
2485
2723
  });
2486
- const notesDir = join14(dir, "notes");
2487
- const notes = existsSync13(notesDir) ? readdirSync4(notesDir).filter((f) => f.endsWith(".md")).sort() : [];
2724
+ const notesDir = join15(dir, "notes");
2725
+ const notes = existsSync14(notesDir) ? readdirSync4(notesDir).filter((f) => f.endsWith(".md")).sort() : [];
2488
2726
  return {
2489
2727
  facts,
2490
2728
  sections,
@@ -2517,9 +2755,9 @@ function extractLinks(facts, notes, dir) {
2517
2755
  scan(f.slug, f.raw);
2518
2756
  }
2519
2757
  for (const note of notes) {
2520
- const path = join14(dir, "notes", note);
2521
- if (!existsSync13(path)) continue;
2522
- scan(note, readFileSync12(path, "utf8"));
2758
+ const path = join15(dir, "notes", note);
2759
+ if (!existsSync14(path)) continue;
2760
+ scan(note, readFileSync13(path, "utf8"));
2523
2761
  }
2524
2762
  return links;
2525
2763
  }
@@ -2568,7 +2806,7 @@ function lintMemory(doc, dir, budgets = DEFAULT_BUDGETS) {
2568
2806
  }
2569
2807
  if (f.note) {
2570
2808
  linked.add(f.note.replace(/^notes\//, ""));
2571
- if (!existsSync13(join14(dir, f.note))) {
2809
+ if (!existsSync14(join15(dir, f.note))) {
2572
2810
  problems.push({
2573
2811
  level: "error",
2574
2812
  rule: "dangling-note",
@@ -2629,8 +2867,8 @@ function lintMemory(doc, dir, budgets = DEFAULT_BUDGETS) {
2629
2867
  function overBudget(doc, dir, budgets) {
2630
2868
  const out = [];
2631
2869
  const kb = (bytes) => Math.round(bytes / 1024);
2632
- const index = join14(dir, "INDEX.md");
2633
- const indexBytes = existsSync13(index) ? statSync3(index).size : 0;
2870
+ const index = join15(dir, "INDEX.md");
2871
+ const indexBytes = existsSync14(index) ? statSync3(index).size : 0;
2634
2872
  if (indexBytes > budgets.indexKb * 1024) {
2635
2873
  const longest = [...doc.facts].sort((a, b) => b.raw.length - a.raw.length).slice(0, 3).map((f) => `\`${f.slug}\` (${f.raw.length})`);
2636
2874
  out.push({
@@ -2640,8 +2878,8 @@ function overBudget(doc, dir, budgets) {
2640
2878
  file: "memory/INDEX.md"
2641
2879
  });
2642
2880
  }
2643
- const log = join14(dir, "..", "log", "decisions.md");
2644
- const logBytes = existsSync13(log) ? statSync3(log).size : 0;
2881
+ const log = join15(dir, "..", "log", "decisions.md");
2882
+ const logBytes = existsSync14(log) ? statSync3(log).size : 0;
2645
2883
  if (logBytes > budgets.decisionsKb * 1024) {
2646
2884
  out.push({
2647
2885
  level: "warning",
@@ -2654,9 +2892,9 @@ function overBudget(doc, dir, budgets) {
2654
2892
  }
2655
2893
 
2656
2894
  // src/lib/runs.ts
2657
- import { existsSync as existsSync14, mkdtempSync as mkdtempSync2, readFileSync as readFileSync13, rmSync as rmSync2 } from "fs";
2895
+ import { existsSync as existsSync15, mkdtempSync as mkdtempSync2, readFileSync as readFileSync14, rmSync as rmSync2 } from "fs";
2658
2896
  import { tmpdir as tmpdir2 } from "os";
2659
- import { join as join15 } from "path";
2897
+ import { join as join16 } from "path";
2660
2898
  var KINDS2 = ["daily", "mention"];
2661
2899
  var records = /* @__PURE__ */ new Map();
2662
2900
  async function staffRuns(brain, handle, days = 30) {
@@ -2717,7 +2955,7 @@ async function staffRuns(brain, handle, days = 30) {
2717
2955
  async function recordFor(brain, id) {
2718
2956
  const key = `${brain}#${id}`;
2719
2957
  if (records.has(key)) return records.get(key);
2720
- const dir = mkdtempSync2(join15(tmpdir2(), "roster-run-"));
2958
+ const dir = mkdtempSync2(join16(tmpdir2(), "roster-run-"));
2721
2959
  try {
2722
2960
  const res = await ghJson([
2723
2961
  "run",
@@ -2730,8 +2968,8 @@ async function recordFor(brain, id) {
2730
2968
  "--dir",
2731
2969
  dir
2732
2970
  ]);
2733
- const file = join15(dir, "run.json");
2734
- const record = res.ok && existsSync14(file) ? JSON.parse(readFileSync13(file, "utf8")) : null;
2971
+ const file = join16(dir, "run.json");
2972
+ const record = res.ok && existsSync15(file) ? JSON.parse(readFileSync14(file, "utf8")) : null;
2735
2973
  records.set(key, record);
2736
2974
  return record;
2737
2975
  } catch {
@@ -2766,28 +3004,28 @@ function budgetOf(value) {
2766
3004
  // src/commands/upgrade.ts
2767
3005
  import { execFileSync as execFileSync5 } from "child_process";
2768
3006
  import {
2769
- existsSync as existsSync15,
3007
+ existsSync as existsSync16,
2770
3008
  mkdirSync as mkdirSync3,
2771
3009
  mkdtempSync as mkdtempSync4,
2772
3010
  readdirSync as readdirSync5,
2773
- readFileSync as readFileSync14,
3011
+ readFileSync as readFileSync15,
2774
3012
  rmSync as rmSync4,
2775
3013
  writeFileSync as writeFileSync5
2776
3014
  } from "fs";
2777
3015
  import { tmpdir as tmpdir4 } from "os";
2778
- import { dirname as dirname6, join as join17 } from "path";
3016
+ import { dirname as dirname6, join as join18 } from "path";
2779
3017
 
2780
3018
  // src/lib/merge.ts
2781
3019
  import { execFileSync as execFileSync4 } from "child_process";
2782
3020
  import { mkdtempSync as mkdtempSync3, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "fs";
2783
3021
  import { tmpdir as tmpdir3 } from "os";
2784
- import { join as join16 } from "path";
3022
+ import { join as join17 } from "path";
2785
3023
  function merge3(current, base2, incoming) {
2786
- const dir = mkdtempSync3(join16(tmpdir3(), "roster-merge-"));
3024
+ const dir = mkdtempSync3(join17(tmpdir3(), "roster-merge-"));
2787
3025
  try {
2788
- const c = join16(dir, "current");
2789
- const b = join16(dir, "base");
2790
- const i = join16(dir, "incoming");
3026
+ const c = join17(dir, "current");
3027
+ const b = join17(dir, "base");
3028
+ const i = join17(dir, "incoming");
2791
3029
  writeFileSync4(c, current);
2792
3030
  writeFileSync4(b, base2);
2793
3031
  writeFileSync4(i, incoming);
@@ -2860,7 +3098,7 @@ async function upgradeCommand(argv) {
2860
3098
  const ws = findWorkspace(opts.ops);
2861
3099
  const { parseYaml } = await loadComposer(ws.opsDir);
2862
3100
  const tplDir = opsTemplateDir();
2863
- const seedDir = join17(ws.opsDir, ".roster", "seed");
3101
+ const seedDir = join18(ws.opsDir, ".roster", "seed");
2864
3102
  if (opts.baseline) {
2865
3103
  return writeBaseline(ws.opsDir, seedDir, tplDir, opts.baseline);
2866
3104
  }
@@ -2891,12 +3129,12 @@ function planAll(tplDir, seedDir, opsDir) {
2891
3129
  }
2892
3130
  function plan(rel, tplDir, seedDir, opsDir) {
2893
3131
  const kind = classify(rel);
2894
- const incoming = readFileSync14(join17(tplDir, rel), "utf8");
2895
- const currentPath = join17(opsDir, rel);
2896
- const basePath = join17(seedDir, rel);
2897
- const has = (p) => existsSync15(p);
2898
- const current = has(currentPath) ? readFileSync14(currentPath, "utf8") : null;
2899
- const base2 = has(basePath) ? readFileSync14(basePath, "utf8") : null;
3132
+ const incoming = readFileSync15(join18(tplDir, rel), "utf8");
3133
+ const currentPath = join18(opsDir, rel);
3134
+ const basePath = join18(seedDir, rel);
3135
+ const has = (p) => existsSync16(p);
3136
+ const current = has(currentPath) ? readFileSync15(currentPath, "utf8") : null;
3137
+ const base2 = has(basePath) ? readFileSync15(basePath, "utf8") : null;
2900
3138
  if (current === null) {
2901
3139
  return { rel, kind, verdict: "added", note: "new in the framework", next: incoming };
2902
3140
  }
@@ -2998,7 +3236,7 @@ function apply(opsDir, seedDir, tplDir, plans) {
2998
3236
  for (const p of plans) {
2999
3237
  if (p.verdict === "edited-managed") edited++;
3000
3238
  if (p.verdict === "conflict") {
3001
- const out = join17(opsDir, p.rel + ".roster-merge");
3239
+ const out = join18(opsDir, p.rel + ".roster-merge");
3002
3240
  mkdirSync3(dirname6(out), { recursive: true });
3003
3241
  writeFileSync5(out, p.rejected);
3004
3242
  process.stdout.write(` wrote ${p.rel}.roster-merge \u2014 resolve it, then re-run
@@ -3014,7 +3252,7 @@ function apply(opsDir, seedDir, tplDir, plans) {
3014
3252
  if (p.verdict === "same") seed(seedDir, tplDir, p.rel);
3015
3253
  continue;
3016
3254
  }
3017
- const dest = join17(opsDir, p.rel);
3255
+ const dest = join18(opsDir, p.rel);
3018
3256
  mkdirSync3(dirname6(dest), { recursive: true });
3019
3257
  writeFileSync5(dest, p.next);
3020
3258
  seed(seedDir, tplDir, p.rel);
@@ -3025,7 +3263,7 @@ function apply(opsDir, seedDir, tplDir, plans) {
3025
3263
  process.stdout.write(left ? `, ${left} left for you
3026
3264
 
3027
3265
  ` : "\n\n");
3028
- if (!left) writeFileSync5(join17(opsDir, ".roster-version"), stamp() + "\n");
3266
+ if (!left) writeFileSync5(join18(opsDir, ".roster-version"), stamp() + "\n");
3029
3267
  if (edited) {
3030
3268
  process.stdout.write(
3031
3269
  ` ${edited} framework file${edited === 1 ? "" : "s"} still carr${edited === 1 ? "ies" : "y"} a local edit. Move it into the framework, or doctor keeps failing.
@@ -3036,12 +3274,12 @@ function apply(opsDir, seedDir, tplDir, plans) {
3036
3274
  return left || edited ? 1 : 0;
3037
3275
  }
3038
3276
  function seed(seedDir, tplDir, rel) {
3039
- const dest = join17(seedDir, rel);
3277
+ const dest = join18(seedDir, rel);
3040
3278
  mkdirSync3(dirname6(dest), { recursive: true });
3041
- writeFileSync5(dest, readFileSync14(join17(tplDir, rel), "utf8"));
3279
+ writeFileSync5(dest, readFileSync15(join18(tplDir, rel), "utf8"));
3042
3280
  }
3043
3281
  function writeBaseline(opsDir, seedDir, tplDir, ref) {
3044
- const framework = join17(tplDir, "..", "..");
3282
+ const framework = join18(tplDir, "..", "..");
3045
3283
  let files;
3046
3284
  try {
3047
3285
  files = execFileSync5(
@@ -3062,16 +3300,16 @@ function writeBaseline(opsDir, seedDir, tplDir, ref) {
3062
3300
  encoding: "utf8",
3063
3301
  maxBuffer: 16 * 1024 * 1024
3064
3302
  });
3065
- const dest = join17(seedDir, rel);
3303
+ const dest = join18(seedDir, rel);
3066
3304
  mkdirSync3(dirname6(dest), { recursive: true });
3067
3305
  writeFileSync5(dest, text);
3068
3306
  }
3069
- writeFileSync5(join17(opsDir, ".roster-version"), `${ref}
3307
+ writeFileSync5(join18(opsDir, ".roster-version"), `${ref}
3070
3308
  `);
3071
3309
  process.stdout.write(
3072
3310
  `
3073
3311
  Recorded ${files.length} files as the base, from the framework at ${ref}.
3074
- Written to ${join17(opsDir, ".roster", "seed")} \u2014 commit it; it is what makes the next
3312
+ Written to ${join18(opsDir, ".roster", "seed")} \u2014 commit it; it is what makes the next
3075
3313
  upgrade a merge rather than a copy.
3076
3314
 
3077
3315
  Now run: roster upgrade
@@ -3084,7 +3322,7 @@ function stamp() {
3084
3322
  try {
3085
3323
  return execFileSync5(
3086
3324
  "git",
3087
- ["-C", join17(opsTemplateDir(), "..", ".."), "rev-parse", "--short", "HEAD"],
3325
+ ["-C", join18(opsTemplateDir(), "..", ".."), "rev-parse", "--short", "HEAD"],
3088
3326
  {
3089
3327
  encoding: "utf8",
3090
3328
  stdio: ["ignore", "pipe", "ignore"]
@@ -3123,17 +3361,17 @@ function planBrains(ws, parseYaml) {
3123
3361
  const out = [];
3124
3362
  for (const entry of org.staff ?? []) {
3125
3363
  const dir = entry.dir ?? entry.handle;
3126
- const root = join17(ws.root, dir);
3364
+ const root = join18(ws.root, dir);
3127
3365
  const base2 = { handle: entry.handle, dir, root, files: [] };
3128
- const manifestPath = join17(root, "staff.yaml");
3129
- if (!existsSync15(manifestPath)) {
3130
- out.push({ ...base2, problem: existsSync15(root) ? "no staff.yaml" : "not checked out here" });
3366
+ const manifestPath = join18(root, "staff.yaml");
3367
+ if (!existsSync16(manifestPath)) {
3368
+ out.push({ ...base2, problem: existsSync16(root) ? "no staff.yaml" : "not checked out here" });
3131
3369
  continue;
3132
3370
  }
3133
3371
  let want;
3134
3372
  try {
3135
3373
  const spec2 = specFromManifest(
3136
- parseYaml(readFileSync14(manifestPath, "utf8"), "staff.yaml"),
3374
+ parseYaml(readFileSync15(manifestPath, "utf8"), "staff.yaml"),
3137
3375
  dir
3138
3376
  );
3139
3377
  want = renderTree(brainTemplateDir(), tokensFor(orgSpec2, spec2));
@@ -3146,20 +3384,20 @@ function planBrains(ws, parseYaml) {
3146
3384
  }
3147
3385
  for (const [rel, text] of want) {
3148
3386
  const kind = classifyBrain(rel);
3149
- const live = join17(root, rel);
3150
- if (!existsSync15(live)) {
3387
+ const live = join18(root, rel);
3388
+ if (!existsSync16(live)) {
3151
3389
  base2.files.push({ rel, kind, verdict: "missing", next: text });
3152
3390
  continue;
3153
3391
  }
3154
3392
  if (kind === "scaffold") continue;
3155
- const have = readFileSync14(live, "utf8");
3393
+ const have = readFileSync15(live, "utf8");
3156
3394
  base2.files.push(
3157
3395
  have === text ? { rel, kind, verdict: "same" } : { rel, kind, verdict: "regenerate", next: text, diff: unified(have, text) }
3158
3396
  );
3159
3397
  }
3160
3398
  for (const rel of callerFiles(root)) {
3161
3399
  if (want.has(rel)) continue;
3162
- const text = readFileSync14(join17(root, rel), "utf8");
3400
+ const text = readFileSync15(join18(root, rel), "utf8");
3163
3401
  if (!text.includes(`${orgSpec2.opsRepo}/.github/workflows/session.yaml`)) continue;
3164
3402
  base2.files.push({ rel, kind: "generated", verdict: "obsolete" });
3165
3403
  }
@@ -3168,14 +3406,14 @@ function planBrains(ws, parseYaml) {
3168
3406
  return out;
3169
3407
  }
3170
3408
  function unified(before, after) {
3171
- const dir = mkdtempSync4(join17(tmpdir4(), "roster-diff-"));
3409
+ const dir = mkdtempSync4(join18(tmpdir4(), "roster-diff-"));
3172
3410
  try {
3173
- writeFileSync5(join17(dir, "a"), before);
3174
- writeFileSync5(join17(dir, "b"), after);
3411
+ writeFileSync5(join18(dir, "a"), before);
3412
+ writeFileSync5(join18(dir, "b"), after);
3175
3413
  try {
3176
3414
  execFileSync5(
3177
3415
  "diff",
3178
- ["-u", "--label", "yours", "--label", "generated", join17(dir, "a"), join17(dir, "b")],
3416
+ ["-u", "--label", "yours", "--label", "generated", join18(dir, "a"), join18(dir, "b")],
3179
3417
  { encoding: "utf8" }
3180
3418
  );
3181
3419
  return "";
@@ -3188,8 +3426,8 @@ function unified(before, after) {
3188
3426
  }
3189
3427
  }
3190
3428
  function callerFiles(root) {
3191
- const dir = join17(root, ".github", "workflows");
3192
- if (!existsSync15(dir)) return [];
3429
+ const dir = join18(root, ".github", "workflows");
3430
+ if (!existsSync16(dir)) return [];
3193
3431
  return readdirSync5(dir).filter((n) => n.endsWith(".yaml") || n.endsWith(".yml")).map((n) => `.github/workflows/${n}`);
3194
3432
  }
3195
3433
  function reportBrains(brains, opts) {
@@ -3237,12 +3475,12 @@ function applyBrains(brains) {
3237
3475
  for (const b of brains) {
3238
3476
  for (const f of b.files) {
3239
3477
  if (f.verdict === "obsolete") {
3240
- rmSync4(join17(b.root, f.rel), { force: true });
3478
+ rmSync4(join18(b.root, f.rel), { force: true });
3241
3479
  wrote++;
3242
3480
  continue;
3243
3481
  }
3244
3482
  if (f.next === void 0) continue;
3245
- const dest = join17(b.root, f.rel);
3483
+ const dest = join18(b.root, f.rel);
3246
3484
  mkdirSync3(dirname6(dest), { recursive: true });
3247
3485
  writeFileSync5(dest, f.next);
3248
3486
  wrote++;
@@ -3368,7 +3606,7 @@ function checkWorkspace(ws, org) {
3368
3606
  });
3369
3607
  }
3370
3608
  try {
3371
- const plans = planAll(opsTemplateDir(), join18(ws.opsDir, ".roster", "seed"), ws.opsDir);
3609
+ const plans = planAll(opsTemplateDir(), join19(ws.opsDir, ".roster", "seed"), ws.opsDir);
3372
3610
  const edited = plans.filter((p) => p.verdict === "edited-managed");
3373
3611
  const stale = plans.filter((p) => p.next !== void 0);
3374
3612
  const unmergeable = plans.filter((p) => p.verdict === "conflict" || p.verdict === "no-base");
@@ -3417,7 +3655,7 @@ async function checkAgent(ws, org) {
3417
3655
  const scope = "workspace";
3418
3656
  let agent;
3419
3657
  try {
3420
- const { resolveAgent } = await import(`file://${join18(ws.opsDir, "agents.mjs")}`);
3658
+ const { resolveAgent } = await import(`file://${join19(ws.opsDir, "agents.mjs")}`);
3421
3659
  agent = resolveAgent(org, {});
3422
3660
  } catch (err) {
3423
3661
  return [
@@ -3431,8 +3669,8 @@ async function checkAgent(ws, org) {
3431
3669
  ];
3432
3670
  }
3433
3671
  const out = [{ scope, level: "ok", id: "agent", title: `runs on ${agent.id}` }];
3434
- const orgFile = join18(ws.opsDir, "org.yaml");
3435
- if (existsSync16(orgFile) && /FILL IN/.test(readFileSync15(orgFile, "utf8"))) {
3672
+ const orgFile = join19(ws.opsDir, "org.yaml");
3673
+ if (existsSync17(orgFile) && /FILL IN/.test(readFileSync16(orgFile, "utf8"))) {
3436
3674
  out.push({
3437
3675
  scope,
3438
3676
  level: "fail",
@@ -3442,8 +3680,8 @@ async function checkAgent(ws, org) {
3442
3680
  });
3443
3681
  }
3444
3682
  if (!agent.config) return out;
3445
- const path = join18(ws.opsDir, agent.config.path);
3446
- if (!existsSync16(path)) {
3683
+ const path = join19(ws.opsDir, agent.config.path);
3684
+ if (!existsSync17(path)) {
3447
3685
  out.push({
3448
3686
  scope,
3449
3687
  level: "fail",
@@ -3453,7 +3691,7 @@ async function checkAgent(ws, org) {
3453
3691
  });
3454
3692
  return out;
3455
3693
  }
3456
- if (/FILL IN/.test(readFileSync15(path, "utf8"))) {
3694
+ if (/FILL IN/.test(readFileSync16(path, "utf8"))) {
3457
3695
  out.push({
3458
3696
  scope,
3459
3697
  level: "fail",
@@ -3465,8 +3703,8 @@ async function checkAgent(ws, org) {
3465
3703
  return out;
3466
3704
  }
3467
3705
  function checkBusiness(ws) {
3468
- const path = join18(ws.opsDir, "org", "business.md");
3469
- if (!existsSync16(path)) {
3706
+ const path = join19(ws.opsDir, "org", "business.md");
3707
+ if (!existsSync17(path)) {
3470
3708
  return [
3471
3709
  {
3472
3710
  scope: "workspace",
@@ -3477,7 +3715,7 @@ function checkBusiness(ws) {
3477
3715
  }
3478
3716
  ];
3479
3717
  }
3480
- if (looksUnwritten(readFileSync15(path, "utf8"))) {
3718
+ if (looksUnwritten(readFileSync16(path, "utf8"))) {
3481
3719
  return [
3482
3720
  {
3483
3721
  scope: "workspace",
@@ -3491,8 +3729,8 @@ function checkBusiness(ws) {
3491
3729
  return [{ scope: "workspace", level: "ok", id: "business", title: "org/business.md is written" }];
3492
3730
  }
3493
3731
  function checkPriorities(ws) {
3494
- const path = join18(ws.opsDir, "org", "priorities.md");
3495
- if (!existsSync16(path)) {
3732
+ const path = join19(ws.opsDir, "org", "priorities.md");
3733
+ if (!existsSync17(path)) {
3496
3734
  return [
3497
3735
  {
3498
3736
  scope: "workspace",
@@ -3503,7 +3741,7 @@ function checkPriorities(ws) {
3503
3741
  }
3504
3742
  ];
3505
3743
  }
3506
- if (looksUnwritten(readFileSync15(path, "utf8"))) {
3744
+ if (looksUnwritten(readFileSync16(path, "utf8"))) {
3507
3745
  return [
3508
3746
  {
3509
3747
  scope: "workspace",
@@ -3522,8 +3760,8 @@ async function checkStaff(ws, org, entry, composer, online) {
3522
3760
  const scope = entry.handle;
3523
3761
  const out = [];
3524
3762
  const dir = entry.dir ?? entry.handle;
3525
- const root = join18(ws.root, dir);
3526
- if (!existsSync16(root)) {
3763
+ const root = join19(ws.root, dir);
3764
+ if (!existsSync17(root)) {
3527
3765
  return [
3528
3766
  {
3529
3767
  scope,
@@ -3534,9 +3772,9 @@ async function checkStaff(ws, org, entry, composer, online) {
3534
3772
  }
3535
3773
  ];
3536
3774
  }
3537
- const manifestPath = join18(root, "staff.yaml");
3775
+ const manifestPath = join19(root, "staff.yaml");
3538
3776
  let manifest = {};
3539
- if (!existsSync16(manifestPath)) {
3777
+ if (!existsSync17(manifestPath)) {
3540
3778
  out.push({
3541
3779
  scope,
3542
3780
  level: "fail",
@@ -3546,7 +3784,7 @@ async function checkStaff(ws, org, entry, composer, online) {
3546
3784
  });
3547
3785
  } else {
3548
3786
  try {
3549
- manifest = composer.parseYaml(readFileSync15(manifestPath, "utf8"), "staff.yaml");
3787
+ manifest = composer.parseYaml(readFileSync16(manifestPath, "utf8"), "staff.yaml");
3550
3788
  if (manifest.handle !== entry.handle) {
3551
3789
  out.push({
3552
3790
  scope,
@@ -3575,9 +3813,9 @@ async function checkStaff(ws, org, entry, composer, online) {
3575
3813
  });
3576
3814
  }
3577
3815
  }
3578
- const charterPath = join18(root, "CHARTER.md");
3816
+ const charterPath = join19(root, "CHARTER.md");
3579
3817
  out.push(
3580
- existsSync16(charterPath) ? { scope, level: "ok", id: "charter", title: "CHARTER.md present" } : {
3818
+ existsSync17(charterPath) ? { scope, level: "ok", id: "charter", title: "CHARTER.md present" } : {
3581
3819
  scope,
3582
3820
  level: "fail",
3583
3821
  id: "charter",
@@ -3585,7 +3823,7 @@ async function checkStaff(ws, org, entry, composer, online) {
3585
3823
  fix: "The charter is the personality; nothing else supplies it."
3586
3824
  }
3587
3825
  );
3588
- if (existsSync16(charterPath) && looksUnwritten(readFileSync15(charterPath, "utf8"))) {
3826
+ if (existsSync17(charterPath) && looksUnwritten(readFileSync16(charterPath, "utf8"))) {
3589
3827
  out.push({
3590
3828
  scope,
3591
3829
  level: "warn",
@@ -3594,8 +3832,8 @@ async function checkStaff(ws, org, entry, composer, online) {
3594
3832
  fix: `roster brief charter ${entry.handle}, or "Write the charter" on the portal's Staff screen`
3595
3833
  });
3596
3834
  }
3597
- const memDir = join18(root, "memory");
3598
- if (!existsSync16(join18(memDir, "INDEX.md"))) {
3835
+ const memDir = join19(root, "memory");
3836
+ if (!existsSync17(join19(memDir, "INDEX.md"))) {
3599
3837
  out.push({
3600
3838
  scope,
3601
3839
  level: "fail",
@@ -3692,8 +3930,8 @@ async function checkStaff(ws, org, entry, composer, online) {
3692
3930
  fix: "A private reusable workflow is only callable inside its own org, so this can never resolve."
3693
3931
  });
3694
3932
  }
3695
- const local = join18(ws.opsDir, ref.slice(opsRepo.length + 1));
3696
- if (!existsSync16(local)) {
3933
+ const local = join19(ws.opsDir, ref.slice(opsRepo.length + 1));
3934
+ if (!existsSync17(local)) {
3697
3935
  out.push({
3698
3936
  scope,
3699
3937
  level: "fail",
@@ -3711,7 +3949,7 @@ async function checkStaff(ws, org, entry, composer, online) {
3711
3949
  title: `2 callers, both pointing at ${opsRepo}`
3712
3950
  });
3713
3951
  }
3714
- const missing = (manifest.surfaces ?? []).map((s) => s.path).filter((p) => p && !existsSync16(join18(root, p)));
3952
+ const missing = (manifest.surfaces ?? []).map((s) => s.path).filter((p) => p && !existsSync17(join19(root, p)));
3715
3953
  if (missing.length) {
3716
3954
  out.push({
3717
3955
  scope,
@@ -3755,10 +3993,10 @@ function inferredCeiling(cancelled) {
3755
3993
  return most >= 2 ? best : null;
3756
3994
  }
3757
3995
  function readCallers(root, handle) {
3758
- const dir = join18(root, ".github", "workflows");
3759
- if (!existsSync16(dir)) return [];
3996
+ const dir = join19(root, ".github", "workflows");
3997
+ if (!existsSync17(dir)) return [];
3760
3998
  const mine = new RegExp(`^${handle}-(daily|mention)\\.ya?ml$`);
3761
- return readdirSync6(dir).filter((f) => mine.test(f)).sort().map((name) => ({ name, text: readFileSync15(join18(dir, name), "utf8") }));
3999
+ return readdirSync6(dir).filter((f) => mine.test(f)).sort().map((name) => ({ name, text: readFileSync16(join19(dir, name), "utf8") }));
3762
4000
  }
3763
4001
  async function checkOrgOnline(ws, org) {
3764
4002
  const out = [];
@@ -3838,10 +4076,10 @@ async function checkReviewGates(ws, org, composer) {
3838
4076
  );
3839
4077
  const apps = /* @__PURE__ */ new Set();
3840
4078
  for (const s of org.staff ?? []) {
3841
- const path = join18(ws.root, s.dir ?? s.handle, "staff.yaml");
3842
- if (!existsSync16(path)) continue;
4079
+ const path = join19(ws.root, s.dir ?? s.handle, "staff.yaml");
4080
+ if (!existsSync17(path)) continue;
3843
4081
  try {
3844
- const m = composer.parseYaml(readFileSync15(path, "utf8"), "staff.yaml");
4082
+ const m = composer.parseYaml(readFileSync16(path, "utf8"), "staff.yaml");
3845
4083
  for (const w of m.works_in ?? []) if (w?.repo) repos.add(String(w.repo));
3846
4084
  for (const i of m.identities ?? []) if (i?.app) apps.add(String(i.app));
3847
4085
  } catch {
@@ -4282,18 +4520,18 @@ import { writeFileSync as writeFileSync6 } from "fs";
4282
4520
 
4283
4521
  // src/lib/export.ts
4284
4522
  import { execFileSync as execFileSync6 } from "child_process";
4285
- import { existsSync as existsSync17, readdirSync as readdirSync7, readFileSync as readFileSync16, statSync as statSync4 } from "fs";
4286
- import { join as join19, relative as relative3 } from "path";
4523
+ import { existsSync as existsSync18, readdirSync as readdirSync7, readFileSync as readFileSync17, statSync as statSync4 } from "fs";
4524
+ import { join as join20, relative as relative3 } from "path";
4287
4525
  var SKIP = /* @__PURE__ */ new Set([".git", "node_modules", ".next", "dist", "out", ".DS_Store"]);
4288
4526
  function buildExport(ws, org, parseYaml, opts = {}) {
4289
4527
  const staff = [];
4290
4528
  for (const entry of org.staff ?? []) {
4291
4529
  const dir = entry.dir ?? entry.handle;
4292
- const root = join19(ws.root, dir);
4293
- if (!existsSync17(root)) continue;
4530
+ const root = join20(ws.root, dir);
4531
+ if (!existsSync18(root)) continue;
4294
4532
  const manifest = readManifest2(root, parseYaml);
4295
- const memDir = join19(root, "memory");
4296
- const doc = existsSync17(join19(memDir, "INDEX.md")) ? parseMemory(memDir) : { facts: [], sections: [], notes: [], preamble: "", links: [] };
4533
+ const memDir = join20(root, "memory");
4534
+ const doc = existsSync18(join20(memDir, "INDEX.md")) ? parseMemory(memDir) : { facts: [], sections: [], notes: [], preamble: "", links: [] };
4297
4535
  const commits = gitLog(root, 25);
4298
4536
  staff.push({
4299
4537
  handle: entry.handle,
@@ -4313,7 +4551,7 @@ function buildExport(ws, org, parseYaml, opts = {}) {
4313
4551
  sections: doc.sections,
4314
4552
  notes: doc.notes,
4315
4553
  links: doc.links,
4316
- problems: existsSync17(join19(memDir, "INDEX.md")) ? lintMemory(doc, memDir, budgetsFor(org, manifest)) : [],
4554
+ problems: existsSync18(join20(memDir, "INDEX.md")) ? lintMemory(doc, memDir, budgetsFor(org, manifest)) : [],
4317
4555
  surfaces: readSurfaces(root, manifest.surfaces ?? []),
4318
4556
  recentCommits: commits,
4319
4557
  factsChanged: factsChanged(root, opts.since ?? "14 days ago"),
@@ -4341,25 +4579,25 @@ function botName(raw) {
4341
4579
  return String(raw).replace(/\[bot\]$/, "");
4342
4580
  }
4343
4581
  function readManifest2(root, parseYaml) {
4344
- const path = join19(root, "staff.yaml");
4345
- if (!existsSync17(path)) return {};
4582
+ const path = join20(root, "staff.yaml");
4583
+ if (!existsSync18(path)) return {};
4346
4584
  try {
4347
- return parseYaml(readFileSync16(path, "utf8"), "staff.yaml");
4585
+ return parseYaml(readFileSync17(path, "utf8"), "staff.yaml");
4348
4586
  } catch {
4349
4587
  return {};
4350
4588
  }
4351
4589
  }
4352
4590
  function readRig(root, manifest, commits) {
4353
- const wfDir = join19(root, ".github", "workflows");
4591
+ const wfDir = join20(root, ".github", "workflows");
4354
4592
  const declared = manifest.surfaces ?? [];
4355
4593
  return {
4356
- workflows: existsSync17(wfDir) ? readdirSync7(wfDir).filter((f) => /\.ya?ml$/.test(f)).sort() : [],
4357
- hasCharter: existsSync17(join19(root, "CHARTER.md")),
4358
- charterStub: !existsSync17(join19(root, "CHARTER.md")) || looksUnwritten(readFileSync16(join19(root, "CHARTER.md"), "utf8")),
4359
- hasManifest: existsSync17(join19(root, "staff.yaml")),
4360
- missingSurfaces: declared.filter((s) => s?.path && !existsSync17(join19(root, s.path))).map((s) => s.path),
4361
- memoryBytes: bytesOf(join19(root, "memory", "INDEX.md")),
4362
- notesBytes: dirBytes(join19(root, "memory", "notes")),
4594
+ workflows: existsSync18(wfDir) ? readdirSync7(wfDir).filter((f) => /\.ya?ml$/.test(f)).sort() : [],
4595
+ hasCharter: existsSync18(join20(root, "CHARTER.md")),
4596
+ charterStub: !existsSync18(join20(root, "CHARTER.md")) || looksUnwritten(readFileSync17(join20(root, "CHARTER.md"), "utf8")),
4597
+ hasManifest: existsSync18(join20(root, "staff.yaml")),
4598
+ missingSurfaces: declared.filter((s) => s?.path && !existsSync18(join20(root, s.path))).map((s) => s.path),
4599
+ memoryBytes: bytesOf(join20(root, "memory", "INDEX.md")),
4600
+ notesBytes: dirBytes(join20(root, "memory", "notes")),
4363
4601
  lastCommit: commits[0],
4364
4602
  lastMemoryCommit: gitLog(root, 1, ["--", "memory"])[0]
4365
4603
  };
@@ -4372,20 +4610,20 @@ function bytesOf(path) {
4372
4610
  }
4373
4611
  }
4374
4612
  function dirBytes(dir) {
4375
- if (!existsSync17(dir)) return 0;
4613
+ if (!existsSync18(dir)) return 0;
4376
4614
  return walk2(dir, dir).reduce((n, f) => n + f.bytes, 0);
4377
4615
  }
4378
4616
  function readSurfaces(root, declared) {
4379
- return declared.filter((s) => s?.path && existsSync17(join19(root, s.path))).map((s) => ({
4617
+ return declared.filter((s) => s?.path && existsSync18(join20(root, s.path))).map((s) => ({
4380
4618
  path: s.path,
4381
4619
  render: s.render ?? "doc",
4382
- files: walk2(join19(root, s.path), root)
4620
+ files: walk2(join20(root, s.path), root)
4383
4621
  }));
4384
4622
  }
4385
4623
  function walk2(dir, root, out = []) {
4386
4624
  for (const name of readdirSync7(dir)) {
4387
4625
  if (SKIP.has(name)) continue;
4388
- const full = join19(dir, name);
4626
+ const full = join20(dir, name);
4389
4627
  const st = statSync4(full);
4390
4628
  if (st.isDirectory()) walk2(full, root, out);
4391
4629
  else {
@@ -4482,15 +4720,15 @@ async function exportCommand(argv) {
4482
4720
  }
4483
4721
 
4484
4722
  // src/lib/audit.ts
4485
- import { existsSync as existsSync18, readFileSync as readFileSync17 } from "fs";
4486
- import { join as join20 } from "path";
4723
+ import { existsSync as existsSync19, readFileSync as readFileSync18 } from "fs";
4724
+ import { join as join21 } from "path";
4487
4725
  var LONG_WORDS = 3500;
4488
4726
  var ECHO_CHARS = 60;
4489
4727
  function auditPrompt(ws, view, roots) {
4490
4728
  const out = [];
4491
4729
  const text = (rel) => {
4492
- const full = join20(ws.root, rel);
4493
- return existsSync18(full) ? readFileSync17(full, "utf8") : "";
4730
+ const full = join21(ws.root, rel);
4731
+ return existsSync19(full) ? readFileSync18(full, "utf8") : "";
4494
4732
  };
4495
4733
  for (const layer of [...view.layers, ...view.runtime]) {
4496
4734
  if (layer.missing) continue;
@@ -4540,12 +4778,12 @@ Decide which layer each one belongs in and remove it from the others.`
4540
4778
  level: "warning",
4541
4779
  title: `${words.toLocaleString()} words, read in full on every run`,
4542
4780
  detail: `Past about ${LONG_WORDS.toLocaleString()} words a prompt is usually carrying paragraphs nobody has re-read since they were written. This is paid on every run.`,
4543
- want: `This prompt is ${words.toLocaleString()} words and is read in full on every run. Find what can go: anything restated, anything that was true once, anything an agent would do anyway. Show me the cuts, not a rewrite.`
4781
+ want: `This prompt is ${words.toLocaleString()} words and is read in full on every run. Find what can go: anything restated, anything that was true once, anything an agent would do anyway. Cut more than you add, and tell me what you cut.`
4544
4782
  });
4545
4783
  }
4546
- const bases = [ws.root, ...roots.map((r) => join20(ws.root, r))];
4784
+ const bases = [ws.root, ...roots.map((r) => join21(ws.root, r))];
4547
4785
  for (const rel of namedPaths(view.composed)) {
4548
- if (bases.some((b) => existsSync18(join20(b, rel)))) continue;
4786
+ if (bases.some((b) => existsSync19(join21(b, rel)))) continue;
4549
4787
  out.push({
4550
4788
  id: "dangling",
4551
4789
  level: "warning",
@@ -4824,8 +5062,8 @@ function parseFlags7(argv) {
4824
5062
 
4825
5063
  // src/commands/init.ts
4826
5064
  import { execFileSync as execFileSync7 } from "child_process";
4827
- import { existsSync as existsSync19, mkdirSync as mkdirSync4, readFileSync as readFileSync18, writeFileSync as writeFileSync7 } from "fs";
4828
- import { dirname as dirname7, join as join21, resolve as resolve3 } from "path";
5065
+ import { existsSync as existsSync20, mkdirSync as mkdirSync4, readFileSync as readFileSync19, writeFileSync as writeFileSync7 } from "fs";
5066
+ import { dirname as dirname7, join as join22, resolve as resolve3 } from "path";
4829
5067
  var initHelp = `
4830
5068
  roster init --org <github-org> [--name "Acme"] [--human <login>] [--apply]
4831
5069
 
@@ -4870,9 +5108,9 @@ async function initCommand(argv) {
4870
5108
  const name = opts.name ?? opts.org;
4871
5109
  const opsName = opts.ops ?? "roster-ops";
4872
5110
  const root = resolve3(opts.dir ?? process.cwd());
4873
- const opsDir = join21(root, opsName);
5111
+ const opsDir = join22(root, opsName);
4874
5112
  const marker = opts.marker ?? human.split("-")[0];
4875
- if (existsSync19(opsDir)) {
5113
+ if (existsSync20(opsDir)) {
4876
5114
  process.stderr.write(`roster: ${opsDir} already exists. This org is already initialised.
4877
5115
  `);
4878
5116
  return 2;
@@ -4919,14 +5157,14 @@ async function initCommand(argv) {
4919
5157
  }
4920
5158
  mkdirSync4(opsDir, { recursive: true });
4921
5159
  for (const [rel, text] of files) {
4922
- const dest = join21(opsDir, rel);
5160
+ const dest = join22(opsDir, rel);
4923
5161
  mkdirSync4(dirname7(dest), { recursive: true });
4924
5162
  writeFileSync7(dest, text);
4925
5163
  }
4926
5164
  for (const rel of templateFiles(opsTemplateDir())) {
4927
- const dest = join21(opsDir, ".roster", "seed", rel);
5165
+ const dest = join22(opsDir, ".roster", "seed", rel);
4928
5166
  mkdirSync4(dirname7(dest), { recursive: true });
4929
- writeFileSync7(dest, readFileSync18(join21(opsTemplateDir(), rel), "utf8"));
5167
+ writeFileSync7(dest, readFileSync19(join22(opsTemplateDir(), rel), "utf8"));
4930
5168
  }
4931
5169
  process.stdout.write(` wrote ${files.size} files and the recorded base
4932
5170
  `);
@@ -4976,7 +5214,7 @@ async function initCommand(argv) {
4976
5214
  async function initFiles(o) {
4977
5215
  const files = /* @__PURE__ */ new Map();
4978
5216
  for (const rel of templateFiles(opsTemplateDir())) {
4979
- files.set(rel, readFileSync18(join21(opsTemplateDir(), rel), "utf8"));
5217
+ files.set(rel, readFileSync19(join22(opsTemplateDir(), rel), "utf8"));
4980
5218
  }
4981
5219
  const agent = o.agent ?? "claude-code-action";
4982
5220
  const preset = await agentPreset(agent);
@@ -5001,7 +5239,7 @@ async function initFiles(o) {
5001
5239
  return files;
5002
5240
  }
5003
5241
  async function agentPreset(id) {
5004
- const path = join21(opsTemplateDir(), "agents.mjs");
5242
+ const path = join22(opsTemplateDir(), "agents.mjs");
5005
5243
  const { PRESETS } = await import(`file://${path}`);
5006
5244
  const preset = PRESETS[id];
5007
5245
  if (!preset) {
@@ -5135,7 +5373,7 @@ function stamp2() {
5135
5373
  try {
5136
5374
  return execFileSync7(
5137
5375
  "git",
5138
- ["-C", join21(opsTemplateDir(), "..", ".."), "rev-parse", "--short", "HEAD"],
5376
+ ["-C", join22(opsTemplateDir(), "..", ".."), "rev-parse", "--short", "HEAD"],
5139
5377
  {
5140
5378
  encoding: "utf8",
5141
5379
  stdio: ["ignore", "pipe", "ignore"]
@@ -5171,7 +5409,7 @@ function parseFlags8(argv) {
5171
5409
  }
5172
5410
 
5173
5411
  // src/commands/lint.ts
5174
- import { join as join22 } from "path";
5412
+ import { join as join23 } from "path";
5175
5413
  var lintHelp = `
5176
5414
  roster lint [handle]
5177
5415
 
@@ -5199,8 +5437,8 @@ async function lintCommand(argv) {
5199
5437
  let errors = 0;
5200
5438
  let warnings = 0;
5201
5439
  for (const s of staff) {
5202
- const root = join22(ws.root, s.dir ?? s.handle);
5203
- const dir = join22(root, "memory");
5440
+ const root = join23(ws.root, s.dir ?? s.handle);
5441
+ const dir = join23(root, "memory");
5204
5442
  const budgets = budgetsFor(org, readManifest2(root, parseYaml));
5205
5443
  let problems;
5206
5444
  let factCount = 0;
@@ -5303,14 +5541,14 @@ function askBody(req) {
5303
5541
  if (anchor?.path) {
5304
5542
  out.push("", `They were looking at \`${anchor.path}\`.`);
5305
5543
  const hunk = clip(anchor.patch ?? "");
5306
- if (hunk) out.push("", fence3(hunk, "diff"));
5544
+ if (hunk) out.push("", fence4(hunk, "diff"));
5307
5545
  else if (anchor.patch) out.push("", "The diff for it is on the pull request.");
5308
5546
  }
5309
5547
  out.push(
5310
5548
  "",
5311
5549
  `**Answer on the ${it}, not here.** That is where it will be read${isPr ? ", and it is where the diff is" : ""}:`,
5312
5550
  "",
5313
- fence3(`gh issue comment ${pr.number} --repo ${pr.repo} --body "\u2026"`, ""),
5551
+ fence4(`gh issue comment ${pr.number} --repo ${pr.repo} --body "\u2026"`, ""),
5314
5552
  "",
5315
5553
  ...isPr ? [
5316
5554
  pr.head ? `Push any change to \`${pr.head}\`. Close this issue once the reply is up.` : "Push any change to the pull request's branch. Close this issue once the reply is up."
@@ -5334,7 +5572,7 @@ function clip(patch) {
5334
5572
  `\u2026 ${lines.length - MAX_PATCH_LINES} more lines, on the pull request`
5335
5573
  ].join("\n");
5336
5574
  }
5337
- function fence3(text, lang) {
5575
+ function fence4(text, lang) {
5338
5576
  const longest = Math.max(0, ...[...text.matchAll(/`+/g)].map((m) => m[0].length));
5339
5577
  const ticks = "`".repeat(Math.max(3, longest + 1));
5340
5578
  return `${ticks}${lang}
@@ -5538,13 +5776,13 @@ function execWithStdin(args, input) {
5538
5776
 
5539
5777
  // src/lib/attach.ts
5540
5778
  import { execFileSync as execFileSync8 } from "child_process";
5541
- import { existsSync as existsSync20, mkdirSync as mkdirSync5, writeFileSync as writeFileSync8 } from "fs";
5542
- import { basename, extname, join as join23 } from "path";
5779
+ import { existsSync as existsSync21, mkdirSync as mkdirSync5, writeFileSync as writeFileSync8 } from "fs";
5780
+ import { basename, extname, join as join24 } from "path";
5543
5781
  var MAX_UPLOAD = 25 * 1024 * 1024;
5544
5782
  var ATTACH_DIR = "attachments";
5545
5783
  function attach(opts) {
5546
5784
  const { repoDir, repo, data, today } = opts;
5547
- if (!existsSync20(join23(repoDir, ".git"))) {
5785
+ if (!existsSync21(join24(repoDir, ".git"))) {
5548
5786
  throw new Error(`${repo} is not checked out here, so there is nowhere to put the file`);
5549
5787
  }
5550
5788
  if (!data.length) throw new Error("that file is empty");
@@ -5552,8 +5790,8 @@ function attach(opts) {
5552
5790
  throw new Error(`${basename(opts.name)} is larger than ${MAX_UPLOAD / 1024 / 1024}MB`);
5553
5791
  }
5554
5792
  const rel = free(repoDir, `${ATTACH_DIR}/${today}-${safeName(opts.name)}`);
5555
- const full = join23(repoDir, rel);
5556
- mkdirSync5(join23(repoDir, ATTACH_DIR), { recursive: true });
5793
+ const full = join24(repoDir, rel);
5794
+ mkdirSync5(join24(repoDir, ATTACH_DIR), { recursive: true });
5557
5795
  writeFileSync8(full, data);
5558
5796
  const git4 = (args) => execFileSync8("git", ["-C", repoDir, ...args], {
5559
5797
  encoding: "utf8",
@@ -5581,12 +5819,12 @@ function safeName(raw) {
5581
5819
  return stem + ext.replace(/[^a-z0-9.]/g, "");
5582
5820
  }
5583
5821
  function free(repoDir, rel) {
5584
- if (!existsSync20(join23(repoDir, rel))) return rel;
5822
+ if (!existsSync21(join24(repoDir, rel))) return rel;
5585
5823
  const ext = extname(rel);
5586
5824
  const stem = rel.slice(0, rel.length - ext.length);
5587
5825
  for (let n = 2; n < 500; n++) {
5588
5826
  const next = `${stem}-${n}${ext}`;
5589
- if (!existsSync20(join23(repoDir, next))) return next;
5827
+ if (!existsSync21(join24(repoDir, next))) return next;
5590
5828
  }
5591
5829
  throw new Error("too many files by that name today");
5592
5830
  }
@@ -6006,7 +6244,7 @@ function short4(e) {
6006
6244
  var FINISHED_HOURS = 6;
6007
6245
  function parseTitle(handle, title, workflow = "") {
6008
6246
  const m = new RegExp(
6009
- `^${handle.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")} (daily|manual|follow-on|mention|peer)(?: #(\\d+))?\\s*$`
6247
+ `^${handle.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")} (daily|manual|follow-on|mention|peer|ignored)(?: #(\\d+))?\\s*$`
6010
6248
  ).exec(title.trim());
6011
6249
  if (m) return { trigger: m[1], issue: m[2] ? Number(m[2]) : null };
6012
6250
  if (/daily/i.test(workflow)) return { trigger: "daily", issue: null };
@@ -6020,6 +6258,7 @@ function shapeLive(handle, brain, raw, limit, now = Date.now()) {
6020
6258
  for (const r of raw) {
6021
6259
  if (r.conclusion === "skipped") continue;
6022
6260
  const { trigger, issue } = parseTitle(handle, r.displayTitle ?? "", r.workflowName ?? "");
6261
+ if (trigger === "ignored") continue;
6023
6262
  const run6 = {
6024
6263
  id: Number(r.databaseId),
6025
6264
  trigger,
@@ -6096,216 +6335,6 @@ async function markRead(thread) {
6096
6335
  return res.ok;
6097
6336
  }
6098
6337
 
6099
- // src/lib/paste.ts
6100
- var BEGIN = (path) => `<<<ROSTER FILE ${path}>>>`;
6101
- var END = "<<<ROSTER END>>>";
6102
- var BLOCK = /^[ \t]*<<<ROSTER FILE ([^>\n]+)>>>[ \t]*\n([\s\S]*?)\n?[ \t]*<<<ROSTER END>>>/gm;
6103
- var MIN_WORDS = 40;
6104
- function parsePaste(raw, targets) {
6105
- const files = [];
6106
- const problems = [];
6107
- const text = raw.replace(/\r\n/g, "\n");
6108
- if (!text.trim()) {
6109
- return {
6110
- files,
6111
- problems: [{ id: "empty", message: "Nothing was pasted.", retry: "" }]
6112
- };
6113
- }
6114
- const known = new Map(targets.map((t) => [t.path, t]));
6115
- BLOCK.lastIndex = 0;
6116
- for (const m of text.matchAll(BLOCK)) {
6117
- const written = m[1].trim();
6118
- const body = strip(m[2] ?? "");
6119
- const target = resolveTarget(written, known);
6120
- if (!target) {
6121
- problems.push({
6122
- id: "unknown-path",
6123
- path: written,
6124
- message: `The answer writes ${written}, which this brief did not ask for.`,
6125
- retry: `Write only ${[...known.keys()].join(", ")}, using the sentinels exactly as given.`
6126
- });
6127
- continue;
6128
- }
6129
- files.push({
6130
- path: target.path,
6131
- text: ensureNewline(body),
6132
- unchanged: body.trim() === target.before.trim()
6133
- });
6134
- }
6135
- if (!files.length && !problems.length) {
6136
- problems.push({
6137
- id: "no-envelope",
6138
- message: "Your AI answered in prose. The reply has no <<<ROSTER FILE ...>>> block, so there is nothing here that can be saved as a file.",
6139
- retry: `Send that again as the finished file only, wrapped exactly like this and with nothing outside it:
6140
-
6141
- ${BEGIN(targets[0]?.path ?? "<path>")}
6142
- ...the whole file...
6143
- ${END}`
6144
- });
6145
- }
6146
- for (const file of files) {
6147
- const words = file.text.split(/\s+/).filter(Boolean).length;
6148
- if (file.unchanged) {
6149
- problems.push({
6150
- id: "brief-echoed",
6151
- path: file.path,
6152
- message: `${file.path} came back byte for byte. Some models restate a long prompt before working.`,
6153
- retry: "That is the file I sent you. Write the new version."
6154
- });
6155
- continue;
6156
- }
6157
- if (words < MIN_WORDS) {
6158
- problems.push({
6159
- id: "too-short",
6160
- path: file.path,
6161
- message: `${file.path} is ${words} words. That is a failed answer, not a short one.`,
6162
- retry: "That is far too short. Interview me properly first, then write the whole file."
6163
- });
6164
- continue;
6165
- }
6166
- if (looksUnwritten(file.text)) {
6167
- problems.push({
6168
- id: "stub-echoed",
6169
- path: file.path,
6170
- message: `${file.path} still carries the scaffold's own headings, so the questions were never answered.`,
6171
- retry: "You returned the template with its instructions still in it. Answer the questions and write the real file, with none of the scaffold's guidance left in it."
6172
- });
6173
- }
6174
- }
6175
- return { files, problems };
6176
- }
6177
- function resolveTarget(written, known) {
6178
- const exact = known.get(written);
6179
- if (exact) return exact;
6180
- const tail2 = written.replace(/^(\.\/)+/, "").replace(/^\/+/, "");
6181
- if (!tail2 || tail2.split("/").includes("..")) return void 0;
6182
- const matches = [...known.values()].filter((t) => t.path === tail2 || t.path.endsWith(`/${tail2}`));
6183
- return matches.length === 1 ? matches[0] : void 0;
6184
- }
6185
- function strip(body) {
6186
- const trimmed = body.replace(/^\n+/, "").replace(/\s+$/, "");
6187
- const fenced = /^(`{3,}|~{3,})[^\n]*\n([\s\S]*)\n\1\s*$/.exec(trimmed);
6188
- return fenced ? fenced[2] : trimmed;
6189
- }
6190
- function ensureNewline(text) {
6191
- return text.endsWith("\n") ? text : `${text}
6192
- `;
6193
- }
6194
-
6195
- // src/lib/pastebrief.ts
6196
- import { existsSync as existsSync21, readFileSync as readFileSync19 } from "fs";
6197
- import { join as join24 } from "path";
6198
- var WRITES = {
6199
- discover: (c) => [`${c.opsName}/org/business.md`],
6200
- voice: (c) => [`${c.opsName}/org/voice.md`],
6201
- priorities: (c) => [`${c.opsName}/org/priorities.md`],
6202
- charter: (c) => [`${c.dir}/CHARTER.md`]
6203
- };
6204
- var READS = {
6205
- discover: (c) => [
6206
- `${c.opsName}/org/operating.md`,
6207
- `${c.opsName}/org/voice.md`,
6208
- `${c.opsName}/org/guardrails.md`
6209
- ],
6210
- voice: (c) => [`${c.opsName}/org/business.md`, `${c.opsName}/org/guardrails.md`],
6211
- priorities: (c) => [`${c.opsName}/org/business.md`],
6212
- /* A charter is the difference between this staff member and everyone else, so the peers'
6213
- charters are the most useful thing in the room: without them the model writes a second
6214
- copy of whoever it was shown. */
6215
- charter: (c) => [
6216
- `${c.opsName}/org/business.md`,
6217
- `${c.opsName}/org/operating.md`,
6218
- `${c.opsName}/org/voice.md`,
6219
- `${c.opsName}/org/guardrails.md`,
6220
- ...c.peers.map((p) => `${p}/CHARTER.md`)
6221
- ]
6222
- };
6223
- function pasteable(kind) {
6224
- return kind in WRITES;
6225
- }
6226
- function pasteBrief(ws, kind, rendered, ctx) {
6227
- if (!pasteable(kind)) throw new Error(`there is no paste-mode brief for "${kind}"`);
6228
- const c = { opsName: ws.opsName, dir: ctx.dir ?? "", peers: ctx.peers ?? [] };
6229
- const writes = WRITES[kind](c);
6230
- const reads = READS[kind](c).filter((rel) => !writes.includes(rel));
6231
- const targets = writes.map((rel) => ({ path: rel, before: read2(ws, rel) }));
6232
- const parts = [rendered.trimEnd(), "", "---", "", head(writes)];
6233
- parts.push("", "## The files this refers to", "");
6234
- parts.push(
6235
- "You cannot open anything, so everything the brief mentions is below. Do not ask for files;",
6236
- "if something you want is genuinely not here, say what and why, and work without it.",
6237
- ""
6238
- );
6239
- for (const rel of reads) parts.push(...fileBlock(ws, rel, "read this, do not rewrite it"));
6240
- parts.push("## What you are writing", "");
6241
- for (const rel of targets) {
6242
- parts.push(...fileBlock(ws, rel.path, rel.before.trim() ? "replace this" : "currently empty"));
6243
- }
6244
- parts.push(...tail(writes));
6245
- return { kind, text: `${parts.join("\n").replace(/\n{3,}/g, "\n\n")}
6246
- `, targets };
6247
- }
6248
- function head(writes) {
6249
- return [
6250
- "## How to answer",
6251
- "",
6252
- "**You are talking to a person, not to a filesystem.** You cannot read or write anything.",
6253
- "Everything you need is in this message, and your answer comes back by being pasted into a",
6254
- "tool that turns it into " + (writes.length === 1 ? "a file" : "files") + ".",
6255
- "",
6256
- "**Interview first.** Ask one question at a time and wait. Do not produce the file in your",
6257
- "first reply, and do not present a form. When you have enough, say so and write it."
6258
- ].join("\n");
6259
- }
6260
- function tail(writes) {
6261
- const example = writes.map((rel) => `${BEGIN(rel)}
6262
- ...the whole file, exactly as it should be saved...
6263
- ${END}`).join("\n\n");
6264
- return [
6265
- "---",
6266
- "",
6267
- "## How to hand it back",
6268
- "",
6269
- "When the interview is done and you are writing the final version, wrap it like this:",
6270
- "",
6271
- "```",
6272
- example,
6273
- "```",
6274
- "",
6275
- `**Rules**, because the answer is parsed rather than read:`,
6276
- "",
6277
- "- The sentinel lines go on their own lines, spelled exactly as above.",
6278
- "- Between them goes the **whole file**, exactly as it should be saved. No fences around it,",
6279
- " no commentary inside it, no summary of what you changed.",
6280
- "- Anything you want to say to the person goes **outside** the block. That part is ignored by",
6281
- " the tool and read by them, so it is the right place for what you were unsure about.",
6282
- `- Write only ${writes.join(" and ")}. Nothing else.`,
6283
- "",
6284
- "Nothing is saved automatically. They will see a diff and decide."
6285
- ];
6286
- }
6287
- function fileBlock(ws, rel, note) {
6288
- const body = read2(ws, rel);
6289
- if (!body.trim()) return [`### \`${rel}\``, "", `*Empty or absent (${note}).*`, ""];
6290
- return [`### \`${rel}\``, "", `*${note}*`, "", fence4(body), ""];
6291
- }
6292
- function read2(ws, rel) {
6293
- const full = join24(ws.root, rel);
6294
- return existsSync21(full) ? readFileSync19(full, "utf8") : "";
6295
- }
6296
- function fence4(text) {
6297
- const longest = (text.match(/`{3,}/g) ?? []).reduce((n, m) => Math.max(n, m.length), 2);
6298
- const bars = "`".repeat(longest + 1);
6299
- return `${bars}
6300
- ${text.trimEnd()}
6301
- ${bars}`;
6302
- }
6303
- function briefTemplate(kind) {
6304
- const path = join24(briefTemplateDir(), `${kind}.md`);
6305
- if (!existsSync21(path)) throw new Error(`no brief template for "${kind}"`);
6306
- return readFileSync19(path, "utf8");
6307
- }
6308
-
6309
6338
  // src/lib/runonce.ts
6310
6339
  import { execFile as execFile6 } from "child_process";
6311
6340
  function dailyWorkflow(handle) {
@@ -6761,6 +6790,11 @@ async function portalCommand(argv) {
6761
6790
  const progressCache = /* @__PURE__ */ new Map();
6762
6791
  const LABEL_TTL = 3e5;
6763
6792
  const brainDirs = () => (readOrg(ws.opsDir, parseYaml).staff ?? []).map((s) => s.dir ?? s.handle);
6793
+ const amendTargets = (view) => [
6794
+ ...new Set(
6795
+ view.layers.filter((l) => !l.missing && isWritable(ws, l.path, brainDirs())).map((l) => l.path)
6796
+ )
6797
+ ];
6764
6798
  const knownRepos = () => {
6765
6799
  const org = readOrg(ws.opsDir, parseYaml);
6766
6800
  return (org.repos ?? []).map((r) => ({
@@ -7487,7 +7521,41 @@ async function portalCommand(argv) {
7487
7521
  "content-type": "text/plain; charset=utf-8",
7488
7522
  "cache-control": "no-store"
7489
7523
  });
7490
- res.end(amendBrief(w, view, tokens, want));
7524
+ res.end(amendBrief(w, view, tokens, want, amendTargets(view)));
7525
+ return;
7526
+ }
7527
+ if (url.pathname === "/api/amend/check") {
7528
+ if (req.method !== "POST") {
7529
+ res.writeHead(405).end("POST only");
7530
+ return;
7531
+ }
7532
+ body(req).then((raw) => {
7533
+ const payload = JSON.parse(raw || "{}");
7534
+ const org = readOrg(w.opsDir, parseYaml);
7535
+ const entry = (org.staff ?? []).find((s) => s.handle === payload.staff);
7536
+ const kind = payload.kind ?? "daily";
7537
+ if (!entry || !KINDS.includes(kind))
7538
+ throw new Error("bad request");
7539
+ const dir = entry.dir ?? entry.handle;
7540
+ const view = promptView(w, compose, entry.handle, join28(w.root, dir), kind);
7541
+ const targets = amendTargets(view).map((path) => ({
7542
+ path,
7543
+ before: existsSync25(join28(w.root, path)) ? readFileSync22(join28(w.root, path), "utf8") : ""
7544
+ }));
7545
+ const result = parsePaste(payload.answer ?? "", targets);
7546
+ const before = new Map(targets.map((t) => [t.path, t.before]));
7547
+ json(res, {
7548
+ ...result,
7549
+ files: result.files.map((f) => ({
7550
+ ...f,
7551
+ before: before.get(f.path) ?? "",
7552
+ writable: isWritable(w, f.path, brainDirs())
7553
+ }))
7554
+ });
7555
+ }).catch((err) => {
7556
+ res.writeHead(400, { "content-type": "application/json" });
7557
+ res.end(JSON.stringify({ error: String(err?.message ?? err) }));
7558
+ });
7491
7559
  return;
7492
7560
  }
7493
7561
  if (url.pathname === "/api/save") {