@clossys/launcher 0.3.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 (290) hide show
  1. package/README.md +1373 -60
  2. package/contracts/conversation-contract.md +2 -2
  3. package/contracts/product-ci-workflow.yml +74 -0
  4. package/contracts/repository-inventory.json +53 -0
  5. package/dist/admission-fixture.d.ts +168 -0
  6. package/dist/admission-fixture.d.ts.map +1 -0
  7. package/dist/admission-fixture.js +467 -0
  8. package/dist/admission-fixture.js.map +1 -0
  9. package/dist/admission.d.ts +124 -0
  10. package/dist/admission.d.ts.map +1 -0
  11. package/dist/admission.js +804 -0
  12. package/dist/admission.js.map +1 -0
  13. package/dist/agents-guide.d.ts +9 -0
  14. package/dist/agents-guide.d.ts.map +1 -0
  15. package/dist/agents-guide.js +26 -0
  16. package/dist/agents-guide.js.map +1 -0
  17. package/dist/apply-command-options.check.d.ts +12 -0
  18. package/dist/apply-command-options.check.d.ts.map +1 -0
  19. package/dist/apply-command-options.check.js +20 -0
  20. package/dist/apply-command-options.check.js.map +1 -0
  21. package/dist/apply-plan-cli.d.ts +39 -1
  22. package/dist/apply-plan-cli.d.ts.map +1 -1
  23. package/dist/apply-plan-cli.js +432 -15
  24. package/dist/apply-plan-cli.js.map +1 -1
  25. package/dist/apply-plan.d.ts +46 -59
  26. package/dist/apply-plan.d.ts.map +1 -1
  27. package/dist/apply-plan.js +112 -97
  28. package/dist/apply-plan.js.map +1 -1
  29. package/dist/apply-step-fixture.d.ts +87 -0
  30. package/dist/apply-step-fixture.d.ts.map +1 -0
  31. package/dist/apply-step-fixture.js +199 -0
  32. package/dist/apply-step-fixture.js.map +1 -0
  33. package/dist/apply-store.d.ts +93 -0
  34. package/dist/apply-store.d.ts.map +1 -0
  35. package/dist/apply-store.js +625 -0
  36. package/dist/apply-store.js.map +1 -0
  37. package/dist/approval-sheet.d.ts +21 -0
  38. package/dist/approval-sheet.d.ts.map +1 -0
  39. package/dist/approval-sheet.js +163 -0
  40. package/dist/approval-sheet.js.map +1 -0
  41. package/dist/body-command.d.ts +42 -0
  42. package/dist/body-command.d.ts.map +1 -0
  43. package/dist/body-command.js +143 -0
  44. package/dist/body-command.js.map +1 -0
  45. package/dist/change-set-contract.d.ts +403 -0
  46. package/dist/change-set-contract.d.ts.map +1 -0
  47. package/dist/change-set-contract.js +781 -0
  48. package/dist/change-set-contract.js.map +1 -0
  49. package/dist/change-set-digest.d.ts +28 -0
  50. package/dist/change-set-digest.d.ts.map +1 -0
  51. package/dist/change-set-digest.js +65 -0
  52. package/dist/change-set-digest.js.map +1 -0
  53. package/dist/check-cli.d.ts.map +1 -1
  54. package/dist/check-cli.js +14 -3
  55. package/dist/check-cli.js.map +1 -1
  56. package/dist/cli.d.ts +17 -6
  57. package/dist/cli.d.ts.map +1 -1
  58. package/dist/cli.js +84 -23
  59. package/dist/cli.js.map +1 -1
  60. package/dist/core.d.ts +79 -22
  61. package/dist/core.d.ts.map +1 -1
  62. package/dist/core.js +843 -268
  63. package/dist/core.js.map +1 -1
  64. package/dist/dry-materialize.d.ts +63 -0
  65. package/dist/dry-materialize.d.ts.map +1 -0
  66. package/dist/dry-materialize.js +330 -0
  67. package/dist/dry-materialize.js.map +1 -0
  68. package/dist/existing-declaration-adoption.check.d.ts +2 -0
  69. package/dist/existing-declaration-adoption.check.d.ts.map +1 -0
  70. package/dist/existing-declaration-adoption.check.js +10 -0
  71. package/dist/existing-declaration-adoption.check.js.map +1 -0
  72. package/dist/generated/contract-schema.generated.d.ts +97 -0
  73. package/dist/generated/contract-schema.generated.d.ts.map +1 -0
  74. package/dist/generated/contract-schema.generated.js +496 -0
  75. package/dist/generated/contract-schema.generated.js.map +1 -0
  76. package/dist/generated/package-scope.generated.d.ts +6 -0
  77. package/dist/generated/package-scope.generated.d.ts.map +1 -0
  78. package/dist/generated/package-scope.generated.js +10 -0
  79. package/dist/generated/package-scope.generated.js.map +1 -0
  80. package/dist/generated/plan-contracts.generated.d.ts +3 -0
  81. package/dist/generated/plan-contracts.generated.d.ts.map +1 -0
  82. package/dist/generated/plan-contracts.generated.js +3101 -0
  83. package/dist/generated/plan-contracts.generated.js.map +1 -0
  84. package/dist/host.d.ts.map +1 -1
  85. package/dist/host.js +11 -0
  86. package/dist/host.js.map +1 -1
  87. package/dist/identity.d.ts +15 -0
  88. package/dist/identity.d.ts.map +1 -0
  89. package/dist/identity.js +48 -0
  90. package/dist/identity.js.map +1 -0
  91. package/dist/index.d.ts +35 -5
  92. package/dist/index.d.ts.map +1 -1
  93. package/dist/index.js +19 -2
  94. package/dist/index.js.map +1 -1
  95. package/dist/inventory-adoption.d.ts +24 -5
  96. package/dist/inventory-adoption.d.ts.map +1 -1
  97. package/dist/inventory-adoption.js +70 -25
  98. package/dist/inventory-adoption.js.map +1 -1
  99. package/dist/inventory-choice.d.ts +40 -0
  100. package/dist/inventory-choice.d.ts.map +1 -0
  101. package/dist/inventory-choice.js +156 -0
  102. package/dist/inventory-choice.js.map +1 -0
  103. package/dist/inventory-contract.d.ts +89 -0
  104. package/dist/inventory-contract.d.ts.map +1 -0
  105. package/dist/inventory-contract.js +121 -0
  106. package/dist/inventory-contract.js.map +1 -0
  107. package/dist/key-editor.d.ts +30 -0
  108. package/dist/key-editor.d.ts.map +1 -0
  109. package/dist/key-editor.js +445 -0
  110. package/dist/key-editor.js.map +1 -0
  111. package/dist/ledger-contract.d.ts +190 -0
  112. package/dist/ledger-contract.d.ts.map +1 -0
  113. package/dist/ledger-contract.js +555 -0
  114. package/dist/ledger-contract.js.map +1 -0
  115. package/dist/ledger-trust.d.ts +90 -0
  116. package/dist/ledger-trust.d.ts.map +1 -0
  117. package/dist/ledger-trust.js +203 -0
  118. package/dist/ledger-trust.js.map +1 -0
  119. package/dist/lockfile-invariants.d.ts +48 -0
  120. package/dist/lockfile-invariants.d.ts.map +1 -0
  121. package/dist/lockfile-invariants.js +375 -0
  122. package/dist/lockfile-invariants.js.map +1 -0
  123. package/dist/lockfile-readers.d.ts +72 -0
  124. package/dist/lockfile-readers.d.ts.map +1 -0
  125. package/dist/lockfile-readers.js +713 -0
  126. package/dist/lockfile-readers.js.map +1 -0
  127. package/dist/lockfile-regen.d.ts +106 -0
  128. package/dist/lockfile-regen.d.ts.map +1 -0
  129. package/dist/lockfile-regen.js +760 -0
  130. package/dist/lockfile-regen.js.map +1 -0
  131. package/dist/lockfile-tool-env.d.ts +29 -0
  132. package/dist/lockfile-tool-env.d.ts.map +1 -0
  133. package/dist/lockfile-tool-env.js +111 -0
  134. package/dist/lockfile-tool-env.js.map +1 -0
  135. package/dist/materialize.d.ts +113 -0
  136. package/dist/materialize.d.ts.map +1 -0
  137. package/dist/materialize.js +881 -0
  138. package/dist/materialize.js.map +1 -0
  139. package/dist/observe-repository.d.ts +90 -0
  140. package/dist/observe-repository.d.ts.map +1 -0
  141. package/dist/observe-repository.js +1367 -0
  142. package/dist/observe-repository.js.map +1 -0
  143. package/dist/plan-bundle-setup-fixture.d.ts +68 -0
  144. package/dist/plan-bundle-setup-fixture.d.ts.map +1 -0
  145. package/dist/plan-bundle-setup-fixture.js +167 -0
  146. package/dist/plan-bundle-setup-fixture.js.map +1 -0
  147. package/dist/plan-bundle.d.ts +256 -0
  148. package/dist/plan-bundle.d.ts.map +1 -0
  149. package/dist/plan-bundle.js +882 -0
  150. package/dist/plan-bundle.js.map +1 -0
  151. package/dist/plan-command.d.ts +29 -0
  152. package/dist/plan-command.d.ts.map +1 -0
  153. package/dist/plan-command.js +523 -0
  154. package/dist/plan-command.js.map +1 -0
  155. package/dist/plan-contract.d.ts +153 -0
  156. package/dist/plan-contract.d.ts.map +1 -0
  157. package/dist/plan-contract.js +61 -0
  158. package/dist/plan-contract.js.map +1 -0
  159. package/dist/plan-digest.d.ts +25 -0
  160. package/dist/plan-digest.d.ts.map +1 -0
  161. package/dist/plan-digest.js +106 -0
  162. package/dist/plan-digest.js.map +1 -0
  163. package/dist/plan-rules.d.ts +23 -0
  164. package/dist/plan-rules.d.ts.map +1 -0
  165. package/dist/plan-rules.js +177 -0
  166. package/dist/plan-rules.js.map +1 -0
  167. package/dist/planned-bundle.d.ts +20 -0
  168. package/dist/planned-bundle.d.ts.map +1 -0
  169. package/dist/planned-bundle.js +191 -0
  170. package/dist/planned-bundle.js.map +1 -0
  171. package/dist/product-repository.d.ts +4 -0
  172. package/dist/product-repository.d.ts.map +1 -1
  173. package/dist/product-repository.js +9 -1
  174. package/dist/product-repository.js.map +1 -1
  175. package/dist/provenance-gate.d.ts +48 -0
  176. package/dist/provenance-gate.d.ts.map +1 -0
  177. package/dist/provenance-gate.js +324 -0
  178. package/dist/provenance-gate.js.map +1 -0
  179. package/dist/pull-request-body.d.ts +45 -0
  180. package/dist/pull-request-body.d.ts.map +1 -0
  181. package/dist/pull-request-body.js +232 -0
  182. package/dist/pull-request-body.js.map +1 -0
  183. package/dist/registry-snapshot.d.ts +141 -0
  184. package/dist/registry-snapshot.d.ts.map +1 -0
  185. package/dist/registry-snapshot.js +483 -0
  186. package/dist/registry-snapshot.js.map +1 -0
  187. package/dist/release-age-edit.d.ts +52 -0
  188. package/dist/release-age-edit.d.ts.map +1 -0
  189. package/dist/release-age-edit.js +413 -0
  190. package/dist/release-age-edit.js.map +1 -0
  191. package/dist/root-entries.d.ts +36 -0
  192. package/dist/root-entries.d.ts.map +1 -0
  193. package/dist/root-entries.js +80 -0
  194. package/dist/root-entries.js.map +1 -0
  195. package/dist/setup-template-scripts.d.ts +36 -0
  196. package/dist/setup-template-scripts.d.ts.map +1 -0
  197. package/dist/setup-template-scripts.js +568 -0
  198. package/dist/setup-template-scripts.js.map +1 -0
  199. package/dist/setup-templates.d.ts +55 -0
  200. package/dist/setup-templates.d.ts.map +1 -0
  201. package/dist/setup-templates.js +438 -0
  202. package/dist/setup-templates.js.map +1 -0
  203. package/dist/skills.d.ts +34 -1
  204. package/dist/skills.d.ts.map +1 -1
  205. package/dist/skills.js +129 -17
  206. package/dist/skills.js.map +1 -1
  207. package/dist/status.d.ts +63 -0
  208. package/dist/status.d.ts.map +1 -0
  209. package/dist/status.js +539 -0
  210. package/dist/status.js.map +1 -0
  211. package/dist/types.d.ts +151 -13
  212. package/dist/types.d.ts.map +1 -1
  213. package/package.json +4 -4
  214. package/skeleton/README.md +14 -9
  215. package/skeleton/package.json +2 -1
  216. package/skill/SKILL.md +23 -7
  217. package/skill-catalogue/advisor/SKILL.md +59 -6
  218. package/skill-catalogue/architect/SKILL.md +2 -2
  219. package/skill-catalogue/bouncer/SKILL.md +2 -2
  220. package/skill-catalogue/builder/SKILL.md +2 -2
  221. package/skill-catalogue/butler/SKILL.md +2 -2
  222. package/skill-catalogue/controller/SKILL.md +2 -2
  223. package/skill-catalogue/customer/SKILL.md +2 -2
  224. package/skill-catalogue/designer/SKILL.md +4 -2
  225. package/skill-catalogue/giver/SKILL.md +2 -2
  226. package/skill-catalogue/influencer/SKILL.md +2 -2
  227. package/skill-catalogue/inspector/SKILL.md +2 -2
  228. package/skill-catalogue/integrator/SKILL.md +2 -2
  229. package/skill-catalogue/keeper/SKILL.md +2 -2
  230. package/skill-catalogue/launcher/SKILL.md +23 -7
  231. package/skill-catalogue/locksmith/SKILL.md +2 -2
  232. package/skill-catalogue/messenger/SKILL.md +2 -2
  233. package/skill-catalogue/observer/SKILL.md +2 -2
  234. package/skill-catalogue/publisher/SKILL.md +2 -2
  235. package/skill-catalogue/starter/SKILL.md +3 -2
  236. package/skill-catalogue/strategist/SKILL.md +12 -4
  237. package/skill-catalogue/writer/SKILL.md +2 -2
  238. package/src/admission-fixture.ts +585 -0
  239. package/src/admission.ts +819 -0
  240. package/src/agents-guide.ts +29 -0
  241. package/src/apply-command-options.check.ts +27 -0
  242. package/src/apply-plan-cli.ts +454 -14
  243. package/src/apply-plan.ts +112 -124
  244. package/src/apply-step-fixture.ts +236 -0
  245. package/src/apply-store.ts +584 -0
  246. package/src/approval-sheet.ts +170 -0
  247. package/src/body-command.ts +162 -0
  248. package/src/change-set-contract.ts +987 -0
  249. package/src/change-set-digest.ts +70 -0
  250. package/src/check-cli.ts +14 -3
  251. package/src/cli.ts +90 -22
  252. package/src/core.ts +973 -275
  253. package/src/dry-materialize.ts +353 -0
  254. package/src/existing-declaration-adoption.check.ts +12 -0
  255. package/src/generated/contract-schema.generated.ts +520 -0
  256. package/src/generated/package-scope.generated.ts +10 -0
  257. package/src/generated/plan-contracts.generated.ts +3101 -0
  258. package/src/host.ts +10 -0
  259. package/src/identity.ts +51 -0
  260. package/src/index.ts +74 -3
  261. package/src/inventory-adoption.ts +107 -29
  262. package/src/inventory-choice.ts +172 -0
  263. package/src/inventory-contract.ts +166 -0
  264. package/src/key-editor.ts +446 -0
  265. package/src/ledger-contract.ts +660 -0
  266. package/src/ledger-trust.ts +272 -0
  267. package/src/lockfile-invariants.ts +421 -0
  268. package/src/lockfile-readers.ts +749 -0
  269. package/src/lockfile-regen.ts +851 -0
  270. package/src/lockfile-tool-env.ts +131 -0
  271. package/src/materialize.ts +915 -0
  272. package/src/observe-repository.ts +1365 -0
  273. package/src/plan-bundle-setup-fixture.ts +200 -0
  274. package/src/plan-bundle.ts +1014 -0
  275. package/src/plan-command.ts +532 -0
  276. package/src/plan-contract.ts +179 -0
  277. package/src/plan-digest.ts +102 -0
  278. package/src/plan-rules.ts +188 -0
  279. package/src/planned-bundle.ts +211 -0
  280. package/src/product-repository.ts +10 -1
  281. package/src/provenance-gate.ts +352 -0
  282. package/src/pull-request-body.ts +261 -0
  283. package/src/registry-snapshot.ts +534 -0
  284. package/src/release-age-edit.ts +430 -0
  285. package/src/root-entries.ts +81 -0
  286. package/src/setup-template-scripts.ts +580 -0
  287. package/src/setup-templates.ts +479 -0
  288. package/src/skills.ts +161 -18
  289. package/src/status.ts +557 -0
  290. package/src/types.ts +148 -13
