silvermoon 0.0.2 → 0.1.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 +168 -0
- package/README.zh-CN.md +19 -4
- package/docs/core-concepts.md +14 -12
- package/docs/getting-started.md +53 -27
- package/docs/maintaining.md +11 -2
- package/docs/operations.md +77 -30
- package/docs/reference.md +197 -37
- package/docs/repository-tasks.md +25 -13
- package/package.json +2 -2
- package/schema/v1/idea-status.schema.json +3 -3
- package/skills/silvermoon/SKILL.md +142 -187
- package/skills/silvermoon/references/adoption.md +34 -20
- package/src/adoption.js +136 -260
- package/src/cli.js +86 -113
- package/src/create-idea.js +222 -110
- package/src/dialogue.js +303 -0
- package/src/git.js +172 -97
- package/src/idea-layout.js +59 -123
- package/src/index.js +203 -170
- package/src/observation.js +305 -0
- package/src/trace.js +171 -0
- package/src/whatsnext.js +529 -378
package/src/adoption.js
CHANGED
|
@@ -3,42 +3,26 @@ import {
|
|
|
3
3
|
lstat,
|
|
4
4
|
readFile,
|
|
5
5
|
readdir,
|
|
6
|
-
realpath,
|
|
7
6
|
} from "node:fs/promises";
|
|
8
|
-
import {
|
|
7
|
+
import { relative, resolve, sep } from "node:path";
|
|
9
8
|
import { fileURLToPath } from "node:url";
|
|
10
9
|
|
|
10
|
+
import { diagnosticProblem } from "./dialogue.js";
|
|
11
11
|
import { loadConfig } from "./config.js";
|
|
12
12
|
import { runGit } from "./git.js";
|
|
13
|
+
import { traceAsync } from "./trace.js";
|
|
13
14
|
|
|
14
15
|
const packageRoot = fileURLToPath(new URL("..", import.meta.url));
|
|
15
16
|
const packageJson = JSON.parse(await readFile(resolve(packageRoot, "package.json"), "utf8"));
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
}
|
|
17
|
+
const packagedSkillsRoot = resolve(packageRoot, "skills");
|
|
18
|
+
const packagedSkill = resolve(packagedSkillsRoot, "silvermoon");
|
|
30
19
|
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
}
|
|
20
|
+
export const SILVERMOON_VERSION = packageJson.version;
|
|
21
|
+
export const REPOSITORY_SKILL_PATH = ".agents/skills/silvermoon";
|
|
39
22
|
|
|
40
23
|
async function directoryDigest(root) {
|
|
41
24
|
const hash = createHash("sha256");
|
|
25
|
+
|
|
42
26
|
async function visit(directory) {
|
|
43
27
|
const entries = (await readdir(directory, { withFileTypes: true }))
|
|
44
28
|
.sort((left, right) => left.name.localeCompare(right.name));
|
|
@@ -51,263 +35,155 @@ async function directoryDigest(root) {
|
|
|
51
35
|
hash.update("\0");
|
|
52
36
|
hash.update(await readFile(path));
|
|
53
37
|
hash.update("\0");
|
|
38
|
+
} else if (entry.isSymbolicLink()) {
|
|
39
|
+
await visit(path);
|
|
54
40
|
} else {
|
|
55
41
|
throw new Error(`Skill contains a non-regular path: ${path}`);
|
|
56
42
|
}
|
|
57
43
|
}
|
|
58
44
|
}
|
|
45
|
+
|
|
59
46
|
await visit(root);
|
|
60
47
|
return hash.digest("hex");
|
|
61
48
|
}
|
|
62
49
|
|
|
63
|
-
async function
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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;
|
|
50
|
+
async function inspectSkill(contentRoot) {
|
|
51
|
+
const path = resolve(contentRoot, ...REPOSITORY_SKILL_PATH.split("/"));
|
|
52
|
+
const expectedDigest = await traceAsync(
|
|
53
|
+
"skill.digest-packaged",
|
|
54
|
+
{},
|
|
55
|
+
() => directoryDigest(packagedSkill),
|
|
56
|
+
);
|
|
57
|
+
let metadata;
|
|
109
58
|
try {
|
|
110
|
-
|
|
111
|
-
} catch {
|
|
112
|
-
|
|
59
|
+
metadata = await lstat(path);
|
|
60
|
+
} catch (caught) {
|
|
61
|
+
if (caught.code !== "ENOENT") {
|
|
62
|
+
return {
|
|
63
|
+
ok: false,
|
|
64
|
+
problem: {
|
|
65
|
+
type: "canonical-skill-invalid",
|
|
66
|
+
summary: `Cannot inspect ${REPOSITORY_SKILL_PATH}: ${caught.message}`,
|
|
67
|
+
},
|
|
68
|
+
instruction:
|
|
69
|
+
`Repair the path to ${REPOSITORY_SKILL_PATH}, then register the `
|
|
70
|
+
+ "canonical skill again.",
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
ok: false,
|
|
75
|
+
problem: {
|
|
76
|
+
type: "canonical-skill-missing",
|
|
77
|
+
summary: `Missing canonical Silvermoon skill at ${REPOSITORY_SKILL_PATH}.`,
|
|
78
|
+
},
|
|
79
|
+
instruction:
|
|
80
|
+
`Run \`npx skills add "${packagedSkillsRoot}" --skill silvermoon `
|
|
81
|
+
+ "--agent universal --yes --copy`.",
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
if (!metadata.isDirectory() && !metadata.isSymbolicLink()) {
|
|
85
|
+
return {
|
|
86
|
+
ok: false,
|
|
87
|
+
problem: {
|
|
88
|
+
type: "canonical-skill-invalid",
|
|
89
|
+
summary: `${REPOSITORY_SKILL_PATH} must be a directory or directory symlink.`,
|
|
90
|
+
},
|
|
91
|
+
instruction:
|
|
92
|
+
`Preserve the conflicting path, then register the canonical skill at `
|
|
93
|
+
+ `${REPOSITORY_SKILL_PATH}.`,
|
|
94
|
+
};
|
|
113
95
|
}
|
|
114
|
-
let
|
|
96
|
+
let actualDigest;
|
|
115
97
|
try {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
98
|
+
actualDigest = await traceAsync(
|
|
99
|
+
"skill.digest-repository",
|
|
100
|
+
{},
|
|
101
|
+
() => directoryDigest(path),
|
|
102
|
+
);
|
|
103
|
+
} catch (caught) {
|
|
104
|
+
return {
|
|
105
|
+
ok: false,
|
|
106
|
+
problem: {
|
|
107
|
+
type: "canonical-skill-invalid",
|
|
108
|
+
summary: `Cannot inspect ${REPOSITORY_SKILL_PATH}: ${caught.message}`,
|
|
109
|
+
},
|
|
110
|
+
instruction:
|
|
111
|
+
`Repair ${REPOSITORY_SKILL_PATH}, then register the canonical skill again.`,
|
|
112
|
+
};
|
|
119
113
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
114
|
+
if (actualDigest !== expectedDigest) {
|
|
115
|
+
return {
|
|
116
|
+
ok: false,
|
|
117
|
+
problem: {
|
|
118
|
+
type: "canonical-skill-mismatched",
|
|
119
|
+
summary:
|
|
120
|
+
`${REPOSITORY_SKILL_PATH} does not match the canonical skill bundled `
|
|
121
|
+
+ `with Silvermoon ${SILVERMOON_VERSION}.`,
|
|
122
|
+
},
|
|
123
|
+
instruction:
|
|
124
|
+
`Review local skill changes, then run \`npx skills add "${packagedSkillsRoot}" `
|
|
125
|
+
+ "--skill silvermoon --agent universal --yes --copy`.",
|
|
126
|
+
};
|
|
132
127
|
}
|
|
133
|
-
return {
|
|
128
|
+
return { ok: true, digest: actualDigest };
|
|
134
129
|
}
|
|
135
130
|
|
|
136
|
-
function
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
131
|
+
export async function inspectAdoption({
|
|
132
|
+
contentRoot,
|
|
133
|
+
root = process.cwd(),
|
|
134
|
+
} = {}) {
|
|
135
|
+
const requestedRoot = resolve(root);
|
|
136
|
+
const git = runGit(requestedRoot, ["rev-parse", "--show-toplevel"]);
|
|
137
|
+
const repositoryRoot = git.ok ? resolve(git.stdout) : requestedRoot;
|
|
138
|
+
const snapshotRoot = resolve(contentRoot ?? repositoryRoot);
|
|
139
|
+
const findings = [];
|
|
140
|
+
|
|
141
|
+
if (!git.ok) {
|
|
142
|
+
findings.push({
|
|
143
|
+
priority: 10,
|
|
144
|
+
problem: {
|
|
145
|
+
type: "git-repository-missing",
|
|
146
|
+
summary: `${requestedRoot} is not an initialized Git repository.`,
|
|
147
|
+
},
|
|
148
|
+
instruction: `Run \`git -C "${requestedRoot}" init\`.`,
|
|
149
|
+
});
|
|
145
150
|
}
|
|
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
151
|
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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";
|
|
152
|
+
const loadedConfig = await traceAsync(
|
|
153
|
+
"config.load",
|
|
154
|
+
{},
|
|
155
|
+
() => loadConfig({ root: snapshotRoot }),
|
|
156
|
+
);
|
|
157
|
+
for (const diagnostic of loadedConfig.diagnostics) {
|
|
158
|
+
findings.push({
|
|
159
|
+
priority: diagnostic.code === "config.unsupported-version" ? 20 : 30,
|
|
160
|
+
problem: diagnosticProblem(diagnostic),
|
|
161
|
+
instruction: diagnostic.remediation,
|
|
162
|
+
sourceDiagnostic: diagnostic,
|
|
163
|
+
});
|
|
242
164
|
}
|
|
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
165
|
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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
|
-
})
|
|
166
|
+
const skill = await traceAsync(
|
|
167
|
+
"skill.inspect",
|
|
168
|
+
{},
|
|
169
|
+
() => inspectSkill(snapshotRoot),
|
|
298
170
|
);
|
|
171
|
+
if (!skill.ok) {
|
|
172
|
+
findings.push({
|
|
173
|
+
priority: 40,
|
|
174
|
+
problem: skill.problem,
|
|
175
|
+
instruction: skill.instruction,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
findings.sort((left, right) => left.priority - right.priority);
|
|
179
|
+
|
|
299
180
|
return {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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
|
-
),
|
|
181
|
+
config: loadedConfig.config,
|
|
182
|
+
findings,
|
|
183
|
+
gitReady: git.ok,
|
|
184
|
+
instructions: findings.map(({ instruction }) => instruction),
|
|
185
|
+
problems: findings.map(({ problem }) => problem),
|
|
186
|
+
root: repositoryRoot,
|
|
187
|
+
skill,
|
|
312
188
|
};
|
|
313
189
|
}
|