silvermoon 0.0.1 → 0.0.2

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/src/adoption.js CHANGED
@@ -1,313 +1,313 @@
1
- import { createHash } from "node:crypto";
2
- import {
3
- lstat,
4
- readFile,
5
- readdir,
6
- realpath,
7
- } from "node:fs/promises";
8
- import { dirname, relative, resolve, sep } from "node:path";
9
- import { fileURLToPath } from "node:url";
10
-
11
- import { loadConfig } from "./config.js";
12
- import { runGit } from "./git.js";
13
-
14
- const packageRoot = fileURLToPath(new URL("..", import.meta.url));
15
- const packageJson = JSON.parse(await readFile(resolve(packageRoot, "package.json"), "utf8"));
16
- export const SILVERMOON_VERSION = packageJson.version;
17
- export const REPOSITORY_SKILL_PATHS = [
18
- ".agents/skills/silvermoon",
19
- ".github/skills/silvermoon",
20
- ];
21
-
22
- async function metadata(path) {
23
- try {
24
- return await lstat(path);
25
- } catch (error) {
26
- if (error.code === "ENOENT") return null;
27
- throw error;
28
- }
29
- }
30
-
31
- async function jsonFile(path) {
32
- try {
33
- return JSON.parse(await readFile(path, "utf8"));
34
- } catch (error) {
35
- if (error.code === "ENOENT") return null;
36
- return { __invalid: error.message };
37
- }
38
- }
39
-
40
- async function directoryDigest(root) {
41
- const hash = createHash("sha256");
42
- async function visit(directory) {
43
- const entries = (await readdir(directory, { withFileTypes: true }))
44
- .sort((left, right) => left.name.localeCompare(right.name));
45
- for (const entry of entries) {
46
- const path = resolve(directory, entry.name);
47
- if (entry.isDirectory()) {
48
- await visit(path);
49
- } else if (entry.isFile()) {
50
- hash.update(relative(root, path).split(sep).join("/"));
51
- hash.update("\0");
52
- hash.update(await readFile(path));
53
- hash.update("\0");
54
- } else {
55
- throw new Error(`Skill contains a non-regular path: ${path}`);
56
- }
57
- }
58
- }
59
- await visit(root);
60
- return hash.digest("hex");
61
- }
62
-
63
- async function skillDigest(path) {
64
- const entry = await lstat(path);
65
- if (entry.isFile()) {
66
- const pointer = (await readFile(path, "utf8")).trim();
67
- if (!pointer) throw new Error(`Skill pointer is empty: ${path}`);
68
- return directoryDigest(resolve(dirname(path), pointer));
69
- }
70
- if (!entry.isDirectory() && !entry.isSymbolicLink()) {
71
- throw new Error(`Skill discovery path is not a directory: ${path}`);
72
- }
73
- return directoryDigest(path);
74
- }
75
-
76
- function command(executable, args, description) {
77
- return { kind: "command", executable, args, description };
78
- }
79
-
80
- function manual(path, description) {
81
- return { kind: "manual", path, description };
82
- }
83
-
84
- function requirement(id, title, status, blocking, dependencies, observed, remediation = null) {
85
- return { id, title, status, blocking, dependencies, observed, remediation };
86
- }
87
-
88
- async function detectPackageManager(root, manifest) {
89
- for (const [file, name] of [
90
- ["pnpm-lock.yaml", "pnpm"],
91
- ["yarn.lock", "yarn"],
92
- ["bun.lock", "bun"],
93
- ["bun.lockb", "bun"],
94
- ["package-lock.json", "npm"],
95
- ["npm-shrinkwrap.json", "npm"],
96
- ]) {
97
- if (await metadata(resolve(root, file))) return name;
98
- }
99
- const declared = manifest?.packageManager?.split("@")[0];
100
- return ["pnpm", "npm", "yarn", "bun"].includes(declared) ? declared : "npm";
101
- }
102
-
103
- export async function detectExecutionSource({
104
- root,
105
- runtimeRoot = packageRoot,
106
- } = {}) {
107
- const runtime = await realpath(runtimeRoot);
108
- let repository;
109
- try {
110
- repository = await realpath(root);
111
- } catch {
112
- repository = resolve(root);
113
- }
114
- let local = null;
115
- try {
116
- local = await realpath(resolve(repository, "node_modules", "silvermoon"));
117
- } catch (error) {
118
- if (error.code !== "ENOENT") throw error;
119
- }
120
- const normalizedRuntime = runtime.toLowerCase();
121
- const normalizedRoot = repository.toLowerCase();
122
- const normalizedLocal = local?.toLowerCase() ?? null;
123
- let kind = "unknown";
124
- if (normalizedRuntime === normalizedRoot && await metadata(resolve(runtime, ".git"))) {
125
- kind = "source-checkout";
126
- } else if (normalizedRuntime === normalizedLocal) {
127
- kind = "project-local";
128
- } else if (/(^|[\\/])_npx([\\/]|$)/i.test(runtime)) {
129
- kind = "temporary";
130
- } else if (normalizedRuntime.includes(`${sep}node_modules${sep}`) && !normalizedRuntime.startsWith(normalizedRoot + sep)) {
131
- kind = "global";
132
- }
133
- return { kind, packageRoot: runtime, version: SILVERMOON_VERSION };
134
- }
135
-
136
- function installCommand(manager, version) {
137
- if (manager === "pnpm") {
138
- return command("pnpm", ["add", "--save-dev", "--save-exact", `silvermoon@${version}`], `Declare Silvermoon ${version} and install it locally.`);
139
- }
140
- if (manager === "yarn") {
141
- return command("yarn", ["add", "--dev", "--exact", `silvermoon@${version}`], `Declare Silvermoon ${version} and install it locally.`);
142
- }
143
- if (manager === "bun") {
144
- return command("bun", ["add", "--dev", "--exact", `silvermoon@${version}`], `Declare Silvermoon ${version} and install it locally.`);
145
- }
146
- return command("npm", ["install", "--save-dev", "--save-exact", `silvermoon@${version}`], `Declare Silvermoon ${version} and install it locally.`);
147
- }
148
-
149
- export async function inspectAdoption({ root, selector } = {}) {
150
- const repositoryRoot = resolve(root ?? process.cwd());
151
- const source = await detectExecutionSource({ root: repositoryRoot });
152
- const manifest = await jsonFile(resolve(repositoryRoot, "package.json"));
153
- const manager = await detectPackageManager(repositoryRoot, manifest);
154
- const sourceCheckout = source.kind === "source-checkout";
155
- const desiredVersion = source.version;
156
- const declared = manifest?.devDependencies?.silvermoon ?? manifest?.dependencies?.silvermoon ?? null;
157
- const installedManifest = sourceCheckout
158
- ? packageJson
159
- : await jsonFile(resolve(repositoryRoot, "node_modules", "silvermoon", "package.json"));
160
- const installedVersion = installedManifest?.version ?? null;
161
- const requirements = [];
162
-
163
- const git = runGit(repositoryRoot, ["rev-parse", "--show-toplevel"]);
164
- requirements.push(requirement(
165
- "repository.git",
166
- "Git repository",
167
- git.ok ? "satisfied" : "missing",
168
- true,
169
- [],
170
- { root: git.ok ? git.stdout : null },
171
- git.ok ? null : command("git", ["init"], "Initialize this directory as a Git repository."),
172
- ));
173
-
174
- requirements.push(requirement(
175
- "runtime.execution-source",
176
- "Authoritative execution source",
177
- ["source-checkout", "project-local", "temporary"].includes(source.kind)
178
- ? "satisfied"
179
- : source.kind === "global" ? "conflicting" : "unknown",
180
- !sourceCheckout && !["project-local", "temporary"].includes(source.kind),
181
- ["global", "unknown"].includes(source.kind)
182
- ? ["repository.git", "package.installed"]
183
- : ["repository.git"],
184
- source,
185
- ["global", "unknown"].includes(source.kind)
186
- ? command("npx", ["--no-install", "silvermoon", "whats-next", ...(selector ? [selector] : []), "--json"], "Re-run with the repository-local CLI.")
187
- : null,
188
- ));
189
-
190
- requirements.push(requirement(
191
- "package.manifest",
192
- "Exact project dependency",
193
- sourceCheckout
194
- ? "inapplicable"
195
- : declared === desiredVersion ? "satisfied" : declared ? "mismatched" : "missing",
196
- !sourceCheckout,
197
- ["repository.git"],
198
- { packageManager: manager, declared, expected: sourceCheckout ? null : desiredVersion },
199
- sourceCheckout ? null : installCommand(manager, desiredVersion),
200
- ));
201
-
202
- const installStatus = sourceCheckout
203
- ? "satisfied"
204
- : installedVersion === desiredVersion ? "satisfied" : installedVersion ? "mismatched" : "missing";
205
- requirements.push(requirement(
206
- "package.installed",
207
- "Installed project CLI",
208
- installStatus,
209
- true,
210
- sourceCheckout ? ["runtime.execution-source"] : ["package.manifest"],
211
- { installedVersion, expected: desiredVersion },
212
- installStatus === "satisfied"
213
- ? null
214
- : command(manager, ["install"], `Install the lockfile-resolved Silvermoon ${desiredVersion} dependency.`),
215
- ));
216
-
217
- const packagedSkill = resolve(packageRoot, "skills", "silvermoon");
218
- let skillStatus = "blocked";
219
- const skillObserved = { paths: [], expectedDigest: null };
220
- if (installStatus === "satisfied") {
221
- const expectedDigest = await directoryDigest(packagedSkill);
222
- skillObserved.expectedDigest = expectedDigest;
223
- for (const path of REPOSITORY_SKILL_PATHS) {
224
- try {
225
- skillObserved.paths.push({
226
- path,
227
- digest: await skillDigest(resolve(repositoryRoot, ...path.split("/"))),
228
- });
229
- } catch (error) {
230
- if (error.code !== "ENOENT") {
231
- skillObserved.paths.push({ path, error: error.message });
232
- }
233
- }
234
- }
235
- skillStatus = skillObserved.paths.length === 0
236
- ? "missing"
237
- : skillObserved.paths.every(({ digest }) => digest === expectedDigest)
238
- ? "satisfied"
239
- : skillObserved.paths.some(({ error }) => error)
240
- ? "conflicting"
241
- : "mismatched";
242
- }
243
- requirements.push(requirement(
244
- "skill.repository-local",
245
- "Repository-local canonical skill",
246
- skillStatus,
247
- true,
248
- ["package.installed"],
249
- skillObserved,
250
- skillStatus === "satisfied" || skillStatus === "blocked"
251
- ? null
252
- : command(
253
- "npx",
254
- [
255
- "skills",
256
- "add",
257
- "./node_modules/silvermoon/skills",
258
- "--skill",
259
- "silvermoon",
260
- "--agent",
261
- "github-copilot",
262
- "--yes",
263
- "--copy",
264
- ],
265
- "Register the installed package's canonical skill through the supported npx skills interface.",
266
- ),
267
- ));
268
-
269
- const loadedConfig = await loadConfig({ root: repositoryRoot });
270
- const missingConfig = loadedConfig.diagnostics.some(({ code }) => code === "config.missing");
271
- const configStatus = loadedConfig.config
272
- ? "satisfied"
273
- : missingConfig ? "missing" : "conflicting";
274
- requirements.push(requirement(
275
- "repository.configuration",
276
- "Silvermoon repository configuration",
277
- configStatus,
278
- true,
279
- ["repository.git"],
280
- { path: ".silvermoon/config.yaml", diagnostics: loadedConfig.diagnostics },
281
- configStatus === "satisfied"
282
- ? null
283
- : missingConfig
284
- ? manual(".silvermoon/config.yaml", "Create the documented configuration through ordinary reviewed file editing.")
285
- : manual(".silvermoon/config.yaml", "Repair the reported configuration conflicts without overwriting unrelated content."),
286
- ));
287
-
288
- const blockingFindings = requirements.filter(({ blocking, status }) =>
289
- blocking && !["satisfied", "inapplicable"].includes(status)
290
- );
291
- const recommended = requirements.find(({ remediation, status, dependencies }) =>
292
- remediation
293
- && !["satisfied", "inapplicable"].includes(status)
294
- && dependencies.every((id) => {
295
- const dependency = requirements.find((item) => item.id === id);
296
- return dependency && ["satisfied", "inapplicable"].includes(dependency.status);
297
- })
298
- );
299
- return {
300
- status: blockingFindings.length === 0 ? "ready" : "blocked",
301
- executionSource: source,
302
- packageManager: manager,
303
- desiredVersion,
304
- requirements,
305
- findings: requirements.filter(({ status }) => !["satisfied", "inapplicable"].includes(status)),
306
- recommendedAction: recommended?.remediation ?? null,
307
- recheck: command(
308
- "npx",
309
- ["--no-install", "silvermoon", "whats-next", ...(selector ? [selector] : []), "--json"],
310
- "Re-run the complete diagnosis after an observable change.",
311
- ),
312
- };
313
- }
1
+ import { createHash } from "node:crypto";
2
+ import {
3
+ lstat,
4
+ readFile,
5
+ readdir,
6
+ realpath,
7
+ } from "node:fs/promises";
8
+ import { dirname, relative, resolve, sep } from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+
11
+ import { loadConfig } from "./config.js";
12
+ import { runGit } from "./git.js";
13
+
14
+ const packageRoot = fileURLToPath(new URL("..", import.meta.url));
15
+ const packageJson = JSON.parse(await readFile(resolve(packageRoot, "package.json"), "utf8"));
16
+ export const SILVERMOON_VERSION = packageJson.version;
17
+ export const REPOSITORY_SKILL_PATHS = [
18
+ ".agents/skills/silvermoon",
19
+ ".github/skills/silvermoon",
20
+ ];
21
+
22
+ async function metadata(path) {
23
+ try {
24
+ return await lstat(path);
25
+ } catch (error) {
26
+ if (error.code === "ENOENT") return null;
27
+ throw error;
28
+ }
29
+ }
30
+
31
+ async function jsonFile(path) {
32
+ try {
33
+ return JSON.parse(await readFile(path, "utf8"));
34
+ } catch (error) {
35
+ if (error.code === "ENOENT") return null;
36
+ return { __invalid: error.message };
37
+ }
38
+ }
39
+
40
+ async function directoryDigest(root) {
41
+ const hash = createHash("sha256");
42
+ async function visit(directory) {
43
+ const entries = (await readdir(directory, { withFileTypes: true }))
44
+ .sort((left, right) => left.name.localeCompare(right.name));
45
+ for (const entry of entries) {
46
+ const path = resolve(directory, entry.name);
47
+ if (entry.isDirectory()) {
48
+ await visit(path);
49
+ } else if (entry.isFile()) {
50
+ hash.update(relative(root, path).split(sep).join("/"));
51
+ hash.update("\0");
52
+ hash.update(await readFile(path));
53
+ hash.update("\0");
54
+ } else {
55
+ throw new Error(`Skill contains a non-regular path: ${path}`);
56
+ }
57
+ }
58
+ }
59
+ await visit(root);
60
+ return hash.digest("hex");
61
+ }
62
+
63
+ async function skillDigest(path) {
64
+ const entry = await lstat(path);
65
+ if (entry.isFile()) {
66
+ const pointer = (await readFile(path, "utf8")).trim();
67
+ if (!pointer) throw new Error(`Skill pointer is empty: ${path}`);
68
+ return directoryDigest(resolve(dirname(path), pointer));
69
+ }
70
+ if (!entry.isDirectory() && !entry.isSymbolicLink()) {
71
+ throw new Error(`Skill discovery path is not a directory: ${path}`);
72
+ }
73
+ return directoryDigest(path);
74
+ }
75
+
76
+ function command(executable, args, description) {
77
+ return { kind: "command", executable, args, description };
78
+ }
79
+
80
+ function manual(path, description) {
81
+ return { kind: "manual", path, description };
82
+ }
83
+
84
+ function requirement(id, title, status, blocking, dependencies, observed, remediation = null) {
85
+ return { id, title, status, blocking, dependencies, observed, remediation };
86
+ }
87
+
88
+ async function detectPackageManager(root, manifest) {
89
+ for (const [file, name] of [
90
+ ["pnpm-lock.yaml", "pnpm"],
91
+ ["yarn.lock", "yarn"],
92
+ ["bun.lock", "bun"],
93
+ ["bun.lockb", "bun"],
94
+ ["package-lock.json", "npm"],
95
+ ["npm-shrinkwrap.json", "npm"],
96
+ ]) {
97
+ if (await metadata(resolve(root, file))) return name;
98
+ }
99
+ const declared = manifest?.packageManager?.split("@")[0];
100
+ return ["pnpm", "npm", "yarn", "bun"].includes(declared) ? declared : "npm";
101
+ }
102
+
103
+ export async function detectExecutionSource({
104
+ root,
105
+ runtimeRoot = packageRoot,
106
+ } = {}) {
107
+ const runtime = await realpath(runtimeRoot);
108
+ let repository;
109
+ try {
110
+ repository = await realpath(root);
111
+ } catch {
112
+ repository = resolve(root);
113
+ }
114
+ let local = null;
115
+ try {
116
+ local = await realpath(resolve(repository, "node_modules", "silvermoon"));
117
+ } catch (error) {
118
+ if (error.code !== "ENOENT") throw error;
119
+ }
120
+ const normalizedRuntime = runtime.toLowerCase();
121
+ const normalizedRoot = repository.toLowerCase();
122
+ const normalizedLocal = local?.toLowerCase() ?? null;
123
+ let kind = "unknown";
124
+ if (normalizedRuntime === normalizedRoot && await metadata(resolve(runtime, ".git"))) {
125
+ kind = "source-checkout";
126
+ } else if (normalizedRuntime === normalizedLocal) {
127
+ kind = "project-local";
128
+ } else if (/(^|[\\/])_npx([\\/]|$)/i.test(runtime)) {
129
+ kind = "temporary";
130
+ } else if (normalizedRuntime.includes(`${sep}node_modules${sep}`) && !normalizedRuntime.startsWith(normalizedRoot + sep)) {
131
+ kind = "global";
132
+ }
133
+ return { kind, packageRoot: runtime, version: SILVERMOON_VERSION };
134
+ }
135
+
136
+ function installCommand(manager, version) {
137
+ if (manager === "pnpm") {
138
+ return command("pnpm", ["add", "--save-dev", "--save-exact", `silvermoon@${version}`], `Declare Silvermoon ${version} and install it locally.`);
139
+ }
140
+ if (manager === "yarn") {
141
+ return command("yarn", ["add", "--dev", "--exact", `silvermoon@${version}`], `Declare Silvermoon ${version} and install it locally.`);
142
+ }
143
+ if (manager === "bun") {
144
+ return command("bun", ["add", "--dev", "--exact", `silvermoon@${version}`], `Declare Silvermoon ${version} and install it locally.`);
145
+ }
146
+ return command("npm", ["install", "--save-dev", "--save-exact", `silvermoon@${version}`], `Declare Silvermoon ${version} and install it locally.`);
147
+ }
148
+
149
+ export async function inspectAdoption({ root, selector } = {}) {
150
+ const repositoryRoot = resolve(root ?? process.cwd());
151
+ const source = await detectExecutionSource({ root: repositoryRoot });
152
+ const manifest = await jsonFile(resolve(repositoryRoot, "package.json"));
153
+ const manager = await detectPackageManager(repositoryRoot, manifest);
154
+ const sourceCheckout = source.kind === "source-checkout";
155
+ const desiredVersion = source.version;
156
+ const declared = manifest?.devDependencies?.silvermoon ?? manifest?.dependencies?.silvermoon ?? null;
157
+ const installedManifest = sourceCheckout
158
+ ? packageJson
159
+ : await jsonFile(resolve(repositoryRoot, "node_modules", "silvermoon", "package.json"));
160
+ const installedVersion = installedManifest?.version ?? null;
161
+ const requirements = [];
162
+
163
+ const git = runGit(repositoryRoot, ["rev-parse", "--show-toplevel"]);
164
+ requirements.push(requirement(
165
+ "repository.git",
166
+ "Git repository",
167
+ git.ok ? "satisfied" : "missing",
168
+ true,
169
+ [],
170
+ { root: git.ok ? git.stdout : null },
171
+ git.ok ? null : command("git", ["init"], "Initialize this directory as a Git repository."),
172
+ ));
173
+
174
+ requirements.push(requirement(
175
+ "runtime.execution-source",
176
+ "Authoritative execution source",
177
+ ["source-checkout", "project-local", "temporary"].includes(source.kind)
178
+ ? "satisfied"
179
+ : source.kind === "global" ? "conflicting" : "unknown",
180
+ !sourceCheckout && !["project-local", "temporary"].includes(source.kind),
181
+ ["global", "unknown"].includes(source.kind)
182
+ ? ["repository.git", "package.installed"]
183
+ : ["repository.git"],
184
+ source,
185
+ ["global", "unknown"].includes(source.kind)
186
+ ? command("npx", ["--no-install", "silvermoon", "whats-next", ...(selector ? [selector] : []), "--json"], "Re-run with the repository-local CLI.")
187
+ : null,
188
+ ));
189
+
190
+ requirements.push(requirement(
191
+ "package.manifest",
192
+ "Exact project dependency",
193
+ sourceCheckout
194
+ ? "inapplicable"
195
+ : declared === desiredVersion ? "satisfied" : declared ? "mismatched" : "missing",
196
+ !sourceCheckout,
197
+ ["repository.git"],
198
+ { packageManager: manager, declared, expected: sourceCheckout ? null : desiredVersion },
199
+ sourceCheckout ? null : installCommand(manager, desiredVersion),
200
+ ));
201
+
202
+ const installStatus = sourceCheckout
203
+ ? "satisfied"
204
+ : installedVersion === desiredVersion ? "satisfied" : installedVersion ? "mismatched" : "missing";
205
+ requirements.push(requirement(
206
+ "package.installed",
207
+ "Installed project CLI",
208
+ installStatus,
209
+ true,
210
+ sourceCheckout ? ["runtime.execution-source"] : ["package.manifest"],
211
+ { installedVersion, expected: desiredVersion },
212
+ installStatus === "satisfied"
213
+ ? null
214
+ : command(manager, ["install"], `Install the lockfile-resolved Silvermoon ${desiredVersion} dependency.`),
215
+ ));
216
+
217
+ const packagedSkill = resolve(packageRoot, "skills", "silvermoon");
218
+ let skillStatus = "blocked";
219
+ const skillObserved = { paths: [], expectedDigest: null };
220
+ if (installStatus === "satisfied") {
221
+ const expectedDigest = await directoryDigest(packagedSkill);
222
+ skillObserved.expectedDigest = expectedDigest;
223
+ for (const path of REPOSITORY_SKILL_PATHS) {
224
+ try {
225
+ skillObserved.paths.push({
226
+ path,
227
+ digest: await skillDigest(resolve(repositoryRoot, ...path.split("/"))),
228
+ });
229
+ } catch (error) {
230
+ if (error.code !== "ENOENT") {
231
+ skillObserved.paths.push({ path, error: error.message });
232
+ }
233
+ }
234
+ }
235
+ skillStatus = skillObserved.paths.length === 0
236
+ ? "missing"
237
+ : skillObserved.paths.every(({ digest }) => digest === expectedDigest)
238
+ ? "satisfied"
239
+ : skillObserved.paths.some(({ error }) => error)
240
+ ? "conflicting"
241
+ : "mismatched";
242
+ }
243
+ requirements.push(requirement(
244
+ "skill.repository-local",
245
+ "Repository-local canonical skill",
246
+ skillStatus,
247
+ true,
248
+ ["package.installed"],
249
+ skillObserved,
250
+ skillStatus === "satisfied" || skillStatus === "blocked"
251
+ ? null
252
+ : command(
253
+ "npx",
254
+ [
255
+ "skills",
256
+ "add",
257
+ "./node_modules/silvermoon/skills",
258
+ "--skill",
259
+ "silvermoon",
260
+ "--agent",
261
+ "github-copilot",
262
+ "--yes",
263
+ "--copy",
264
+ ],
265
+ "Register the installed package's canonical skill through the supported npx skills interface.",
266
+ ),
267
+ ));
268
+
269
+ const loadedConfig = await loadConfig({ root: repositoryRoot });
270
+ const missingConfig = loadedConfig.diagnostics.some(({ code }) => code === "config.missing");
271
+ const configStatus = loadedConfig.config
272
+ ? "satisfied"
273
+ : missingConfig ? "missing" : "conflicting";
274
+ requirements.push(requirement(
275
+ "repository.configuration",
276
+ "Silvermoon repository configuration",
277
+ configStatus,
278
+ true,
279
+ ["repository.git"],
280
+ { path: ".silvermoon/config.yaml", diagnostics: loadedConfig.diagnostics },
281
+ configStatus === "satisfied"
282
+ ? null
283
+ : missingConfig
284
+ ? manual(".silvermoon/config.yaml", "Create the documented configuration through ordinary reviewed file editing.")
285
+ : manual(".silvermoon/config.yaml", "Repair the reported configuration conflicts without overwriting unrelated content."),
286
+ ));
287
+
288
+ const blockingFindings = requirements.filter(({ blocking, status }) =>
289
+ blocking && !["satisfied", "inapplicable"].includes(status)
290
+ );
291
+ const recommended = requirements.find(({ remediation, status, dependencies }) =>
292
+ remediation
293
+ && !["satisfied", "inapplicable"].includes(status)
294
+ && dependencies.every((id) => {
295
+ const dependency = requirements.find((item) => item.id === id);
296
+ return dependency && ["satisfied", "inapplicable"].includes(dependency.status);
297
+ })
298
+ );
299
+ return {
300
+ status: blockingFindings.length === 0 ? "ready" : "blocked",
301
+ executionSource: source,
302
+ packageManager: manager,
303
+ desiredVersion,
304
+ requirements,
305
+ findings: requirements.filter(({ status }) => !["satisfied", "inapplicable"].includes(status)),
306
+ recommendedAction: recommended?.remediation ?? null,
307
+ recheck: command(
308
+ "npx",
309
+ ["--no-install", "silvermoon", "whats-next", ...(selector ? [selector] : []), "--json"],
310
+ "Re-run the complete diagnosis after an observable change.",
311
+ ),
312
+ };
313
+ }