@@ -0,0 +1,70 @@
1
+ // The change-set and bundle digests (issue #1178), implemented from their
2
+ // one definition, docs/contracts/apply-change-set-digest.md, and tested
3
+ // against its corpus, apply-change-set-digest.fixture.json beside it. Both
4
+ // files are in the public repository, not shipped in this package. Both
5
+ // digests reuse the plan digest's canonical step, canonicalDigest(); nothing
6
+ // here serializes JSON a second way.
7
+
8
+ import { canonicalDigest } from "./plan-digest.js";
9
+
10
+ /** The top-level change-set members the digest leaves out: each is computed from the digest or from what it covers, or (tooling) records the machine rather than the change. */
11
+ export const CHANGE_SET_DIGEST_EXCLUDED_FIELDS: readonly string[] = ["changeSetDigest", "branch", "bundle", "pullRequest", "inverse", "tooling", "texts"];
12
+
13
+ /** The members a derived file keeps in the digest's subject; its `before` and `after` are left out. */
14
+ export const DERIVED_FILE_DIGEST_FIELDS: readonly string[] = ["path", "mode", "derived", "item", "invariants"];
15
+
16
+ function isRecord(value: unknown): value is Record<string, unknown> {
17
+ return typeof value === "object" && value !== null && !Array.isArray(value);
18
+ }
19
+
20
+ /**
21
+ * The value the change-set digest is computed over: the set without its
22
+ * excluded members, with every file whose `derived` is `true` reduced to
23
+ * DERIVED_FILE_DIGEST_FIELDS. Everything else is kept as it is, arrays in
24
+ * order. Throws when `changeSet` is not an object or its `files` is not an
25
+ * array of objects, because then there is nothing well defined to digest.
26
+ */
27
+ export function changeSetDigestSubject(changeSet: unknown): Record<string, unknown> {
28
+ if (!isRecord(changeSet)) throw new TypeError("a change set must be an object to have a digest");
29
+ const files = changeSet.files;
30
+ if (!Array.isArray(files) || !files.every(isRecord)) throw new TypeError("a change set's files must be an array of objects to have a digest");
31
+ const subject: Record<string, unknown> = {};
32
+ for (const [key, member] of Object.entries(changeSet)) if (!CHANGE_SET_DIGEST_EXCLUDED_FIELDS.includes(key)) subject[key] = member;
33
+ subject.files = files.map((file) => {
34
+ if (file.derived !== true) return file;
35
+ const reduced: Record<string, unknown> = {};
36
+ for (const key of DERIVED_FILE_DIGEST_FIELDS) if (Object.hasOwn(file, key)) reduced[key] = file[key];
37
+ return reduced;
38
+ });
39
+ return subject;
40
+ }
41
+
42
+ /** `canonicalDigest(changeSetDigestSubject(changeSet))`: the same digest for the same change, whatever the excluded members hold. */
43
+ export function changeSetDigest(changeSet: unknown): string {
44
+ return canonicalDigest(changeSetDigestSubject(changeSet));
45
+ }
46
+
47
+ /** One repository's part in the bundle digest. */
48
+ export interface BundleDigestEntry {
49
+ readonly id: string;
50
+ readonly changeSetDigest: string;
51
+ }
52
+
53
+ /** Orders two strings by their UTF-16 code units, as the digest page requires for repository ids. */
54
+ function compareCodeUnits(left: string, right: string): number {
55
+ if (left === right) return 0;
56
+ return left < right ? -1 : 1;
57
+ }
58
+
59
+ /**
60
+ * The bundle digest an approval binds: `canonicalDigest({ planDigest,
61
+ * repositories })`, with `repositories` holding the id and change-set digest
62
+ * of every repository that has a change set, sorted by id. Nothing else is
63
+ * covered -- no authorization, time, check or verdict -- so it can be
64
+ * recomputed from digests alone.
65
+ */
66
+ export function bundleDigest(planDigest: string, repositories: readonly BundleDigestEntry[]): string {
67
+ const entries = repositories.map(({ id, changeSetDigest: digest }) => ({ id, changeSetDigest: digest }));
68
+ entries.sort((left, right) => compareCodeUnits(left.id, right.id));
69
+ return canonicalDigest({ planDigest, repositories: entries });
70
+ }
package/src/check-cli.ts CHANGED
@@ -34,13 +34,16 @@ function parseInventoryObservation(value: unknown): InventoryObservation | undef
34
34
  if (!isRecord(value)) {
35
35
  throw new LauncherCheckInputError("observation.cwd.inventory must be an object");
36
36
  }
