paseo-room 0.4.1 → 0.5.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.
Files changed (38) hide show
  1. package/README.md +122 -70
  2. package/dist/index.js +338 -217
  3. package/dist/index.js.map +1 -1
  4. package/dist/prompts/contract/lead.md +152 -0
  5. package/dist/prompts/contract/peer.md +62 -0
  6. package/dist/prompts/contract/shared-authority.md +33 -0
  7. package/dist/prompts/contract/shared-seat-identity.md +18 -0
  8. package/dist/prompts/contract/supervisor.md +89 -0
  9. package/dist/skills/paseo-project-onboarding/SKILL.md +122 -0
  10. package/dist/skills/paseo-project-onboarding/references/workspace-protocol-template.md +56 -0
  11. package/package.json +2 -2
  12. package/dist/prompts/contract/lead/complete-peer-brief.md +0 -32
  13. package/dist/prompts/contract/lead/independent-review.md +0 -9
  14. package/dist/prompts/contract/lead/moving-write-ownership.md +0 -13
  15. package/dist/prompts/contract/lead/peer-seat-lifecycle.md +0 -39
  16. package/dist/prompts/contract/lead/project-technical-ownership.md +0 -12
  17. package/dist/prompts/contract/lead/technical-acceptance.md +0 -6
  18. package/dist/prompts/contract/peer/assignment-scope.md +0 -10
  19. package/dist/prompts/contract/peer/bounded-outcome.md +0 -13
  20. package/dist/prompts/contract/peer/independent-judgment.md +0 -11
  21. package/dist/prompts/contract/peer/no-orchestration.md +0 -5
  22. package/dist/prompts/contract/peer/no-self-acceptance.md +0 -5
  23. package/dist/prompts/contract/peer/reproducible-handoff.md +0 -17
  24. package/dist/prompts/contract/shared/evidence-and-event-waiting.md +0 -7
  25. package/dist/prompts/contract/shared/human-authority.md +0 -8
  26. package/dist/prompts/contract/shared/scope-and-unrelated-work.md +0 -4
  27. package/dist/prompts/contract/shared-supervisor-lead/workspace-protocol-precedence.md +0 -18
  28. package/dist/prompts/contract/supervisor/directive-integrity.md +0 -5
  29. package/dist/prompts/contract/supervisor/escalation-boundaries.md +0 -5
  30. package/dist/prompts/contract/supervisor/lead-discovery-and-recovery.md +0 -52
  31. package/dist/prompts/contract/supervisor/observation-and-advice.md +0 -13
  32. package/dist/prompts/contract/supervisor/technical-non-interference.md +0 -8
  33. package/dist/prompts/documents/workspace.md +0 -6
  34. package/dist/prompts/workspace/repository-conventions.md +0 -7
  35. package/dist/prompts/workspace/review.md +0 -4
  36. package/dist/prompts/workspace/topology.md +0 -13
  37. package/dist/prompts/workspace/verification.md +0 -10
  38. /package/dist/prompts/contract/{shared-lead-peer/challenge-signals.md → challenge-signals.md} +0 -0
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ var init_package = __esm({
15
15
  "package.json"() {
16
16
  package_default = {
17
17
  name: "paseo-room",
18
- version: "0.4.1",
18
+ version: "0.5.0",
19
19
  description: "One CLI that configures Codex, Claude Code, and Pi role homes in $HOME and registers them with your local Paseo daemon.",
20
20
  keywords: [
21
21
  "paseo",
@@ -49,7 +49,7 @@ var init_package = __esm({
49
49
  lint: "eslint . --max-warnings 0",
50
50
  test: "vitest run",
51
51
  "test:package": "vitest run --config vitest.package.config.ts",
52
- build: `tsup && node --input-type=module -e "import { cpSync, rmSync } from 'node:fs'; for (const [source, target] of [['src/room/prompts', 'dist/prompts'], ['src/plugin-assets', 'dist/plugin-assets']]) { rmSync(target, { recursive: true, force: true }); cpSync(source, target, { recursive: true }); }"`,
52
+ build: `tsup && node --input-type=module -e "import { cpSync, rmSync } from 'node:fs'; for (const [source, target] of [['src/room/prompts', 'dist/prompts'], ['src/room/skills', 'dist/skills'], ['src/plugin-assets', 'dist/plugin-assets']]) { rmSync(target, { recursive: true, force: true }); cpSync(source, target, { recursive: true }); }"`,
53
53
  prepack: "npm run build",
54
54
  verify: "npm run typecheck && npm run lint && npm test && npm run build && npm run test:package"
55
55
  },
@@ -158,12 +158,6 @@ function preservedCredentialCheck(input) {
158
158
  function presentNames(names, supported) {
159
159
  return supported.filter((name) => names.has(name));
160
160
  }
161
- function configuredByNamesCheck(id, agent, role, names) {
162
- return warn(
163
- `${id}.configured-structurally`,
164
- `${agent} ${role} auth: configured structurally; ${names.join(", ")} ${names.length === 1 ? "is" : "are"} present by name. Values, token validity, and freshness were not checked.`
165
- );
166
- }
167
161
  function ambientNamesCheck(id, agent, role, names) {
168
162
  return warn(
169
163
  `${id}.ambient-auth-unverifiable`,
@@ -765,18 +759,11 @@ function validateHeadOrCapsule(source, kind) {
765
759
  }
766
760
  return trimmed;
767
761
  }
768
- function validateSection(source) {
769
- const trimmed = source.trimEnd();
770
- if (!trimmed.trim()) throw new Error("is empty.");
771
- const lines = trimmed.split("\n");
772
- const heading = lines.shift() ?? "";
773
- if (!/^## \S.*$/.test(heading) || headingCount(trimmed, 2) !== 1) {
774
- throw new Error("section must begin with exactly one H2 heading.");
775
- }
776
- if (lines.some((line) => /^#{1,6}\s/.test(line))) {
762
+ function renderSection(heading, bodyLines) {
763
+ if (bodyLines.some((line) => /^#{1,6}\s/.test(line))) {
777
764
  throw new Error("section must not contain additional Markdown headings.");
778
765
  }
779
- const body = lines.join("\n").trim();
766
+ const body = bodyLines.join("\n").trim();
780
767
  if (!body) throw new Error("section must contain at least one non-empty statement.");
781
768
  const statements = body.split(/\n\s*\n/).map(
782
769
  (statement) => statement.split("\n").map((line) => line.trim()).join(" ")
@@ -787,6 +774,40 @@ function validateSection(source) {
787
774
  return `${heading}
788
775
  ${statements.map((statement) => `- ${statement}`).join("\n")}`;
789
776
  }
777
+ function validateSection(source) {
778
+ const trimmed = source.trimEnd();
779
+ if (!trimmed.trim()) throw new Error("is empty.");
780
+ const lines = trimmed.split("\n");
781
+ const heading = lines.shift() ?? "";
782
+ if (!/^## \S.*$/.test(heading) || headingCount(trimmed, 2) !== 1) {
783
+ throw new Error("section must begin with exactly one H2 heading.");
784
+ }
785
+ return renderSection(heading, lines);
786
+ }
787
+ function splitSections(lines) {
788
+ const preface = [];
789
+ const sections = [];
790
+ for (const line of lines) {
791
+ if (/^## \S.*$/.test(line)) {
792
+ sections.push({ heading: line, lines: [] });
793
+ continue;
794
+ }
795
+ const current2 = sections.at(-1);
796
+ if (current2 === void 0) preface.push(line);
797
+ else current2.lines.push(line);
798
+ }
799
+ return { preface, sections };
800
+ }
801
+ function validateBody(source) {
802
+ const trimmed = source.trimEnd();
803
+ if (!trimmed.trim()) throw new Error("is empty.");
804
+ const { preface, sections } = splitSections(trimmed.split("\n"));
805
+ if (sections.length === 0) throw new Error("role body must contain at least one H2 section.");
806
+ if (preface.some((line) => line.trim())) {
807
+ throw new Error("role body must not contain content above its first H2 heading.");
808
+ }
809
+ return sections.map((section) => renderSection(section.heading, section.lines)).join("\n\n");
810
+ }
790
811
  function loadPromptAsset(group, key) {
791
812
  const logicalAsset = `${group}.${String(key)}`;
792
813
  const cached = cache.get(logicalAsset);
@@ -796,7 +817,7 @@ function loadPromptAsset(group, key) {
796
817
  try {
797
818
  const source = readFileSync(assetUrl, "utf8");
798
819
  if (source.includes("\uFFFD")) throw new Error("is not valid UTF-8 text.");
799
- const validated = asset.kind === "section" ? validateSection(source) : validateHeadOrCapsule(source, asset.kind);
820
+ const validated = validate(source, asset.kind);
800
821
  cache.set(logicalAsset, validated);
801
822
  return validated;
802
823
  } catch (error) {
@@ -805,6 +826,16 @@ function loadPromptAsset(group, key) {
805
826
  throw new PromptAssetError(logicalAsset, assetUrl, detail, { cause: error });
806
827
  }
807
828
  }
829
+ function validate(source, kind) {
830
+ switch (kind) {
831
+ case "section":
832
+ return validateSection(source);
833
+ case "body":
834
+ return validateBody(source);
835
+ default:
836
+ return validateHeadOrCapsule(source, kind);
837
+ }
838
+ }
808
839
  var PROMPT_ASSETS, PromptAssetError, cache;
809
840
  var init_prompts = __esm({
810
841
  "src/room/prompts.ts"() {
@@ -813,38 +844,15 @@ var init_prompts = __esm({
813
844
  documents: {
814
845
  supervisor: { path: "documents/supervisor.md", kind: "head" },
815
846
  lead: { path: "documents/lead.md", kind: "head" },
816
- peer: { path: "documents/peer.md", kind: "head" },
817
- workspace: { path: "documents/workspace.md", kind: "head" }
847
+ peer: { path: "documents/peer.md", kind: "head" }
818
848
  },
819
849
  contract: {
820
- humanAuthority: { path: "contract/shared/human-authority.md", kind: "section" },
821
- workspaceProtocolPrecedence: { path: "contract/shared-supervisor-lead/workspace-protocol-precedence.md", kind: "section" },
822
- evidenceAndEventWaiting: { path: "contract/shared/evidence-and-event-waiting.md", kind: "section" },
823
- scopeAndUnrelatedWork: { path: "contract/shared/scope-and-unrelated-work.md", kind: "section" },
824
- directiveIntegrity: { path: "contract/supervisor/directive-integrity.md", kind: "section" },
825
- technicalNonInterference: { path: "contract/supervisor/technical-non-interference.md", kind: "section" },
826
- leadDiscoveryAndRecovery: { path: "contract/supervisor/lead-discovery-and-recovery.md", kind: "section" },
827
- observationAndAdvice: { path: "contract/supervisor/observation-and-advice.md", kind: "section" },
828
- escalationBoundaries: { path: "contract/supervisor/escalation-boundaries.md", kind: "section" },
829
- projectTechnicalOwnership: { path: "contract/lead/project-technical-ownership.md", kind: "section" },
830
- movingWriteOwnership: { path: "contract/lead/moving-write-ownership.md", kind: "section" },
831
- completePeerBrief: { path: "contract/lead/complete-peer-brief.md", kind: "section" },
832
- challengeSignals: { path: "contract/shared-lead-peer/challenge-signals.md", kind: "section" },
833
- technicalAcceptance: { path: "contract/lead/technical-acceptance.md", kind: "section" },
834
- independentReview: { path: "contract/lead/independent-review.md", kind: "section" },
835
- peerSeatLifecycle: { path: "contract/lead/peer-seat-lifecycle.md", kind: "section" },
836
- boundedOutcome: { path: "contract/peer/bounded-outcome.md", kind: "section" },
837
- independentJudgment: { path: "contract/peer/independent-judgment.md", kind: "section" },
838
- assignmentScope: { path: "contract/peer/assignment-scope.md", kind: "section" },
839
- noOrchestration: { path: "contract/peer/no-orchestration.md", kind: "section" },
840
- reproducibleHandoff: { path: "contract/peer/reproducible-handoff.md", kind: "section" },
841
- noSelfAcceptance: { path: "contract/peer/no-self-acceptance.md", kind: "section" }
842
- },
843
- workspace: {
844
- topology: { path: "workspace/topology.md", kind: "section" },
845
- verification: { path: "workspace/verification.md", kind: "section" },
846
- review: { path: "workspace/review.md", kind: "section" },
847
- repositoryConventions: { path: "workspace/repository-conventions.md", kind: "section" }
850
+ sharedAuthority: { path: "contract/shared-authority.md", kind: "body" },
851
+ sharedSeatIdentity: { path: "contract/shared-seat-identity.md", kind: "section" },
852
+ challengeSignals: { path: "contract/challenge-signals.md", kind: "section" },
853
+ supervisor: { path: "contract/supervisor.md", kind: "body" },
854
+ lead: { path: "contract/lead.md", kind: "body" },
855
+ peer: { path: "contract/peer.md", kind: "body" }
848
856
  },
849
857
  pi: {
850
858
  communicationStyle: { path: "pi/communication-style.md", kind: "capsule" },
@@ -868,24 +876,12 @@ var init_prompts = __esm({
868
876
  // src/room/instructions.ts
869
877
  import { createHash } from "crypto";
870
878
  function instructionKeys(role) {
871
- return [...SHARED_KEYS[role], ...ROLE_KEYS[role]];
872
- }
873
- function protocolKeys(role) {
874
- return PROTOCOL_KEYS[role];
875
- }
876
- function protocol(keys) {
877
- if (keys.length === 0) return [];
878
- return [
879
- loadPromptAsset("documents", "workspace"),
880
- ...keys.map((key) => loadPromptAsset("workspace", key))
881
- ];
879
+ return [...SHARED_KEYS[role], ROLE_KEYS[role]];
882
880
  }
883
881
  function renderInstructions(kind) {
884
- if (kind === "workspace") return protocol(ALL_PROTOCOL_KEYS).join("\n\n") + "\n";
885
882
  return [
886
883
  loadPromptAsset("documents", kind),
887
- ...instructionKeys(kind).map((key) => loadPromptAsset("contract", key)),
888
- ...protocol(protocolKeys(kind))
884
+ ...instructionKeys(kind).map((key) => loadPromptAsset("contract", key))
889
885
  ].join("\n\n") + "\n";
890
886
  }
891
887
  function contractDigest() {
@@ -893,68 +889,23 @@ function contractDigest() {
893
889
  for (const kind of INSTRUCTION_KINDS) hash.update(`${kind}\0${renderInstructions(kind)}\0`);
894
890
  return `sha256:${hash.digest("hex").slice(0, 16)}`;
895
891
  }
896
- var AUTHORITY_KEYS, PROTOCOL_READER_KEYS, SHARED_KEYS, ROLE_KEYS, ALL_PROTOCOL_KEYS, PROTOCOL_KEYS, INSTRUCTION_KINDS;
892
+ var SHARED_KEYS, ROLE_KEYS, INSTRUCTION_KINDS;
897
893
  var init_instructions = __esm({
898
894
  "src/room/instructions.ts"() {
899
895
  "use strict";
900
896
  init_roles();
901
897
  init_prompts();
902
- AUTHORITY_KEYS = [
903
- "humanAuthority",
904
- "evidenceAndEventWaiting",
905
- "scopeAndUnrelatedWork"
906
- ];
907
- PROTOCOL_READER_KEYS = [
908
- "humanAuthority",
909
- "workspaceProtocolPrecedence",
910
- "evidenceAndEventWaiting",
911
- "scopeAndUnrelatedWork"
912
- ];
913
898
  SHARED_KEYS = {
914
- supervisor: PROTOCOL_READER_KEYS,
915
- lead: PROTOCOL_READER_KEYS,
916
- peer: AUTHORITY_KEYS
899
+ supervisor: ["sharedAuthority", "sharedSeatIdentity"],
900
+ lead: ["sharedAuthority", "sharedSeatIdentity", "challengeSignals"],
901
+ peer: ["sharedAuthority", "challengeSignals"]
917
902
  };
918
903
  ROLE_KEYS = {
919
- supervisor: [
920
- "directiveIntegrity",
921
- "technicalNonInterference",
922
- "leadDiscoveryAndRecovery",
923
- "observationAndAdvice",
924
- "escalationBoundaries"
925
- ],
926
- lead: [
927
- "projectTechnicalOwnership",
928
- "movingWriteOwnership",
929
- "completePeerBrief",
930
- "challengeSignals",
931
- "technicalAcceptance",
932
- "independentReview",
933
- "peerSeatLifecycle"
934
- ],
935
- // Peer needs Challenge Signals as well as its own obligations.
936
- peer: [
937
- "challengeSignals",
938
- "boundedOutcome",
939
- "independentJudgment",
940
- "assignmentScope",
941
- "noOrchestration",
942
- "reproducibleHandoff",
943
- "noSelfAcceptance"
944
- ]
945
- };
946
- ALL_PROTOCOL_KEYS = [
947
- "topology",
948
- "verification",
949
- "review",
950
- "repositoryConventions"
951
- ];
952
- PROTOCOL_KEYS = {
953
- supervisor: ALL_PROTOCOL_KEYS,
954
- lead: ALL_PROTOCOL_KEYS,
955
- peer: []
904
+ supervisor: "supervisor",
905
+ lead: "lead",
906
+ peer: "peer"
956
907
  };
957
- INSTRUCTION_KINDS = [...ROLES, "workspace"];
908
+ INSTRUCTION_KINDS = [...ROLES];
958
909
  }
959
910
  });
960
911
 
@@ -1040,14 +991,39 @@ function isPaseoSkill(name) {
1040
991
  function receivesExecutableResources(role) {
1041
992
  return role !== "peer";
1042
993
  }
1043
- async function skillNames(path, reserved) {
994
+ function receivesRoomSkill(role) {
995
+ return role === "lead";
996
+ }
997
+ async function skillNames(path, reserved, keep) {
1044
998
  try {
1045
- return (await readdir2(path)).filter((name) => !isPaseoSkill(name) && !reserved.has(name)).sort();
999
+ return (await readdir2(path)).filter((name) => keep(name) && !reserved.has(name.toLowerCase())).sort();
1046
1000
  } catch (error) {
1047
1001
  if (error !== null && typeof error === "object" && "code" in error && error.code === "ENOENT") return [];
1048
1002
  throw error;
1049
1003
  }
1050
1004
  }
1005
+ async function skillsProjection(input, path, alias, present, reserved) {
1006
+ const roomSkill = receivesRoomSkill(input.role) ? input.roomSkill : void 0;
1007
+ const projectsChildren = !receivesExecutableResources(input.role) || roomSkill !== void 0;
1008
+ if (!projectsChildren) return void 0;
1009
+ const reservedNames = new Set([...reserved].map((name) => name.toLowerCase()));
1010
+ const keep = roomSkill === void 0 ? (name) => !isPaseoSkill(name) : (name) => name.toLowerCase() !== roomSkill.name.toLowerCase();
1011
+ const operator = present.has(path) ? await skillNames(path, reservedNames, keep) : [];
1012
+ const children = roomSkill === void 0 ? operator : [...operator, roomSkill.name];
1013
+ const projection = input.role === "lead" ? input.leadSkillProjection : alias;
1014
+ return [
1015
+ {
1016
+ kind: "managed-dir",
1017
+ path: projection,
1018
+ children,
1019
+ ...input.role === "peer" ? { legacyLink: path } : {},
1020
+ ...reserved.size === 0 ? {} : { reserved: [...reserved] }
1021
+ },
1022
+ ...operator.map((child) => ({ kind: "link", path: join6(projection, child), target: join6(path, child) })),
1023
+ ...roomSkill === void 0 ? [] : [{ kind: "link", path: join6(projection, roomSkill.name), target: roomSkill.source }],
1024
+ ...input.role === "lead" ? [{ kind: "link", path: alias, target: projection }] : []
1025
+ ];
1026
+ }
1051
1027
  async function roleResourceEntries(input) {
1052
1028
  const present = new Set(input.shared);
1053
1029
  const executable = new Set(input.executable);
@@ -1056,21 +1032,14 @@ async function roleResourceEntries(input) {
1056
1032
  for (const name of input.names) {
1057
1033
  const path = join6(input.home, name);
1058
1034
  const alias = join6(input.target, name);
1059
- if (!receivesExecutableResources(input.role)) {
1060
- if (executable.has(name)) continue;
1061
- if (name === SKILLS) {
1062
- const children = present.has(path) ? await skillNames(path, reserved) : [];
1063
- entries.push({
1064
- kind: "managed-dir",
1065
- path: alias,
1066
- children,
1067
- legacyLink: path,
1068
- ...reserved.size === 0 ? {} : { reserved: [...reserved] }
1069
- });
1070
- for (const child of children) entries.push({ kind: "link", path: join6(alias, child), target: join6(path, child) });
1035
+ if (name === SKILLS) {
1036
+ const projection = await skillsProjection(input, path, alias, present, reserved);
1037
+ if (projection !== void 0) {
1038
+ entries.push(...projection);
1071
1039
  continue;
1072
1040
  }
1073
1041
  }
1042
+ if (!receivesExecutableResources(input.role) && executable.has(name)) continue;
1074
1043
  if (!present.has(path)) continue;
1075
1044
  entries.push({ kind: "link", path: alias, target: path });
1076
1045
  }
@@ -1084,9 +1053,99 @@ var init_resources = __esm({
1084
1053
  }
1085
1054
  });
1086
1055
 
1056
+ // src/room/skills.ts
1057
+ import { readFileSync as readFileSync2 } from "fs";
1058
+ import { join as join7 } from "path";
1059
+ function validateSkillAsset(path, source) {
1060
+ if (source.includes("\uFFFD")) throw new Error("is not valid UTF-8 text.");
1061
+ if (!source.trim()) throw new Error("is empty.");
1062
+ if (path === "SKILL.md") {
1063
+ const frontmatter = /^---\n([\s\S]*?)\n---\n/.exec(source)?.[1];
1064
+ if (frontmatter === void 0) throw new Error("must begin with YAML frontmatter.");
1065
+ const declaredName = /^name: (.+)$/m.exec(frontmatter)?.[1];
1066
+ if (declaredName !== ROOM_SKILL_NAME) {
1067
+ throw new Error(`frontmatter name must be ${ROOM_SKILL_NAME}.`);
1068
+ }
1069
+ if (!/^description: \S.+$/m.test(frontmatter)) {
1070
+ throw new Error("frontmatter must contain a non-empty single-line description.");
1071
+ }
1072
+ }
1073
+ if (path === "references/workspace-protocol-template.md" && !source.startsWith("# Workspace protocol template (scaffold)\n")) {
1074
+ throw new Error("must begin with the workspace protocol scaffold heading.");
1075
+ }
1076
+ return source;
1077
+ }
1078
+ function loadSkillAsset(path) {
1079
+ const cached = cache2.get(path);
1080
+ if (cached !== void 0) return cached;
1081
+ const logicalAsset = `${ROOM_SKILL_NAME}/${path}`;
1082
+ const assetUrl = new URL(`./skills/${ROOM_SKILL_NAME}/${path}`, import.meta.url);
1083
+ try {
1084
+ const validated = validateSkillAsset(path, readFileSync2(assetUrl, "utf8"));
1085
+ cache2.set(path, validated);
1086
+ return validated;
1087
+ } catch (error) {
1088
+ if (error instanceof SkillAssetError) throw error;
1089
+ const detail = error instanceof Error ? error.message : "could not be read.";
1090
+ throw new SkillAssetError(logicalAsset, assetUrl, detail, { cause: error });
1091
+ }
1092
+ }
1093
+ function roomSkillSource(layout) {
1094
+ return join7(sharedRoom(layout), "skills", ROOM_SKILL_NAME);
1095
+ }
1096
+ function skillProjectionRoot(layout) {
1097
+ return join7(sharedRoom(layout), "skill-projections");
1098
+ }
1099
+ function agentSkillProjectionRoot(layout, agent) {
1100
+ return join7(skillProjectionRoot(layout), agent);
1101
+ }
1102
+ function leadSkillProjection(layout, agent) {
1103
+ return join7(agentSkillProjectionRoot(layout, agent), "lead");
1104
+ }
1105
+ function roomSkillEntries(layout, agents) {
1106
+ const root = roomSkillSource(layout);
1107
+ return [
1108
+ { kind: "dir", path: join7(sharedRoom(layout), "skills") },
1109
+ { kind: "dir", path: skillProjectionRoot(layout) },
1110
+ ...agents.map((agent) => ({ kind: "dir", path: agentSkillProjectionRoot(layout, agent) })),
1111
+ { kind: "managed-dir", path: root, children: ["SKILL.md", ...SKILL_DIRECTORIES] },
1112
+ {
1113
+ kind: "managed-dir",
1114
+ path: join7(root, "references"),
1115
+ children: SKILL_FILES.filter((path) => path.startsWith("references/")).map((path) => path.slice("references/".length))
1116
+ },
1117
+ ...SKILL_FILES.map((path) => ({
1118
+ kind: "file",
1119
+ path: join7(root, path),
1120
+ content: loadSkillAsset(path)
1121
+ }))
1122
+ ];
1123
+ }
1124
+ var ROOM_SKILL_NAME, SKILL_DIRECTORIES, SKILL_FILES, SkillAssetError, cache2;
1125
+ var init_skills = __esm({
1126
+ "src/room/skills.ts"() {
1127
+ "use strict";
1128
+ init_layout();
1129
+ ROOM_SKILL_NAME = "paseo-project-onboarding";
1130
+ SKILL_DIRECTORIES = ["references"];
1131
+ SKILL_FILES = ["SKILL.md", "references/workspace-protocol-template.md"];
1132
+ SkillAssetError = class extends Error {
1133
+ logicalAsset;
1134
+ assetUrl;
1135
+ constructor(logicalAsset, assetUrl, detail, options) {
1136
+ super(`Skill asset ${logicalAsset} at ${assetUrl.pathname} ${detail} Reinstall paseo-room and try again.`, options);
1137
+ this.name = "SkillAssetError";
1138
+ this.logicalAsset = logicalAsset;
1139
+ this.assetUrl = assetUrl;
1140
+ }
1141
+ };
1142
+ cache2 = /* @__PURE__ */ new Map();
1143
+ }
1144
+ });
1145
+
1087
1146
  // src/agents/claude.ts
1088
1147
  import { readFile as readFile2 } from "fs/promises";
1089
- import { join as join7 } from "path";
1148
+ import { join as join8 } from "path";
1090
1149
  function asObject(value) {
1091
1150
  return value !== null && typeof value === "object" && !Array.isArray(value) ? { ...value } : {};
1092
1151
  }
@@ -1105,17 +1164,21 @@ async function readStateIfPresent(path) {
1105
1164
  throw error;
1106
1165
  }
1107
1166
  }
1108
- function renderRoleSettings(source, role, secureStorageDir) {
1109
- const settings = readObject(source);
1110
- settings.env = {
1111
- ...asObject(settings.env),
1112
- ...CONTROL_PLANE_ENV,
1113
- PASEO_ROOM_ROLE: role,
1114
- ...secureStorageDir === void 0 ? {} : { [SECURE_STORAGE_ENV]: secureStorageDir }
1167
+ function renderRoleSettings(role, secureStorageDir) {
1168
+ const settings = {
1169
+ // Claude applies settings.env after the launch environment, so repeat provider closures here.
1170
+ env: {
1171
+ ...CONTROL_PLANE_ENV,
1172
+ PASEO_ROOM_ROLE: role,
1173
+ ...secureStorageDir === void 0 ? {} : { [SECURE_STORAGE_ENV]: secureStorageDir }
1174
+ },
1175
+ // Provider disallowedTools is not sufficient alone; the isolated home carries the same list.
1176
+ permissions: { deny: DISALLOWED_TOOLS },
1177
+ // These restrictive settings cannot be weakened by another settings scope.
1178
+ disableAgentView: true,
1179
+ disableWorkflows: true,
1180
+ crossSessionInbound: "refuse"
1115
1181
  };
1116
- settings.disableAgentView = true;
1117
- settings.disableWorkflows = true;
1118
- settings.crossSessionInbound = "refuse";
1119
1182
  return JSON.stringify(settings, null, 2) + "\n";
1120
1183
  }
1121
1184
  function renderRoleState(source) {
@@ -1132,17 +1195,9 @@ function renderRoleMemory(source, role, contract = true) {
1132
1195
 
1133
1196
  ${renderInstructions(role)}` : renderInstructions(role);
1134
1197
  }
1135
- function claudeAuthMethodNames(settingsSource, envNames) {
1136
- const settings = readObject(settingsSource);
1137
- const settingsEnv = asObject(settings.env);
1138
- const names = new Set(presentNames(envNames, AUTH_ENV));
1139
- for (const name of AUTH_ENV) if (Object.hasOwn(settingsEnv, name)) names.add(name);
1140
- if (Object.hasOwn(settings, "apiKeyHelper")) names.add("apiKeyHelper");
1141
- return [...names];
1142
- }
1143
- async function claudeCredentialDiagnostic(layout, role, settingsSource, platform = process.platform, binary = "claude") {
1198
+ async function claudeCredentialDiagnostic(layout, role, platform = process.platform, binary = "claude") {
1144
1199
  const home = roleHome(layout, "claude", role);
1145
- const path = join7(home, ".credentials.json");
1200
+ const path = join8(home, ".credentials.json");
1146
1201
  const roleEnvironment = { CLAUDE_CONFIG_DIR: home, [SECURE_STORAGE_ENV]: home };
1147
1202
  const login = roleCommand(roleEnvironment, binary, ["auth", "login"]);
1148
1203
  const status = roleCommand(roleEnvironment, binary, ["auth", "status"]);
@@ -1151,8 +1206,6 @@ async function claudeCredentialDiagnostic(layout, role, settingsSource, platform
1151
1206
  if (state.kind !== "missing") {
1152
1207
  return { path, checks: [preservedCredentialCheck({ id, agent: "Claude", role, path, state, login, status })] };
1153
1208
  }
1154
- const configured = claudeAuthMethodNames(settingsSource, /* @__PURE__ */ new Set());
1155
- if (configured.length > 0) return { path, checks: [configuredByNamesCheck(id, "Claude", role, configured)] };
1156
1209
  const ambient = presentNames(layout.envNames, AUTH_ENV);
1157
1210
  if (ambient.length > 0) return { path, checks: [ambientNamesCheck(id, "Claude", role, ambient)] };
1158
1211
  if (platform === "darwin") {
@@ -1166,7 +1219,7 @@ async function claudeCredentialDiagnostic(layout, role, settingsSource, platform
1166
1219
  return { path, checks: [{
1167
1220
  id: `${id}.login-required`,
1168
1221
  status: "warn",
1169
- message: `Claude ${role} auth: login-required; no role-owned .credentials.json or configured environment/static auth method was detected. Authentication was not attempted.`,
1222
+ message: `Claude ${role} auth: login-required; no role-owned .credentials.json or supported ambient auth environment name was detected. Authentication was not attempted.`,
1170
1223
  fix: `Authenticate this role with: ${login}. If that subcommand is unavailable, launch ${roleCommand(roleEnvironment, binary)} and run /login. Check status with: ${status}.`
1171
1224
  }] };
1172
1225
  }
@@ -1181,7 +1234,7 @@ function mcpDriftCheck(role, home, path, divergence) {
1181
1234
  `If this role should have the same servers, add them with: CLAUDE_CONFIG_DIR=${home} claude mcp add ... \u2014 or delete ${path} and run setup --apply to reseed it from your current Claude state, which discards that role's other runtime state.`
1182
1235
  );
1183
1236
  }
1184
- var SHARED, EXECUTABLE, RUNTIME_SKILLS, MCP_KEY, AUTH_ENV, CONTROL_PLANE_ENV, SECURE_STORAGE_ENV, SEEDED_KEYS, claudeAgent;
1237
+ var SHARED, EXECUTABLE, RUNTIME_SKILLS, MCP_KEY, AUTH_ENV, CONTROL_PLANE_ENV, DISALLOWED_TOOLS, SECURE_STORAGE_ENV, SEEDED_KEYS, claudeAgent;
1185
1238
  var init_claude = __esm({
1186
1239
  "src/agents/claude.ts"() {
1187
1240
  "use strict";
@@ -1193,6 +1246,7 @@ var init_claude = __esm({
1193
1246
  init_which();
1194
1247
  init_mcp();
1195
1248
  init_resources();
1249
+ init_skills();
1196
1250
  SHARED = [
1197
1251
  "skills",
1198
1252
  "plugins",
@@ -1218,6 +1272,22 @@ var init_claude = __esm({
1218
1272
  CLAUDE_CODE_DISABLE_AGENT_VIEW: "1",
1219
1273
  CLAUDE_CODE_DISABLE_WORKFLOWS: "1"
1220
1274
  };
1275
+ DISALLOWED_TOOLS = [
1276
+ "Task",
1277
+ "Agent",
1278
+ "Workflow",
1279
+ "ListAgents",
1280
+ "SendMessage",
1281
+ "TeamCreate",
1282
+ "TeamDelete",
1283
+ "TaskCreate",
1284
+ "TaskGet",
1285
+ "TaskList",
1286
+ "TaskUpdate",
1287
+ "CronCreate",
1288
+ "CronDelete",
1289
+ "CronList"
1290
+ ];
1221
1291
  SECURE_STORAGE_ENV = "CLAUDE_SECURESTORAGE_CONFIG_DIR";
1222
1292
  SEEDED_KEYS = ["hasCompletedOnboarding", "theme", "installMethod", "userID", MCP_KEY];
1223
1293
  claudeAgent = {
@@ -1230,24 +1300,7 @@ var init_claude = __esm({
1230
1300
  // Keep the legacy Task name and block current native orchestration,
1231
1301
  // shared task/cron coordination, and cross-session paths. Paseo alone owns
1232
1302
  // agent lifecycle and coordination.
1233
- pins: {
1234
- disallowedTools: [
1235
- "Task",
1236
- "Agent",
1237
- "Workflow",
1238
- "ListAgents",
1239
- "SendMessage",
1240
- "TeamCreate",
1241
- "TeamDelete",
1242
- "TaskCreate",
1243
- "TaskGet",
1244
- "TaskList",
1245
- "TaskUpdate",
1246
- "CronCreate",
1247
- "CronDelete",
1248
- "CronList"
1249
- ]
1250
- },
1303
+ pins: { disallowedTools: DISALLOWED_TOOLS },
1251
1304
  async build(layout, roles, options = {}) {
1252
1305
  const home = layout.agentHome.claude;
1253
1306
  const binary = await which(layout.bin.claude, layout.searchPath);
@@ -1258,8 +1311,7 @@ var init_claude = __esm({
1258
1311
  return { entries: [], checks: [fail("claude.home", `No Claude config directory at ${home}.`, "Run claude once to initialise it, or pass --claude-home.")] };
1259
1312
  }
1260
1313
  const checks = [pass("claude.home", `Claude Code found at ${binary} using ${home}.`)];
1261
- const settingsSource = await readIfPresent(join7(home, "settings.json"));
1262
- const memorySource = await readIfPresent(join7(home, "CLAUDE.md"));
1314
+ const memorySource = await readIfPresent(join8(home, "CLAUDE.md"));
1263
1315
  let stateSource;
1264
1316
  let operatorServers;
1265
1317
  try {
@@ -1279,9 +1331,10 @@ var init_claude = __esm({
1279
1331
  const credentials = [];
1280
1332
  const providerEnv = {};
1281
1333
  const drift = [];
1334
+ const roomSkill = { name: ROOM_SKILL_NAME, source: roomSkillSource(layout) };
1282
1335
  for (const role of roles) {
1283
1336
  const target = roleHome(layout, "claude", role);
1284
- const statePath = join7(target, ".claude.json");
1337
+ const statePath = join8(target, ".claude.json");
1285
1338
  let roleStateSource;
1286
1339
  let roleServers;
1287
1340
  try {
@@ -1301,13 +1354,23 @@ var init_claude = __esm({
1301
1354
  if (divergence) drift.push(mcpDriftCheck(role, target, statePath, divergence));
1302
1355
  }
1303
1356
  entries.push({ kind: "dir", path: target });
1304
- const memoryPath = join7(target, "CLAUDE.md");
1357
+ const memoryPath = join8(target, "CLAUDE.md");
1305
1358
  const memory = renderRoleMemory(memorySource, role, options.memoryContract ?? true);
1306
1359
  entries.push(memory === void 0 ? { kind: "absent", path: memoryPath } : { kind: "file", path: memoryPath, content: memory });
1307
- entries.push({ kind: "file", path: join7(target, "settings.json"), content: renderRoleSettings(settingsSource, role, target) });
1360
+ entries.push({ kind: "file", path: join8(target, "settings.json"), content: renderRoleSettings(role, target) });
1308
1361
  entries.push({ kind: "file", path: statePath, content: renderRoleState(stateSource), once: true });
1309
- entries.push(...await roleResourceEntries({ role, target, home, names: SHARED, shared, executable: EXECUTABLE, reservedSkills: RUNTIME_SKILLS }));
1310
- credentials.push(await claudeCredentialDiagnostic(layout, role, settingsSource, process.platform, binary));
1362
+ entries.push(...await roleResourceEntries({
1363
+ role,
1364
+ target,
1365
+ home,
1366
+ names: SHARED,
1367
+ shared,
1368
+ executable: EXECUTABLE,
1369
+ reservedSkills: RUNTIME_SKILLS,
1370
+ roomSkill,
1371
+ leadSkillProjection: leadSkillProjection(layout, "claude")
1372
+ }));
1373
+ credentials.push(await claudeCredentialDiagnostic(layout, role, process.platform, binary));
1311
1374
  providerEnv[role] = { [SECURE_STORAGE_ENV]: target };
1312
1375
  }
1313
1376
  return { entries, credentials, checks: [...checks, ...drift], binary, providerEnv };
@@ -1317,7 +1380,7 @@ var init_claude = __esm({
1317
1380
  });
1318
1381
 
1319
1382
  // src/agents/codex.ts
1320
- import { join as join8 } from "path";
1383
+ import { join as join9 } from "path";
1321
1384
  import { parse, stringify } from "smol-toml";
1322
1385
  function table(parent, key) {
1323
1386
  const value = parent[key];
@@ -1374,7 +1437,7 @@ function codexCredentialStore(config) {
1374
1437
  }
1375
1438
  async function credentialDiagnostic(layout, role, store, binary) {
1376
1439
  const home = roleHome(layout, "codex", role);
1377
- const path = join8(home, "auth.json");
1440
+ const path = join9(home, "auth.json");
1378
1441
  const login = roleCommand({ CODEX_HOME: home }, binary, ["login"]);
1379
1442
  const status = roleCommand({ CODEX_HOME: home }, binary, ["login", "status"]);
1380
1443
  const state = await inspectCredentialPath(path);
@@ -1423,6 +1486,7 @@ var init_codex = __esm({
1423
1486
  init_which();
1424
1487
  init_mcp();
1425
1488
  init_resources();
1489
+ init_skills();
1426
1490
  SHARED2 = ["AGENTS.md", "skills", "plugins", "hooks.json"];
1427
1491
  EXECUTABLE2 = ["plugins", "hooks.json"];
1428
1492
  CATALOG = "model-catalog.json";
@@ -1441,7 +1505,7 @@ var init_codex = __esm({
1441
1505
  if (!binary) {
1442
1506
  return { entries: [], checks: [fail("codex.bin", "Codex executable not found.", "Install Codex, or pass --codex-bin /path/to/codex.")] };
1443
1507
  }
1444
- const configPath = join8(home, "config.toml");
1508
+ const configPath = join9(home, "config.toml");
1445
1509
  const raw = await readIfPresent(configPath);
1446
1510
  if (raw === void 0) {
1447
1511
  return { entries: [], checks: [fail("codex.home", `No config.toml in ${home}.`, "Run codex once to initialise it, or pass --codex-home.")] };
@@ -1489,19 +1553,29 @@ var init_codex = __esm({
1489
1553
  }
1490
1554
  checks.push(pass("codex.catalog", evidence2.markers === 0 ? `Codex model catalog captured from ${capture}; it declared no multi_agent_version field, and each role home gets that generated copy instead of Codex's built-in catalog.` : `Codex model catalog captured from ${capture}; ${String(evidence2.markers)} multi_agent_version field(s) are nulled in the generated copy in each role home.`));
1491
1555
  const shared = await existingPaths(home, SHARED2);
1556
+ const roomSkill = { name: ROOM_SKILL_NAME, source: roomSkillSource(layout) };
1492
1557
  for (const role of roles) {
1493
1558
  const target = roleHome(layout, "codex", role);
1494
- const catalogPath = join8(target, CATALOG);
1559
+ const catalogPath = join9(target, CATALOG);
1495
1560
  const roleDocument = renderInstructions(role);
1496
1561
  entries.push({ kind: "dir", path: target });
1497
- entries.push({ kind: "file", path: join8(target, "role-instructions.md"), content: roleDocument });
1562
+ entries.push({ kind: "file", path: join9(target, "role-instructions.md"), content: roleDocument });
1498
1563
  entries.push({
1499
1564
  kind: "file",
1500
- path: join8(target, "config.toml"),
1565
+ path: join9(target, "config.toml"),
1501
1566
  content: renderRoleConfig(source, { roleDocument, catalogPath })
1502
1567
  });
1503
1568
  entries.push({ kind: "file", path: catalogPath, content: catalogSource });
1504
- entries.push(...await roleResourceEntries({ role, target, home, names: SHARED2, shared, executable: EXECUTABLE2 }));
1569
+ entries.push(...await roleResourceEntries({
1570
+ role,
1571
+ target,
1572
+ home,
1573
+ names: SHARED2,
1574
+ shared,
1575
+ executable: EXECUTABLE2,
1576
+ roomSkill,
1577
+ leadSkillProjection: leadSkillProjection(layout, "codex")
1578
+ }));
1505
1579
  credentials.push(await credentialDiagnostic(layout, role, store, binary));
1506
1580
  }
1507
1581
  return { entries, credentials, checks, binary };
@@ -1514,7 +1588,7 @@ var init_codex = __esm({
1514
1588
  import { spawn as spawn2 } from "child_process";
1515
1589
  import { mkdtemp, readFile as readFile3, realpath, rm as rm2, stat } from "fs/promises";
1516
1590
  import { tmpdir } from "os";
1517
- import { isAbsolute as isAbsolute2, join as join9, resolve as resolve3 } from "path";
1591
+ import { isAbsolute as isAbsolute2, join as join10, resolve as resolve3 } from "path";
1518
1592
  function asObject2(value) {
1519
1593
  return value !== null && typeof value === "object" && !Array.isArray(value) ? { ...value } : void 0;
1520
1594
  }
@@ -1538,12 +1612,12 @@ function adapterFailure(home, detail) {
1538
1612
  return fail(
1539
1613
  "pi.adapter",
1540
1614
  detail,
1541
- `Install ${PACKAGE_NAME} in ${join9(home, "npm")} with Pi yourself, then run setup again.`
1615
+ `Install ${PACKAGE_NAME} in ${join10(home, "npm")} with Pi yourself, then run setup again.`
1542
1616
  );
1543
1617
  }
1544
1618
  async function resolvePiAdapter(home) {
1545
- const packageRoot = join9(home, "npm", "node_modules", PACKAGE_NAME);
1546
- const manifestPath = join9(packageRoot, "package.json");
1619
+ const packageRoot = join10(home, "npm", "node_modules", PACKAGE_NAME);
1620
+ const manifestPath = join10(packageRoot, "package.json");
1547
1621
  try {
1548
1622
  const [canonicalRoot, manifestStat, raw] = await Promise.all([
1549
1623
  realpath(packageRoot),
@@ -1651,7 +1725,7 @@ function runProbeProcess(executable, adapterEntry, env, options, cwd) {
1651
1725
  });
1652
1726
  }
1653
1727
  async function runProbe(executable, adapterEntry, env, options) {
1654
- const createdCwd = await mkdtemp(join9(tmpdir(), "paseo-room-pi-probe-"));
1728
+ const createdCwd = await mkdtemp(join10(tmpdir(), "paseo-room-pi-probe-"));
1655
1729
  const cwd = await realpath(createdCwd);
1656
1730
  try {
1657
1731
  return await runProbeProcess(executable, adapterEntry, {
@@ -1728,7 +1802,7 @@ async function readPiOptional(path) {
1728
1802
  }
1729
1803
  async function piCredentialDiagnostic(layout, role, binary = "pi") {
1730
1804
  const home = roleHome(layout, "pi", role);
1731
- const path = join9(home, "auth.json");
1805
+ const path = join10(home, "auth.json");
1732
1806
  const launch = piLoginCommand(home, binary);
1733
1807
  const state = await inspectCredentialPath(path);
1734
1808
  const id = `pi.auth.${role}`;
@@ -1768,6 +1842,7 @@ var init_pi = __esm({
1768
1842
  init_which();
1769
1843
  init_mcp();
1770
1844
  init_resources();
1845
+ init_skills();
1771
1846
  PACKAGE_NAME = "pi-mcp-adapter";
1772
1847
  PROBE_ID = "paseo-room-pi-mcp-probe";
1773
1848
  PROBE_TIMEOUT_MS = 1e4;
@@ -1824,7 +1899,7 @@ var init_pi = __esm({
1824
1899
  PI_OFFLINE: "1"
1825
1900
  });
1826
1901
  if (capability.status === "fail") return { entries: [], checks: [resolved.check, capability] };
1827
- const settingsPath = join9(home, "settings.json");
1902
+ const settingsPath = join10(home, "settings.json");
1828
1903
  let settings;
1829
1904
  try {
1830
1905
  settings = renderPiSettings(await readPiOptional(settingsPath));
@@ -1842,7 +1917,7 @@ var init_pi = __esm({
1842
1917
  ]
1843
1918
  };
1844
1919
  }
1845
- const operatorAppendPath = join9(home, "APPEND_SYSTEM.md");
1920
+ const operatorAppendPath = join10(home, "APPEND_SYSTEM.md");
1846
1921
  let operatorAppend;
1847
1922
  try {
1848
1923
  operatorAppend = await readPiOptional(operatorAppendPath);
@@ -1861,7 +1936,7 @@ var init_pi = __esm({
1861
1936
  };
1862
1937
  }
1863
1938
  const shared = await existingPaths(home, SHARED3);
1864
- const mcpPath = join9(home, MCP_FILE);
1939
+ const mcpPath = join10(home, MCP_FILE);
1865
1940
  let servers;
1866
1941
  try {
1867
1942
  servers = jsonServerTable(await readPiOptional(mcpPath), MCP_KEYS);
@@ -1884,13 +1959,23 @@ var init_pi = __esm({
1884
1959
  const entries = [];
1885
1960
  const credentials = [];
1886
1961
  const argv = {};
1962
+ const roomSkill = { name: ROOM_SKILL_NAME, source: roomSkillSource(layout) };
1887
1963
  for (const role of roles) {
1888
1964
  const target = roleHome(layout, "pi", role);
1889
- const appendPath = join9(target, "APPEND_SYSTEM.md");
1965
+ const appendPath = join10(target, "APPEND_SYSTEM.md");
1890
1966
  entries.push({ kind: "dir", path: target });
1891
- entries.push({ kind: "file", path: join9(target, "settings.json"), content: settings });
1967
+ entries.push({ kind: "file", path: join10(target, "settings.json"), content: settings });
1892
1968
  entries.push({ kind: "file", path: appendPath, content: renderPiAppend(operatorAppend, role) });
1893
- entries.push(...await roleResourceEntries({ role, target, home, names: SHARED3, shared, executable: EXECUTABLE3 }));
1969
+ entries.push(...await roleResourceEntries({
1970
+ role,
1971
+ target,
1972
+ home,
1973
+ names: SHARED3,
1974
+ shared,
1975
+ executable: EXECUTABLE3,
1976
+ roomSkill,
1977
+ leadSkillProjection: leadSkillProjection(layout, "pi")
1978
+ }));
1894
1979
  credentials.push(await piCredentialDiagnostic(layout, role, binary));
1895
1980
  argv[role] = ["--no-extensions", "--extension", resolved.adapter.entry, "--no-approve", "--append-system-prompt", appendPath];
1896
1981
  }
@@ -2116,20 +2201,29 @@ var init_paseo = __esm({
2116
2201
  });
2117
2202
 
2118
2203
  // src/plugin.ts
2119
- import { readFileSync as readFileSync2 } from "fs";
2120
- import { join as join10 } from "path";
2204
+ import { readFileSync as readFileSync3 } from "fs";
2205
+ import { join as join11 } from "path";
2121
2206
  function loadAsset(path) {
2122
- return readFileSync2(new URL(`./plugin-assets/${path}`, import.meta.url), "utf8");
2207
+ return readFileSync3(new URL(`./plugin-assets/${path}`, import.meta.url), "utf8");
2123
2208
  }
2124
2209
  function claudeCarrierPluginDir(layout) {
2125
- return join10(layout.roomHome, "plugin");
2210
+ return join11(layout.roomHome, "plugin");
2211
+ }
2212
+ function typescriptTemplateLiteral(value) {
2213
+ const escaped = value.replaceAll("\\", "\\\\").replaceAll("`", "\\`").replaceAll("${", "\\${");
2214
+ return `\`${escaped}\``;
2126
2215
  }
2127
2216
  function renderClaudeCarrierContract(roles) {
2128
- const contracts = Object.fromEntries(roles.map((role) => [providerId("claude", role), renderInstructions(role)]));
2217
+ const entries = roles.map((role) => {
2218
+ const id = JSON.stringify(providerId("claude", role));
2219
+ return ` ${id}: ${typescriptTemplateLiteral(renderInstructions(role))},`;
2220
+ });
2129
2221
  return [
2130
2222
  "/** Generated by paseo-room. The Markdown prompt assets remain authoritative. */",
2131
2223
  `export const GENERATION = ${JSON.stringify(contractDigest())};`,
2132
- `export const CONTRACT: Readonly<Record<string, string>> = ${JSON.stringify(contracts, null, 2)};`,
2224
+ "export const CONTRACT: Readonly<Record<string, string>> = {",
2225
+ ...entries,
2226
+ "};",
2133
2227
  ""
2134
2228
  ].join("\n");
2135
2229
  }
@@ -2137,9 +2231,9 @@ function claudeCarrierEntries(layout, roles) {
2137
2231
  const root = claudeCarrierPluginDir(layout);
2138
2232
  return [
2139
2233
  { kind: "dir", path: root },
2140
- { kind: "dir", path: join10(root, "server") },
2141
- ...STATIC_ASSETS.map((path) => ({ kind: "file", path: join10(root, path), content: loadAsset(path) })),
2142
- { kind: "file", path: join10(root, "server", "contract.ts"), content: renderClaudeCarrierContract(roles) }
2234
+ { kind: "dir", path: join11(root, "server") },
2235
+ ...STATIC_ASSETS.map((path) => ({ kind: "file", path: join11(root, path), content: loadAsset(path) })),
2236
+ { kind: "file", path: join11(root, "server", "contract.ts"), content: renderClaudeCarrierContract(roles) }
2143
2237
  ];
2144
2238
  }
2145
2239
  var CLAUDE_CARRIER_PLUGIN_ID, STATIC_ASSETS;
@@ -2160,7 +2254,7 @@ var init_plugin = __esm({
2160
2254
  });
2161
2255
 
2162
2256
  // src/commands.ts
2163
- import { join as join11, sep as sep2 } from "path";
2257
+ import { join as join12, sep as sep2 } from "path";
2164
2258
  import { lstat as lstat4, rm as rm3 } from "fs/promises";
2165
2259
  function sessionOptions(options) {
2166
2260
  const password = (options.env ?? process.env).PASEO_PASSWORD;
@@ -2192,7 +2286,10 @@ async function buildDesired(layout, agents, roles, build = {}) {
2192
2286
  const entries = [
2193
2287
  { kind: "dir", path: layout.roomHome },
2194
2288
  { kind: "dir", path: sharedRoom(layout) },
2195
- { kind: "file", path: join11(sharedRoom(layout), "WORKSPACE_PROTOCOL.md"), content: renderInstructions("workspace") }
2289
+ // Earlier versions generated a default workspace protocol template here. The room ships no
2290
+ // default protocol now, so the old generated regular file is declared absent and removed.
2291
+ { kind: "absent", path: join12(sharedRoom(layout), "WORKSPACE_PROTOCOL.md") },
2292
+ ...roomSkillEntries(layout, agents)
2196
2293
  ];
2197
2294
  const providers = {};
2198
2295
  const profiles = [];
@@ -2226,18 +2323,18 @@ async function buildDesired(layout, agents, roles, build = {}) {
2226
2323
  if (agents.every((id) => binaries[id] !== void 0)) {
2227
2324
  entries.push({
2228
2325
  kind: "file",
2229
- path: join11(layout.roomHome, AUTHENTICATION_GUIDE),
2326
+ path: join12(layout.roomHome, AUTHENTICATION_GUIDE),
2230
2327
  content: renderAuthenticationGuide(layout.roomHome, binaries, agents, roles)
2231
2328
  });
2232
2329
  checks.push(warn(
2233
2330
  "room.authentication-not-validated",
2234
- `Role authentication was not validated. Login instructions are managed at ${join11(layout.roomHome, AUTHENTICATION_GUIDE)}.`,
2331
+ `Role authentication was not validated. Login instructions are managed at ${join12(layout.roomHome, AUTHENTICATION_GUIDE)}.`,
2235
2332
  "After setup --apply, follow that guide or run: paseo-room auth login <agent> <role>"
2236
2333
  ));
2237
2334
  }
2238
2335
  const pluginPath = agents.includes("claude") ? claudeCarrierPluginDir(layout) : void 0;
2239
2336
  if (pluginPath !== void 0) entries.push(...claudeCarrierEntries(layout, roles));
2240
- entries.push({ kind: "file", path: join11(layout.roomHome, MARKER), content: renderMarker(package_default.version, agents, roles, contractDigest(), markerMemoryContract(agents, build.memoryContract ?? true)) });
2337
+ entries.push({ kind: "file", path: join12(layout.roomHome, MARKER), content: renderMarker(package_default.version, agents, roles, contractDigest(), markerMemoryContract(agents, build.memoryContract ?? true)) });
2241
2338
  return { entries, providers, profiles, checks, ...pluginPath === void 0 ? {} : { pluginPath } };
2242
2339
  }
2243
2340
  function contractProvenance(previous) {
@@ -2300,11 +2397,26 @@ async function safeDirectory(path, checks) {
2300
2397
  async function roomPathSafety(layout, agents, roles) {
2301
2398
  const checks = [];
2302
2399
  if (!await safeDirectory(layout.roomHome, checks)) return checks;
2303
- await safeDirectory(sharedRoom(layout), checks);
2304
- const rolesHome = join11(layout.roomHome, "roles");
2400
+ const room = sharedRoom(layout);
2401
+ const roomIsSafe = await safeDirectory(room, checks);
2402
+ if (roomIsSafe && agents.length > 0) {
2403
+ const skills = join12(room, "skills");
2404
+ if (await safeDirectory(skills, checks)) {
2405
+ const source = roomSkillSource(layout);
2406
+ if (await safeDirectory(source, checks)) await safeDirectory(join12(source, "references"), checks);
2407
+ }
2408
+ const projections = skillProjectionRoot(layout);
2409
+ if (await safeDirectory(projections, checks)) {
2410
+ for (const agent of agents) {
2411
+ const agentRoot = agentSkillProjectionRoot(layout, agent);
2412
+ if (await safeDirectory(agentRoot, checks)) await safeDirectory(leadSkillProjection(layout, agent), checks);
2413
+ }
2414
+ }
2415
+ }
2416
+ const rolesHome = join12(layout.roomHome, "roles");
2305
2417
  if (!await safeDirectory(rolesHome, checks)) return checks;
2306
2418
  for (const agent of agents) {
2307
- const agentHome = join11(rolesHome, agent);
2419
+ const agentHome = join12(rolesHome, agent);
2308
2420
  if (!await safeDirectory(agentHome, checks)) continue;
2309
2421
  for (const role of roles) await safeDirectory(roleHome(layout, agent, role), checks);
2310
2422
  }
@@ -2484,7 +2596,7 @@ async function setup(options = {}) {
2484
2596
  operations
2485
2597
  };
2486
2598
  }
2487
- const markerPath = join11(layout.roomHome, MARKER);
2599
+ const markerPath = join12(layout.roomHome, MARKER);
2488
2600
  const markerEntries = desired.entries.filter((entry) => entry.path === markerPath);
2489
2601
  await applyEntries(desired.entries.filter((entry) => entry.path !== markerPath));
2490
2602
  const ids = Object.keys(desired.providers);
@@ -2673,6 +2785,7 @@ var init_commands = __esm({
2673
2785
  init_plugin();
2674
2786
  init_result();
2675
2787
  init_instructions();
2788
+ init_skills();
2676
2789
  init_room();
2677
2790
  init_roles();
2678
2791
  AGENTS = { codex: codexAgent, claude: claudeAgent, pi: piAgent };
@@ -2786,6 +2899,7 @@ function renderHuman(result) {
2786
2899
  init_result();
2787
2900
  init_roles();
2788
2901
  init_prompts();
2902
+ init_skills();
2789
2903
  function collectAgent(value, previous) {
2790
2904
  const agent = AGENT_IDS.find((id) => id === value);
2791
2905
  if (!agent) throw new InvalidArgumentError(`Unknown agent: ${value}`);
@@ -2817,6 +2931,13 @@ function failedFromThrown(command, error) {
2817
2931
  "Reinstall paseo-room, then try again."
2818
2932
  )]);
2819
2933
  }
2934
+ if (error instanceof SkillAssetError) {
2935
+ return failed(command, [fail(
2936
+ `${command}.skill-asset`,
2937
+ error.message,
2938
+ "Reinstall paseo-room, then try again."
2939
+ )]);
2940
+ }
2820
2941
  if (error instanceof ManagedPathError) {
2821
2942
  return failed(command, [fail(`${command}.managed-path`, error.message, error.fix)]);
2822
2943
  }