@clossys/launcher 0.3.0 → 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 +1328 -60
- package/contracts/conversation-contract.md +2 -1
- 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 -5
- package/skeleton/README.md +14 -9
- package/skeleton/package.json +2 -1
- package/skill/SKILL.md +20 -16
- package/skill-catalogue/advisor/SKILL.md +100 -11
- package/skill-catalogue/architect/SKILL.md +2 -13
- package/skill-catalogue/bouncer/SKILL.md +2 -13
- package/skill-catalogue/builder/SKILL.md +2 -13
- package/skill-catalogue/butler/SKILL.md +2 -13
- package/skill-catalogue/controller/SKILL.md +2 -13
- package/skill-catalogue/customer/SKILL.md +4 -13
- package/skill-catalogue/designer/SKILL.md +9 -14
- package/skill-catalogue/giver/SKILL.md +2 -13
- package/skill-catalogue/influencer/SKILL.md +2 -13
- package/skill-catalogue/inspector/SKILL.md +2 -13
- package/skill-catalogue/integrator/SKILL.md +2 -13
- package/skill-catalogue/keeper/SKILL.md +2 -13
- package/skill-catalogue/launcher/SKILL.md +20 -16
- package/skill-catalogue/locksmith/SKILL.md +2 -13
- package/skill-catalogue/messenger/SKILL.md +2 -13
- package/skill-catalogue/observer/SKILL.md +2 -13
- package/skill-catalogue/publisher/SKILL.md +11 -17
- package/skill-catalogue/starter/SKILL.md +3 -13
- package/skill-catalogue/strategist/SKILL.md +14 -17
- package/skill-catalogue/writer/SKILL.md +7 -14
- 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
- package/CHANGELOG.md +0 -131
package/src/skills.ts
CHANGED
|
@@ -6,10 +6,36 @@ import type { SkillManifestEntry, WorkspaceHost } from "./types.js";
|
|
|
6
6
|
export interface SkillCompositionResult {
|
|
7
7
|
readonly composed: readonly string[];
|
|
8
8
|
readonly skipped: readonly { readonly packageDir: string; readonly note: string }[];
|
|
9
|
+
/** The checkouts this run composed skills into: the hub. */
|
|
9
10
|
readonly rosterTargets?: readonly string[];
|
|
10
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Each inventoried repository other than the hub, with what a hub run
|
|
13
|
+
* found for it. A hub run never writes into one: a product repository's
|
|
14
|
+
* team arrives only once it is staffed in an approved plan, with that
|
|
15
|
+
* plan's setup pull request. `inventoryId` is already a stored-inventory
|
|
16
|
+
* position label (e.g. `repositories[0] in the stored inventory`), never
|
|
17
|
+
* the raw id -- populated by ./core.ts's composeSkillRoster; see its own
|
|
18
|
+
* doc comment.
|
|
19
|
+
*/
|
|
20
|
+
readonly siblings?: readonly { readonly inventoryId: string; readonly note: string }[];
|
|
11
21
|
/** Skills pruned this run: present in the directory's previous manifest, absent from this run's composed set. */
|
|
12
22
|
readonly retired: readonly string[];
|
|
23
|
+
/**
|
|
24
|
+
* Skills this run left exactly as found instead of rewriting or retiring them,
|
|
25
|
+
* because a file at their composed location is not one Launcher can show it
|
|
26
|
+
* wrote (#1473). Each needs the client's decision; see `note`.
|
|
27
|
+
*/
|
|
28
|
+
readonly preserved: readonly SkillPreservation[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** One composed skill left untouched because its on-disk content is not provably Launcher's (#1473). */
|
|
32
|
+
export interface SkillPreservation {
|
|
33
|
+
readonly packageDir: string;
|
|
34
|
+
/** What this run would have done to the skill had Launcher still owned it. */
|
|
35
|
+
readonly action: "rewrite" | "retire";
|
|
36
|
+
/** The file or directory that failed the ownership check, relative to the composed directory. */
|
|
37
|
+
readonly path: string;
|
|
38
|
+
readonly note: string;
|
|
13
39
|
}
|
|
14
40
|
|
|
15
41
|
export interface ComposeSkillsOptions {
|
|
@@ -123,17 +149,14 @@ export function listSkillPackageCandidates(host: WorkspaceHost, options: Compose
|
|
|
123
149
|
return [...names].sort((a, b) => a.localeCompare(b));
|
|
124
150
|
}
|
|
125
151
|
|
|
126
|
-
function writeAgentsSkill(host: WorkspaceHost, directory: string, packageDir: string,
|
|
127
|
-
const
|
|
128
|
-
const skillRel = join(relativeDir, "SKILL.md");
|
|
129
|
-
const target = containedPath(directory, skillRel);
|
|
152
|
+
function writeAgentsSkill(host: WorkspaceHost, directory: string, packageDir: string, content: string): void {
|
|
153
|
+
const target = containedPath(directory, join(agentsSkillDirRel(packageDir), "SKILL.md"));
|
|
130
154
|
host.mkdirp(dirname(target));
|
|
131
|
-
host.writeText(target,
|
|
155
|
+
host.writeText(target, content);
|
|
132
156
|
}
|
|
133
157
|
|
|
134
|
-
function writeDiscoveryLink(host: WorkspaceHost, directory: string, packageDir: string,
|
|
135
|
-
const
|
|
136
|
-
const linkTarget = join("..", "..", agentsDirRel);
|
|
158
|
+
function writeDiscoveryLink(host: WorkspaceHost, directory: string, packageDir: string, content: string): void {
|
|
159
|
+
const linkTarget = join("..", "..", agentsSkillDirRel(packageDir));
|
|
137
160
|
for (const prefix of DISCOVERY_PREFIXES) {
|
|
138
161
|
const prefixPath = containedPath(directory, prefix);
|
|
139
162
|
// A directory-symlink onto `.agents/skills` already exposes every composed
|
|
@@ -148,14 +171,99 @@ function writeDiscoveryLink(host: WorkspaceHost, directory: string, packageDir:
|
|
|
148
171
|
} catch {
|
|
149
172
|
const fallbackDir = containedPath(directory, join(linkRel, "SKILL.md"));
|
|
150
173
|
host.mkdirp(dirname(fallbackDir));
|
|
151
|
-
host.writeText(fallbackDir,
|
|
174
|
+
host.writeText(fallbackDir, content);
|
|
152
175
|
}
|
|
153
176
|
}
|
|
154
177
|
}
|
|
155
178
|
|
|
179
|
+
function agentsSkillDirRel(packageDir: string): string {
|
|
180
|
+
return join(AGENTS_SKILLS_REL, `clossys-${packageDir}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* The on-disk locations that hold a copy of this skill's composed content:
|
|
185
|
+
* `.agents/skills/clossys-<pkg>/`, plus any host discovery path that is a real
|
|
186
|
+
* directory rather than a symlink (the copy `writeDiscoveryLink` falls back to
|
|
187
|
+
* when a symlink cannot be made, or a directory someone else put there). A
|
|
188
|
+
* discovery symlink holds no content of its own and is not listed.
|
|
189
|
+
*/
|
|
190
|
+
function skillCopyLocations(host: WorkspaceHost, directory: string, packageDir: string): { rel: string; replacedWhole: boolean }[] {
|
|
191
|
+
const locations: { rel: string; replacedWhole: boolean }[] = [{ rel: agentsSkillDirRel(packageDir), replacedWhole: false }];
|
|
192
|
+
for (const prefix of DISCOVERY_PREFIXES) {
|
|
193
|
+
if (host.isSymlink(containedPath(directory, prefix))) continue;
|
|
194
|
+
const rel = join(prefix, `clossys-${packageDir}`);
|
|
195
|
+
const path = containedPath(directory, rel);
|
|
196
|
+
if (host.isSymlink(path) || !host.exists(path)) continue;
|
|
197
|
+
// `host.symlink` removes whatever sits at a discovery path before linking, so
|
|
198
|
+
// everything in that directory is replaced, not just SKILL.md.
|
|
199
|
+
locations.push({ rel, replacedWhole: true });
|
|
200
|
+
}
|
|
201
|
+
return locations;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Decides whether Launcher may rewrite (`intendedBody` given) or remove
|
|
206
|
+
* (`intendedBody` undefined) a composed skill (#1473). The digest recorded in
|
|
207
|
+
* the previous manifest is the ownership record: a copy whose bytes hash to it
|
|
208
|
+
* is still exactly what Launcher last wrote. A copy byte-identical to what this
|
|
209
|
+
* run would write is also safe to adopt, since nothing would be lost. Anything
|
|
210
|
+
* else is the client's, and is left alone. Returns the first location that
|
|
211
|
+
* fails, or null when every location may be changed.
|
|
212
|
+
*/
|
|
213
|
+
function unownedSkillCopy(
|
|
214
|
+
host: WorkspaceHost,
|
|
215
|
+
directory: string,
|
|
216
|
+
packageDir: string,
|
|
217
|
+
recordedSha256: string | undefined,
|
|
218
|
+
intendedBody: string | undefined,
|
|
219
|
+
): { path: string; location: string; reason: string } | null {
|
|
220
|
+
const removing = intendedBody === undefined;
|
|
221
|
+
for (const location of skillCopyLocations(host, directory, packageDir)) {
|
|
222
|
+
const dirPath = containedPath(directory, location.rel);
|
|
223
|
+
if (!host.exists(dirPath)) continue;
|
|
224
|
+
if (!host.isDirectory(dirPath)) {
|
|
225
|
+
return { path: location.rel, location: location.rel, reason: "is not a directory Launcher wrote" };
|
|
226
|
+
}
|
|
227
|
+
if (removing || location.replacedWhole) {
|
|
228
|
+
// .DS_Store is macOS Finder metadata, written just by viewing the folder; it holds no client content.
|
|
229
|
+
const others = host.readDir(dirPath).filter((name) => name !== "SKILL.md" && name !== ".DS_Store");
|
|
230
|
+
if (others.length > 0) {
|
|
231
|
+
return { path: location.rel, location: location.rel, reason: `holds files Launcher did not write (${others.sort().join(", ")})` };
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const skillRel = join(location.rel, "SKILL.md");
|
|
235
|
+
const skillPath = containedPath(directory, skillRel);
|
|
236
|
+
const current = host.readText(skillPath);
|
|
237
|
+
if (current === null) {
|
|
238
|
+
// readText returns null for any read failure (EACCES, EISDIR, ...), not only a missing file.
|
|
239
|
+
if (!host.exists(skillPath)) continue;
|
|
240
|
+
return { path: skillRel, location: location.rel, reason: "exists but could not be read, so Launcher cannot check it against its digest" };
|
|
241
|
+
}
|
|
242
|
+
if (recordedSha256 !== undefined && sha256Hex(current) === recordedSha256) continue;
|
|
243
|
+
if (intendedBody !== undefined && current === intendedBody) continue;
|
|
244
|
+
return {
|
|
245
|
+
path: skillRel,
|
|
246
|
+
location: location.rel,
|
|
247
|
+
reason:
|
|
248
|
+
recordedSha256 === undefined
|
|
249
|
+
? "has no digest recorded in clossys/.state/skills.json, so Launcher cannot show it wrote this file"
|
|
250
|
+
: "was edited since Launcher last wrote it (its content no longer matches the digest in clossys/.state/skills.json)",
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function preservationNote(action: "rewrite" | "retire", unowned: { path: string; location: string; reason: string }): string {
|
|
257
|
+
const remedy =
|
|
258
|
+
action === "rewrite"
|
|
259
|
+
? `to take Launcher's current version instead, move your copy aside, delete \`${unowned.location}\`, and run launcher again`
|
|
260
|
+
: `Launcher no longer composes this skill; delete \`${unowned.location}\` yourself if you no longer want it, and the next run completes the retirement`;
|
|
261
|
+
return `${unowned.path} ${unowned.reason}; left as is -- ${remedy}`;
|
|
262
|
+
}
|
|
263
|
+
|
|
156
264
|
/** Removes a composed skill's `.agents/skills/` directory and its host discovery links. Missing paths are no-ops. */
|
|
157
265
|
function removeComposedSkill(host: WorkspaceHost, directory: string, packageDir: string): void {
|
|
158
|
-
host.remove(containedPath(directory,
|
|
266
|
+
host.remove(containedPath(directory, agentsSkillDirRel(packageDir)));
|
|
159
267
|
for (const prefix of DISCOVERY_PREFIXES) {
|
|
160
268
|
const prefixPath = containedPath(directory, prefix);
|
|
161
269
|
if (host.isSymlink(prefixPath)) continue; // shared view onto .agents/skills; already handled above
|
|
@@ -174,6 +282,14 @@ function removeComposedSkill(host: WorkspaceHost, directory: string, packageDir:
|
|
|
174
282
|
* but not composed this run is retired — its composed output is pruned and it is
|
|
175
283
|
* dropped from the new manifest. Never touches a skill this directory's manifest
|
|
176
284
|
* did not itself write.
|
|
285
|
+
*
|
|
286
|
+
* Before rewriting or retiring a skill, the on-disk copy is checked against the
|
|
287
|
+
* digest that manifest recorded (#1473). A copy that no longer matches (the
|
|
288
|
+
* client edited it), or one with no recorded digest that differs from what this
|
|
289
|
+
* run would write, is left exactly as found and reported in `preserved`; its
|
|
290
|
+
* previous manifest entry, if any, is carried forward unchanged so the next run
|
|
291
|
+
* checks it again. A missing copy is recreated: `.agents/skills` is
|
|
292
|
+
* Launcher-generated output, and recreating it loses no client content.
|
|
177
293
|
*/
|
|
178
294
|
export function composeSkills(
|
|
179
295
|
host: WorkspaceHost,
|
|
@@ -182,9 +298,13 @@ export function composeSkills(
|
|
|
182
298
|
): SkillCompositionResult {
|
|
183
299
|
const composed: string[] = [];
|
|
184
300
|
const skipped: { packageDir: string; note: string }[] = [];
|
|
301
|
+
const preserved: SkillPreservation[] = [];
|
|
185
302
|
const manifestEntries: SkillManifestEntry[] = [];
|
|
186
303
|
const contractBlock = readContractBlock(host, options);
|
|
187
304
|
const launcherVersion = readLauncherVersion(host, options);
|
|
305
|
+
const previousManifest = parseSkillManifest(host.readText(join(directory, SKILLS_MANIFEST_REL)));
|
|
306
|
+
const previousEntries = new Map((previousManifest?.skills ?? []).map((entry) => [entry.name, entry]));
|
|
307
|
+
const handled = new Set<string>();
|
|
188
308
|
|
|
189
309
|
for (const packageDir of listSkillPackageCandidates(host, options)) {
|
|
190
310
|
const found = readSkillBody(host, packageDir, options, directory);
|
|
@@ -193,23 +313,46 @@ export function composeSkills(
|
|
|
193
313
|
continue;
|
|
194
314
|
}
|
|
195
315
|
const body = contractBlock === null ? found.body : injectContract(found.body, contractBlock);
|
|
196
|
-
|
|
197
|
-
|
|
316
|
+
const content = body.endsWith("\n") ? body : `${body}\n`;
|
|
317
|
+
const previous = previousEntries.get(packageDir);
|
|
318
|
+
handled.add(packageDir);
|
|
319
|
+
const unowned = unownedSkillCopy(host, directory, packageDir, previous?.sha256, content);
|
|
320
|
+
if (unowned !== null) {
|
|
321
|
+
preserved.push({
|
|
322
|
+
packageDir,
|
|
323
|
+
action: "rewrite",
|
|
324
|
+
path: unowned.path,
|
|
325
|
+
note: preservationNote("rewrite", unowned),
|
|
326
|
+
});
|
|
327
|
+
if (previous !== undefined) manifestEntries.push(previous);
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
writeAgentsSkill(host, directory, packageDir, content);
|
|
331
|
+
writeDiscoveryLink(host, directory, packageDir, content);
|
|
198
332
|
composed.push(packageDir);
|
|
199
333
|
const version = found.source === "installed" ? readInstalledVersion(host, directory, packageDir) : launcherVersion;
|
|
200
334
|
manifestEntries.push({
|
|
201
335
|
name: packageDir,
|
|
202
336
|
source: found.source,
|
|
203
|
-
sha256: sha256Hex(
|
|
337
|
+
sha256: sha256Hex(content),
|
|
204
338
|
...(version === undefined ? {} : { version }),
|
|
205
339
|
});
|
|
206
340
|
}
|
|
207
341
|
|
|
208
|
-
const previousManifest = parseSkillManifest(host.readText(join(directory, SKILLS_MANIFEST_REL)));
|
|
209
|
-
const composedSet = new Set(composed);
|
|
210
342
|
const retired: string[] = [];
|
|
211
343
|
for (const entry of previousManifest?.skills ?? []) {
|
|
212
|
-
if (
|
|
344
|
+
if (handled.has(entry.name)) continue;
|
|
345
|
+
const unowned = unownedSkillCopy(host, directory, entry.name, entry.sha256, undefined);
|
|
346
|
+
if (unowned !== null) {
|
|
347
|
+
preserved.push({
|
|
348
|
+
packageDir: entry.name,
|
|
349
|
+
action: "retire",
|
|
350
|
+
path: unowned.path,
|
|
351
|
+
note: preservationNote("retire", unowned),
|
|
352
|
+
});
|
|
353
|
+
manifestEntries.push(entry);
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
213
356
|
removeComposedSkill(host, directory, entry.name);
|
|
214
357
|
retired.push(entry.name);
|
|
215
358
|
}
|
|
@@ -221,5 +364,5 @@ export function composeSkills(
|
|
|
221
364
|
serializeSkillManifest({ schemaVersion: 1, generatedAt: host.now(), skills: manifestEntries }),
|
|
222
365
|
);
|
|
223
366
|
|
|
224
|
-
return { composed, skipped, retired };
|
|
367
|
+
return { composed, skipped, retired, preserved };
|
|
225
368
|
}
|