37
- if (value.status !== "missing" && value.status !== "empty" && value.status !== "populated") {
38
- throw new LauncherCheckInputError("observation.cwd.inventory.status must be missing, empty, or populated");
37
+ if (value.status !== "missing" && value.status !== "empty" && value.status !== "populated" && value.status !== "invalid") {
38
+ throw new LauncherCheckInputError("observation.cwd.inventory.status must be missing, empty, populated, or invalid");
39
39
  }
40
40
  if (typeof value.count !== "number" || !Number.isInteger(value.count) || value.count < 0) {
41
41
  throw new LauncherCheckInputError("observation.cwd.inventory.count must be a nonnegative integer");
42
42
  }
43
- return { status: value.status, count: value.count };
43
+ if (value.reason !== undefined && !isText(value.reason)) {
44
+ throw new LauncherCheckInputError("observation.cwd.inventory.reason must be a nonempty string when present");
45
+ }
46
+ return { status: value.status, count: value.count, ...(isText(value.reason) ? { reason: value.reason } : {}) };
44
47
  }
45
48
 
46
49
  function parseCwd(value: unknown): CwdObservation {
@@ -86,6 +89,9 @@ export function parseObservation(value: unknown): WorkspaceObservation {
86
89
  if (value.advisorVersion !== undefined && !isText(value.advisorVersion)) {
87
90
  throw new LauncherCheckInputError("observation.advisorVersion must be a string");
88
91
  }
92
+ if (value.integratorVersion !== undefined && !isText(value.integratorVersion)) {
93
+ throw new LauncherCheckInputError("observation.integratorVersion must be a string");
94
+ }
89
95
  return {
90
96
  cwd: parseCwd(value.cwd),
91
97
  ownerCandidates: value.ownerCandidates,
@@ -93,6 +99,7 @@ export function parseObservation(value: unknown): WorkspaceObservation {
93
99
  gitAvailable: value.gitAvailable,
94
100
  ...(isText(value.envOwner) ? { envOwner: value.envOwner } : {}),
95
101
  ...(isText(value.advisorVersion) ? { advisorVersion: value.advisorVersion } : {}),
102
+ ...(isText(value.integratorVersion) ? { integratorVersion: value.integratorVersion } : {}),
96
103
  };
97
104
  }
98
105
 
@@ -111,9 +118,13 @@ export function planningHost(): WorkspaceHost {
111
118
  isDirectory: () => false,
112
119
  isSymlink: () => false,
113
120
  readText: () => null,
121
+ readBytes: () => null,
114
122
  writeText: () => {
115
123
  throw new Error("launcher-check does not write");
116
124
  },
125
+ writeBytes: () => {
126
+ throw new Error("launcher-check does not write");
127
+ },
117
128
  mkdirp: () => {
118
129
  throw new Error("launcher-check does not write");
119
130
  },
package/src/cli.ts CHANGED
@@ -5,6 +5,7 @@ import { fileURLToPath } from "node:url";
5
5
  import {
6
6
  applyWorkspacePlan,
7
7
  cloneMissingInventoryRepositories,
8
+ inventoryPositionLabel,
8
9
  observeWorkspace,
9
10
  planWorkspace,
10
11
  launcherPackageRootFromModule,
@@ -14,7 +15,7 @@ import {
14
15
  import { createNodeHost } from "./host.js";
15
16
  import type { WorkspaceHost } from "./types.js";
16
17
 
17
- export const USAGE = `Usage: launcher [--inventory <path>] [--clone-missing]
18
+ export const USAGE = `Usage: launcher [--repositories <owner/name>[,<owner/name>...] [--replace-inventory]] [--inventory <path>] [--clone-missing]
18
19
 
19
20
  Create, resume, or appoint a GitHub repository as the account workspace hub.
20
21
 
@@ -23,16 +24,30 @@ hub to resume. Run from any GitHub repository you want to own the account-level
23
24
  hub to appoint it — it does not have to be a new exclusive repo, and it keeps
24
25
  its current name and files.
25
26
 
26
- Appointing requires a populated generated hub inventory (packed template
27
- skeleton/clossys/.state/inventory.json; the generated path does not ship), or
28
- --inventory <path> pointing at one. Resume refreshes composed skills and
29
- stale hub guidance, and migrates a legacy .clossys/ hub state to
30
- clossys/.state/ automatically. Create may write an empty inventory.
27
+ Appointing needs the repositories the hub covers. Choose them on Advisor's
28
+ repository card (npx -p @clossys/advisor advisor-repository-card), built
29
+ from the repositories GitHub lists for your sign-in, then pass the chosen ids
30
+ to --repositories: Launcher writes clossys/.state/inventory.json for you,
31
+ when appointing or on an existing hub. It never merges into or overwrites an
32
+ inventory that lists a different set of repositories; it reports the
33
+ difference, and --replace-inventory is the explicit approval to replace it.
34
+ --inventory <path> still accepts a prepared inventory document when
35
+ appointing. Resume refreshes composed skills and stale hub guidance, pins
36
+ the hub's @clossys/advisor and @clossys/integrator at their live versions,
37
+ and migrates a legacy .clossys/ hub state to clossys/.state/ automatically.
38
+ Create may write an empty inventory. A run composes skills into the hub only
39
+ and changes nothing in an inventoried repository's checkout beside it: once
40
+ a product repository is staffed in an approved plan, its team arrives with
41
+ that plan's setup pull request. When a run changes an engine pin, it says
42
+ what changed: run the hub's package manager install, then commit
43
+ package.json together with its lockfile.
31
44
 
32
45
  By default launcher never \`gh repo clone\`s a missing inventory entry --
33
46
  that is not how you talk to the team. --clone-missing is the one explicit,
34
47
  approved exception (#1179): on resume only, it clones every inventoried
35
- repository not yet sitting beside the hub, and only those.
48
+ repository not yet sitting beside the hub, and only those. Cloning is not
49
+ composing: a cloned repository receives its team only once it is staffed in
50
+ an approved plan, with that plan's setup pull request.
36
51
 
37
52
  GitHub-only. Owner is inferred from \`gh\` and git remotes. Public npm reads
38
53
  need no token.
@@ -45,20 +60,57 @@ function exitCodeFor(state: "satisfied" | "violated" | "indeterminate"): number
45
60
  return state === "satisfied" ? 0 : state === "violated" ? 1 : 2;
46
61
  }
47
62
 
48
- export function parseLauncherArgs(argv: readonly string[]): { help: boolean; inventoryPath?: string; cloneMissing: boolean } {
49
- if (argv.length === 1 && (argv[0] === "--help" || argv[0] === "-h")) return { help: true, cloneMissing: false };
50
- const rest = [...argv];
63
+ export interface LauncherArgs {
64
+ readonly help: boolean;
65
+ readonly inventoryPath?: string;
66
+ /** `--repositories`: the comma-separated ids, split but not yet validated (planWorkspace validates them against the inventory contract). */
67
+ readonly repositories?: readonly string[];
68
+ readonly replaceInventory: boolean;
69
+ readonly cloneMissing: boolean;
70
+ }
71
+
72
+ const ARGUMENT_ERROR =
73
+ "launcher takes no arguments except optional --inventory <path>, --repositories <owner/name>[,<owner/name>...], --replace-inventory, and --clone-missing, each at most once; run it from the directory to create or appoint";
74
+
75
+ /**
76
+ * Parses launcher's flags. Each flag may appear once, in any order; a
77
+ * value flag takes the next argument. `--repositories` is one argument of
78
+ * comma-separated ids -- a repository id never contains a comma -- so an
79
+ * empty id (a doubled or trailing comma) reaches the contract check as an
80
+ * empty string and is refused there, by position.
81
+ */
82
+ export function parseLauncherArgs(argv: readonly string[]): LauncherArgs {
83
+ if (argv.length === 1 && (argv[0] === "--help" || argv[0] === "-h")) return { help: true, replaceInventory: false, cloneMissing: false };
84
+ const seen = new Set<string>();
85
+ let inventoryPath: string | undefined;
86
+ let repositories: readonly string[] | undefined;
87
+ let replaceInventory = false;
51
88
  let cloneMissing = false;
52
- const cloneIndex = rest.indexOf("--clone-missing");
53
- if (cloneIndex !== -1) {
54
- cloneMissing = true;
55
- rest.splice(cloneIndex, 1);
89
+ for (let index = 0; index < argv.length; index += 1) {
90
+ const flag = argv[index];
91
+ if (flag === undefined || seen.has(flag)) throw new LauncherInputError(ARGUMENT_ERROR);
92
+ seen.add(flag);
93
+ if (flag === "--clone-missing") {
94
+ cloneMissing = true;
95
+ } else if (flag === "--replace-inventory") {
96
+ replaceInventory = true;
97
+ } else if (flag === "--inventory" || flag === "--repositories") {
98
+ const value = argv[index + 1];
99
+ if (!value || value.startsWith("--")) throw new LauncherInputError(ARGUMENT_ERROR);
100
+ index += 1;
101
+ if (flag === "--inventory") inventoryPath = value;
102
+ else repositories = value.split(",");
103
+ } else {
104
+ throw new LauncherInputError(ARGUMENT_ERROR);
105
+ }
56
106
  }
57
- if (rest.length === 0) return { help: false, cloneMissing };
58
- if (rest.length === 2 && rest[0] === "--inventory" && rest[1]) return { help: false, inventoryPath: rest[1], cloneMissing };
59
- throw new LauncherInputError(
60
- "launcher takes no arguments except optional --inventory <path> and/or --clone-missing; run it from the directory to create or appoint",
61
- );
107
+ return {
108
+ help: false,
109
+ ...(inventoryPath === undefined ? {} : { inventoryPath }),
110
+ ...(repositories === undefined ? {} : { repositories }),
111
+ replaceInventory,
112
+ cloneMissing,
113
+ };
62
114
  }
63
115
 
64
116
  /** Testable CLI dispatcher. Unknown arguments throw; the executable maps them to exit 2. */
@@ -69,7 +121,16 @@ export function main(argv: readonly string[], host: WorkspaceHost, skeletonRoot:
69
121
  return 0;
70
122
  }
71
123
  const observation = observeWorkspace(host);
124
+ // A bare `launcher`, with no flags at all, in a directory that is none of
125
+ // empty, a git repository, an existing hub, or the Foundry supplier tree
126
+ // itself: there is nothing to plan, so show usage and exit 0 -- that is
127
+ // someone finding out what this command does, not a refused request. But
128
+ // when a flag IS present (--inventory, --repositories, ...), the caller
129
+ // asked for something specific here; fall through to planWorkspace()
130
+ // instead, which refuses it with exit 1 and says why, the same as any
131
+ // other unsatisfiable request (#1179).
72
132
  if (
133
+ argv.length === 0 &&
73
134
  !observation.cwd.empty &&
74
135
  !observation.cwd.git &&
75
136
  observation.cwd.hub === undefined &&
@@ -78,14 +139,20 @@ export function main(argv: readonly string[], host: WorkspaceHost, skeletonRoot:
78
139
  console.log(USAGE);
79
140
  return 0;
80
141
  }
81
- const decision = planWorkspace(observation, host, { inventoryPath: parsed.inventoryPath });
142
+ const decision = planWorkspace(observation, host, {
143
+ ...(parsed.inventoryPath === undefined ? {} : { inventoryPath: parsed.inventoryPath }),
144
+ ...(parsed.repositories === undefined ? {} : { repositories: parsed.repositories }),
145
+ ...(parsed.replaceInventory ? { replaceInventory: true } : {}),
146
+ });
82
147
  if (decision.action === "refuse") {
83
148
  console.error(`launcher: ${decision.message}`);
84
149
  return exitCodeFor(decision.state);
85
150
  }
86
151
  if (parsed.inventoryPath !== undefined && decision.action !== "adopt") {
87
152
  if (decision.action === "resume") {
88
- console.error("launcher: this hub is already appointed; edit clossys/.state/inventory.json to change its inventory");
153
+ console.error(
154
+ "launcher: this hub is already appointed; to change the repositories it covers, choose them again on Advisor's repository card and run launcher --repositories <owner/name>[,<owner/name>...] -- never hand-edit clossys/.state/inventory.json",
155
+ );
89
156
  } else {
90
157
  console.error("launcher: --inventory is only valid when appointing a GitHub repository");
91
158
  }
@@ -104,7 +171,8 @@ export function main(argv: readonly string[], host: WorkspaceHost, skeletonRoot:
104
171
  const outcomes = cloneMissingInventoryRepositories(host, decision.directory, decision.owner);
105
172
  for (const outcome of outcomes) {
106
173
  if (outcome.result === "skipped-other-reason") continue;
107
- console.log(`clone-missing (${outcome.inventoryId}): ${outcome.result} -- ${outcome.note}`);
174
+ // Named by position, never by outcome.inventoryId itself: that id came from the stored inventory, an agent-read document (see core.ts's classifyInventoriedSiblings).
175
+ console.log(`clone-missing (${inventoryPositionLabel(outcome.inventoryId, outcome.position)}): ${outcome.result} -- ${outcome.note}`);
108
176
  }
109
177
  }
110
178
  return 0;