@clossys/launcher 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1324 -60
- package/contracts/conversation-contract.md +2 -2
- package/contracts/product-ci-workflow.yml +74 -0
- package/contracts/repository-inventory.json +53 -0
- package/dist/admission-fixture.d.ts +168 -0
- package/dist/admission-fixture.d.ts.map +1 -0
- package/dist/admission-fixture.js +453 -0
- package/dist/admission-fixture.js.map +1 -0
- package/dist/admission.d.ts +124 -0
- package/dist/admission.d.ts.map +1 -0
- package/dist/admission.js +799 -0
- package/dist/admission.js.map +1 -0
- package/dist/agents-guide.d.ts +9 -0
- package/dist/agents-guide.d.ts.map +1 -0
- package/dist/agents-guide.js +26 -0
- package/dist/agents-guide.js.map +1 -0
- package/dist/apply-command-options.check.d.ts +12 -0
- package/dist/apply-command-options.check.d.ts.map +1 -0
- package/dist/apply-command-options.check.js +20 -0
- package/dist/apply-command-options.check.js.map +1 -0
- package/dist/apply-plan-cli.d.ts +39 -1
- package/dist/apply-plan-cli.d.ts.map +1 -1
- package/dist/apply-plan-cli.js +432 -15
- package/dist/apply-plan-cli.js.map +1 -1
- package/dist/apply-plan.d.ts +46 -59
- package/dist/apply-plan.d.ts.map +1 -1
- package/dist/apply-plan.js +112 -97
- package/dist/apply-plan.js.map +1 -1
- package/dist/apply-step-fixture.d.ts +87 -0
- package/dist/apply-step-fixture.d.ts.map +1 -0
- package/dist/apply-step-fixture.js +199 -0
- package/dist/apply-step-fixture.js.map +1 -0
- package/dist/apply-store.d.ts +93 -0
- package/dist/apply-store.d.ts.map +1 -0
- package/dist/apply-store.js +625 -0
- package/dist/apply-store.js.map +1 -0
- package/dist/approval-sheet.d.ts +21 -0
- package/dist/approval-sheet.d.ts.map +1 -0
- package/dist/approval-sheet.js +157 -0
- package/dist/approval-sheet.js.map +1 -0
- package/dist/body-command.d.ts +42 -0
- package/dist/body-command.d.ts.map +1 -0
- package/dist/body-command.js +143 -0
- package/dist/body-command.js.map +1 -0
- package/dist/change-set-contract.d.ts +381 -0
- package/dist/change-set-contract.d.ts.map +1 -0
- package/dist/change-set-contract.js +738 -0
- package/dist/change-set-contract.js.map +1 -0
- package/dist/change-set-digest.d.ts +28 -0
- package/dist/change-set-digest.d.ts.map +1 -0
- package/dist/change-set-digest.js +65 -0
- package/dist/change-set-digest.js.map +1 -0
- package/dist/check-cli.d.ts.map +1 -1
- package/dist/check-cli.js +14 -3
- package/dist/check-cli.js.map +1 -1
- package/dist/cli.d.ts +17 -6
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +84 -23
- package/dist/cli.js.map +1 -1
- package/dist/core.d.ts +79 -22
- package/dist/core.d.ts.map +1 -1
- package/dist/core.js +843 -268
- package/dist/core.js.map +1 -1
- package/dist/dry-materialize.d.ts +63 -0
- package/dist/dry-materialize.d.ts.map +1 -0
- package/dist/dry-materialize.js +330 -0
- package/dist/dry-materialize.js.map +1 -0
- package/dist/generated/contract-schema.generated.d.ts +97 -0
- package/dist/generated/contract-schema.generated.d.ts.map +1 -0
- package/dist/generated/contract-schema.generated.js +496 -0
- package/dist/generated/contract-schema.generated.js.map +1 -0
- package/dist/generated/package-scope.generated.d.ts +6 -0
- package/dist/generated/package-scope.generated.d.ts.map +1 -0
- package/dist/generated/package-scope.generated.js +10 -0
- package/dist/generated/package-scope.generated.js.map +1 -0
- package/dist/generated/plan-contracts.generated.d.ts +3 -0
- package/dist/generated/plan-contracts.generated.d.ts.map +1 -0
- package/dist/generated/plan-contracts.generated.js +2840 -0
- package/dist/generated/plan-contracts.generated.js.map +1 -0
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +11 -0
- package/dist/host.js.map +1 -1
- package/dist/identity.d.ts +15 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +48 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +34 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -2
- package/dist/index.js.map +1 -1
- package/dist/inventory-adoption.d.ts +24 -5
- package/dist/inventory-adoption.d.ts.map +1 -1
- package/dist/inventory-adoption.js +70 -25
- package/dist/inventory-adoption.js.map +1 -1
- package/dist/inventory-choice.d.ts +40 -0
- package/dist/inventory-choice.d.ts.map +1 -0
- package/dist/inventory-choice.js +156 -0
- package/dist/inventory-choice.js.map +1 -0
- package/dist/inventory-contract.d.ts +89 -0
- package/dist/inventory-contract.d.ts.map +1 -0
- package/dist/inventory-contract.js +121 -0
- package/dist/inventory-contract.js.map +1 -0
- package/dist/key-editor.d.ts +30 -0
- package/dist/key-editor.d.ts.map +1 -0
- package/dist/key-editor.js +445 -0
- package/dist/key-editor.js.map +1 -0
- package/dist/ledger-contract.d.ts +187 -0
- package/dist/ledger-contract.d.ts.map +1 -0
- package/dist/ledger-contract.js +532 -0
- package/dist/ledger-contract.js.map +1 -0
- package/dist/ledger-trust.d.ts +90 -0
- package/dist/ledger-trust.d.ts.map +1 -0
- package/dist/ledger-trust.js +198 -0
- package/dist/ledger-trust.js.map +1 -0
- package/dist/lockfile-invariants.d.ts +48 -0
- package/dist/lockfile-invariants.d.ts.map +1 -0
- package/dist/lockfile-invariants.js +375 -0
- package/dist/lockfile-invariants.js.map +1 -0
- package/dist/lockfile-readers.d.ts +72 -0
- package/dist/lockfile-readers.d.ts.map +1 -0
- package/dist/lockfile-readers.js +713 -0
- package/dist/lockfile-readers.js.map +1 -0
- package/dist/lockfile-regen.d.ts +106 -0
- package/dist/lockfile-regen.d.ts.map +1 -0
- package/dist/lockfile-regen.js +760 -0
- package/dist/lockfile-regen.js.map +1 -0
- package/dist/lockfile-tool-env.d.ts +29 -0
- package/dist/lockfile-tool-env.d.ts.map +1 -0
- package/dist/lockfile-tool-env.js +111 -0
- package/dist/lockfile-tool-env.js.map +1 -0
- package/dist/materialize.d.ts +113 -0
- package/dist/materialize.d.ts.map +1 -0
- package/dist/materialize.js +840 -0
- package/dist/materialize.js.map +1 -0
- package/dist/observe-repository.d.ts +90 -0
- package/dist/observe-repository.d.ts.map +1 -0
- package/dist/observe-repository.js +1367 -0
- package/dist/observe-repository.js.map +1 -0
- package/dist/plan-bundle-setup-fixture.d.ts +68 -0
- package/dist/plan-bundle-setup-fixture.d.ts.map +1 -0
- package/dist/plan-bundle-setup-fixture.js +167 -0
- package/dist/plan-bundle-setup-fixture.js.map +1 -0
- package/dist/plan-bundle.d.ts +250 -0
- package/dist/plan-bundle.d.ts.map +1 -0
- package/dist/plan-bundle.js +827 -0
- package/dist/plan-bundle.js.map +1 -0
- package/dist/plan-command.d.ts +29 -0
- package/dist/plan-command.d.ts.map +1 -0
- package/dist/plan-command.js +493 -0
- package/dist/plan-command.js.map +1 -0
- package/dist/plan-contract.d.ts +153 -0
- package/dist/plan-contract.d.ts.map +1 -0
- package/dist/plan-contract.js +61 -0
- package/dist/plan-contract.js.map +1 -0
- package/dist/plan-digest.d.ts +25 -0
- package/dist/plan-digest.d.ts.map +1 -0
- package/dist/plan-digest.js +106 -0
- package/dist/plan-digest.js.map +1 -0
- package/dist/plan-rules.d.ts +23 -0
- package/dist/plan-rules.d.ts.map +1 -0
- package/dist/plan-rules.js +177 -0
- package/dist/plan-rules.js.map +1 -0
- package/dist/planned-bundle.d.ts +20 -0
- package/dist/planned-bundle.d.ts.map +1 -0
- package/dist/planned-bundle.js +191 -0
- package/dist/planned-bundle.js.map +1 -0
- package/dist/product-repository.d.ts +4 -0
- package/dist/product-repository.d.ts.map +1 -1
- package/dist/product-repository.js +9 -1
- package/dist/product-repository.js.map +1 -1
- package/dist/provenance-gate.d.ts +48 -0
- package/dist/provenance-gate.d.ts.map +1 -0
- package/dist/provenance-gate.js +324 -0
- package/dist/provenance-gate.js.map +1 -0
- package/dist/pull-request-body.d.ts +45 -0
- package/dist/pull-request-body.d.ts.map +1 -0
- package/dist/pull-request-body.js +232 -0
- package/dist/pull-request-body.js.map +1 -0
- package/dist/registry-snapshot.d.ts +141 -0
- package/dist/registry-snapshot.d.ts.map +1 -0
- package/dist/registry-snapshot.js +483 -0
- package/dist/registry-snapshot.js.map +1 -0
- package/dist/release-age-edit.d.ts +52 -0
- package/dist/release-age-edit.d.ts.map +1 -0
- package/dist/release-age-edit.js +413 -0
- package/dist/release-age-edit.js.map +1 -0
- package/dist/root-entries.d.ts +36 -0
- package/dist/root-entries.d.ts.map +1 -0
- package/dist/root-entries.js +80 -0
- package/dist/root-entries.js.map +1 -0
- package/dist/setup-template-scripts.d.ts +34 -0
- package/dist/setup-template-scripts.d.ts.map +1 -0
- package/dist/setup-template-scripts.js +557 -0
- package/dist/setup-template-scripts.js.map +1 -0
- package/dist/setup-templates.d.ts +54 -0
- package/dist/setup-templates.d.ts.map +1 -0
- package/dist/setup-templates.js +427 -0
- package/dist/setup-templates.js.map +1 -0
- package/dist/skills.d.ts +34 -1
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +129 -17
- package/dist/skills.js.map +1 -1
- package/dist/status.d.ts +63 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +539 -0
- package/dist/status.js.map +1 -0
- package/dist/types.d.ts +151 -13
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/skeleton/README.md +14 -9
- package/skeleton/package.json +2 -1
- package/skill/SKILL.md +23 -7
- package/skill-catalogue/advisor/SKILL.md +59 -6
- package/skill-catalogue/architect/SKILL.md +2 -2
- package/skill-catalogue/bouncer/SKILL.md +2 -2
- package/skill-catalogue/builder/SKILL.md +2 -2
- package/skill-catalogue/butler/SKILL.md +2 -2
- package/skill-catalogue/controller/SKILL.md +2 -2
- package/skill-catalogue/customer/SKILL.md +2 -2
- package/skill-catalogue/designer/SKILL.md +4 -2
- package/skill-catalogue/giver/SKILL.md +2 -2
- package/skill-catalogue/influencer/SKILL.md +2 -2
- package/skill-catalogue/inspector/SKILL.md +2 -2
- package/skill-catalogue/integrator/SKILL.md +2 -2
- package/skill-catalogue/keeper/SKILL.md +2 -2
- package/skill-catalogue/launcher/SKILL.md +23 -7
- package/skill-catalogue/locksmith/SKILL.md +2 -2
- package/skill-catalogue/messenger/SKILL.md +2 -2
- package/skill-catalogue/observer/SKILL.md +2 -2
- package/skill-catalogue/publisher/SKILL.md +2 -2
- package/skill-catalogue/starter/SKILL.md +3 -2
- package/skill-catalogue/strategist/SKILL.md +12 -4
- package/skill-catalogue/writer/SKILL.md +2 -2
- package/src/admission-fixture.ts +572 -0
- package/src/admission.ts +816 -0
- package/src/agents-guide.ts +29 -0
- package/src/apply-command-options.check.ts +27 -0
- package/src/apply-plan-cli.ts +454 -14
- package/src/apply-plan.ts +112 -124
- package/src/apply-step-fixture.ts +236 -0
- package/src/apply-store.ts +584 -0
- package/src/approval-sheet.ts +164 -0
- package/src/body-command.ts +162 -0
- package/src/change-set-contract.ts +937 -0
- package/src/change-set-digest.ts +70 -0
- package/src/check-cli.ts +14 -3
- package/src/cli.ts +90 -22
- package/src/core.ts +973 -275
- package/src/dry-materialize.ts +353 -0
- package/src/generated/contract-schema.generated.ts +520 -0
- package/src/generated/package-scope.generated.ts +10 -0
- package/src/generated/plan-contracts.generated.ts +2840 -0
- package/src/host.ts +10 -0
- package/src/identity.ts +51 -0
- package/src/index.ts +72 -3
- package/src/inventory-adoption.ts +107 -29
- package/src/inventory-choice.ts +172 -0
- package/src/inventory-contract.ts +166 -0
- package/src/key-editor.ts +446 -0
- package/src/ledger-contract.ts +637 -0
- package/src/ledger-trust.ts +267 -0
- package/src/lockfile-invariants.ts +421 -0
- package/src/lockfile-readers.ts +749 -0
- package/src/lockfile-regen.ts +851 -0
- package/src/lockfile-tool-env.ts +131 -0
- package/src/materialize.ts +886 -0
- package/src/observe-repository.ts +1365 -0
- package/src/plan-bundle-setup-fixture.ts +200 -0
- package/src/plan-bundle.ts +964 -0
- package/src/plan-command.ts +509 -0
- package/src/plan-contract.ts +179 -0
- package/src/plan-digest.ts +102 -0
- package/src/plan-rules.ts +188 -0
- package/src/planned-bundle.ts +211 -0
- package/src/product-repository.ts +10 -1
- package/src/provenance-gate.ts +352 -0
- package/src/pull-request-body.ts +261 -0
- package/src/registry-snapshot.ts +534 -0
- package/src/release-age-edit.ts +430 -0
- package/src/root-entries.ts +81 -0
- package/src/setup-template-scripts.ts +571 -0
- package/src/setup-templates.ts +471 -0
- package/src/skills.ts +161 -18
- package/src/status.ts +557 -0
- 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
|
|
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
|
-
|
|
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
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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, {
|
|
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(
|
|
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
|
-
|
|
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;
|