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/cli.js
CHANGED
|
@@ -1,116 +1,62 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
Command,
|
|
4
|
+
CommanderError,
|
|
5
|
+
InvalidArgumentError,
|
|
6
|
+
Option,
|
|
7
|
+
} from "commander";
|
|
8
|
+
|
|
9
|
+
import { canonicalizeLanguageTag } from "./language.js";
|
|
10
|
+
import { renderCheck, renderDialogue } from "./dialogue.js";
|
|
4
11
|
import { checkRepository } from "./index.js";
|
|
5
12
|
import { createIdea } from "./create-idea.js";
|
|
13
|
+
import { withTraceFile } from "./trace.js";
|
|
6
14
|
import { whatsNext } from "./whatsnext.js";
|
|
7
15
|
|
|
8
16
|
const { version: VERSION } = JSON.parse(
|
|
9
17
|
readFileSync(new URL("../package.json", import.meta.url), "utf8"),
|
|
10
18
|
);
|
|
11
19
|
|
|
20
|
+
const TRACE_FILE_SUFFIX = ".trace.jsonl";
|
|
21
|
+
|
|
12
22
|
function write(method, value) {
|
|
13
23
|
const text = value.replace(/\n$/, "");
|
|
14
24
|
if (text) method(text);
|
|
15
25
|
}
|
|
16
26
|
|
|
27
|
+
export function normalizeTraceFileName(value) {
|
|
28
|
+
return value.endsWith(TRACE_FILE_SUFFIX)
|
|
29
|
+
? value
|
|
30
|
+
: `${value}${TRACE_FILE_SUFFIX}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
17
33
|
function addCommonOptions(command) {
|
|
18
34
|
return command
|
|
19
|
-
.option("--json", "
|
|
35
|
+
.option("--json", "serialize the conversation envelope as JSON")
|
|
36
|
+
.option(
|
|
37
|
+
"--trace <file.trace.jsonl>",
|
|
38
|
+
"write task timing spans as JSONL (suffix is appended when omitted)",
|
|
39
|
+
normalizeTraceFileName,
|
|
40
|
+
)
|
|
20
41
|
.option("-r, --root <path>", "repository root", process.cwd());
|
|
21
42
|
}
|
|
22
43
|
|
|
23
|
-
function
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
function renderIdeaIdentity(idea) {
|
|
33
|
-
return idea.alias === undefined ? idea.id : `${idea.id} (${idea.alias})`;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function renderRemediation(remediation) {
|
|
37
|
-
return remediation.kind === "command"
|
|
38
|
-
? `${remediation.executable} ${remediation.args.join(" ")}`
|
|
39
|
-
: `${remediation.description} (${remediation.path})`;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function renderWhatsNext(result, io) {
|
|
43
|
-
if (result.onboarding) {
|
|
44
|
-
io.log(`onboarding ${result.onboarding.status}`);
|
|
45
|
-
for (const item of result.onboarding.gaps) {
|
|
46
|
-
io.log(
|
|
47
|
-
` ${item.status.padEnd(12)} ${item.id}${item.blocking ? " [blocking]" : " [non-blocking]"}: ${item.title}`,
|
|
48
|
-
);
|
|
49
|
-
io.log(` depends ${item.dependencies.length ? item.dependencies.join(", ") : "none"}`);
|
|
50
|
-
io.log(` observed ${JSON.stringify(item.observed)}`);
|
|
51
|
-
if (item.remediation) io.log(` fix ${renderRemediation(item.remediation)}`);
|
|
52
|
-
}
|
|
53
|
-
if (result.onboarding.recommendedAction) {
|
|
54
|
-
io.log(` next ${renderRemediation(result.onboarding.recommendedAction)}`);
|
|
55
|
-
}
|
|
56
|
-
io.log(` recheck ${renderRemediation(result.onboarding.recheck)}`);
|
|
57
|
-
}
|
|
58
|
-
io.log(`${result.action.code}: ${result.action.message}`);
|
|
59
|
-
io.log(` language ${result.language.tag} (${result.language.source})`);
|
|
60
|
-
if (result.observedPrimaryCommit) io.log(` primary ${result.observedPrimaryCommit}`);
|
|
61
|
-
if (result.selectedIdea) {
|
|
62
|
-
io.log(` idea ${renderIdeaIdentity(result.selectedIdea)}`);
|
|
63
|
-
io.log(` state ${result.selectedIdea.state}`);
|
|
64
|
-
io.log(` ideal ${result.selectedIdea.idealRevision}`);
|
|
65
|
-
io.log(` inner ${result.selectedIdea.implementationRevision}`);
|
|
66
|
-
io.log(` outer ${result.selectedIdea.deploymentRevision}`);
|
|
67
|
-
}
|
|
68
|
-
if (result.action.details?.world) {
|
|
69
|
-
const world = result.action.details.world;
|
|
70
|
-
io.log(` world ${world.name} (${world.displayName})`);
|
|
71
|
-
io.log(` entry ${world.documentPath}`);
|
|
72
|
-
io.log(` support ${world.auxiliaryRoot}`);
|
|
73
|
-
io.log(` decision ${world.decisionField}: ${world.revision}`);
|
|
74
|
-
io.log(` cascade ${world.cascade}`);
|
|
75
|
-
}
|
|
76
|
-
for (const idea of result.action.details?.ideas ?? []) {
|
|
77
|
-
io.log(` option ${renderIdeaIdentity(idea)} ${idea.state}`);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function renderCreateIdea(result, io) {
|
|
82
|
-
if (!result.createdIdea) {
|
|
83
|
-
renderWhatsNext(result, io);
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
io.log(`created: ${result.createdIdea.id}`);
|
|
87
|
-
io.log(` idea ${result.createdIdea.ideaPath}`);
|
|
88
|
-
io.log(` ledger ${result.createdIdea.ledgerPath}`);
|
|
89
|
-
io.log(` status ${result.createdIdea.statusPath}`);
|
|
44
|
+
export function render(report, json, io) {
|
|
45
|
+
io.log(
|
|
46
|
+
json
|
|
47
|
+
? JSON.stringify(report, null, 2)
|
|
48
|
+
: report.intention.command === "check"
|
|
49
|
+
? renderCheck(report)
|
|
50
|
+
: renderDialogue(report),
|
|
51
|
+
);
|
|
90
52
|
}
|
|
91
53
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
renderDiagnostics(report, io);
|
|
98
|
-
if (!report.ok) {
|
|
99
|
-
io.error("FAILED");
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
if (report.command === "whats-next") {
|
|
103
|
-
renderWhatsNext(report.result, io);
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
if (report.command === "create-idea") {
|
|
107
|
-
renderCreateIdea(report.result, io);
|
|
108
|
-
return;
|
|
54
|
+
function languageArgument(value) {
|
|
55
|
+
try {
|
|
56
|
+
return canonicalizeLanguageTag(value);
|
|
57
|
+
} catch (caught) {
|
|
58
|
+
throw new InvalidArgumentError(caught.message);
|
|
109
59
|
}
|
|
110
|
-
io.log(`OK: ${report.command}`);
|
|
111
|
-
io.log(` target ${report.result.target}`);
|
|
112
|
-
if (report.result.commit) io.log(` commit ${report.result.commit}`);
|
|
113
|
-
io.log(` ideas ${report.result.checked}`);
|
|
114
60
|
}
|
|
115
61
|
|
|
116
62
|
export function createProgram(io = console) {
|
|
@@ -134,6 +80,7 @@ Examples:
|
|
|
134
80
|
$ silvermoon whats-next
|
|
135
81
|
$ silvermoon whats-next <idea>
|
|
136
82
|
$ silvermoon whats-next <idea> --json
|
|
83
|
+
$ silvermoon whats-next <idea> --trace whats-next.trace.jsonl
|
|
137
84
|
$ silvermoon create-idea
|
|
138
85
|
$ silvermoon check
|
|
139
86
|
$ silvermoon check --worktree
|
|
@@ -144,45 +91,67 @@ Examples:
|
|
|
144
91
|
addCommonOptions(
|
|
145
92
|
program
|
|
146
93
|
.command("whats-next [idea]")
|
|
147
|
-
.description("
|
|
94
|
+
.description("observe project, repository, and idea readiness"),
|
|
148
95
|
).action(async (idea, options) => {
|
|
149
|
-
const report = await
|
|
96
|
+
const report = await withTraceFile(
|
|
97
|
+
options.trace,
|
|
98
|
+
"command.whats-next",
|
|
99
|
+
{ command: "whats-next", processId: process.pid },
|
|
100
|
+
() => whatsNext({ idea, root: options.root }),
|
|
101
|
+
);
|
|
150
102
|
render(report, options.json, io);
|
|
151
|
-
program.setOptionValue("resultCode",
|
|
103
|
+
program.setOptionValue("resultCode", 0);
|
|
152
104
|
});
|
|
153
105
|
|
|
154
106
|
addCommonOptions(
|
|
155
107
|
program
|
|
156
108
|
.command("create-idea")
|
|
157
|
-
.description("create one structured idea scaffold after
|
|
158
|
-
.option(
|
|
109
|
+
.description("create one structured idea scaffold after readiness checks")
|
|
110
|
+
.option(
|
|
111
|
+
"--language <tag>",
|
|
112
|
+
"persist a canonical language override on the new idea",
|
|
113
|
+
languageArgument,
|
|
114
|
+
),
|
|
159
115
|
).action(async (options) => {
|
|
160
|
-
const report = await
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
116
|
+
const report = await withTraceFile(
|
|
117
|
+
options.trace,
|
|
118
|
+
"command.create-idea",
|
|
119
|
+
{ command: "create-idea", processId: process.pid },
|
|
120
|
+
() => createIdea({
|
|
121
|
+
language: options.language,
|
|
122
|
+
root: options.root,
|
|
123
|
+
}),
|
|
124
|
+
);
|
|
164
125
|
render(report, options.json, io);
|
|
165
|
-
program.setOptionValue("resultCode",
|
|
126
|
+
program.setOptionValue("resultCode", 0);
|
|
166
127
|
});
|
|
167
128
|
|
|
168
129
|
addCommonOptions(
|
|
169
130
|
program
|
|
170
131
|
.command("check")
|
|
171
|
-
.description("validate Silvermoon configuration
|
|
172
|
-
.addOption(new Option("--remote", "fetch and validate the primary tip
|
|
132
|
+
.description("validate Silvermoon configuration and idea state")
|
|
133
|
+
.addOption(new Option("--remote", "fetch and validate the configured primary tip").conflicts(["commit", "staged", "worktree"]))
|
|
173
134
|
.addOption(new Option("--commit <revision>", "validate one local commit snapshot").conflicts(["remote", "staged", "worktree"]))
|
|
174
135
|
.addOption(new Option("--staged", "validate the index snapshot").conflicts(["remote", "commit", "worktree"]))
|
|
175
|
-
.addOption(new Option("--worktree", "validate HEAD plus
|
|
136
|
+
.addOption(new Option("--worktree", "validate HEAD plus staged, unstaged, and untracked changes").conflicts(["remote", "commit", "staged"])),
|
|
176
137
|
).action(async (options) => {
|
|
177
|
-
const report = await
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
138
|
+
const report = await withTraceFile(
|
|
139
|
+
options.trace,
|
|
140
|
+
"command.check",
|
|
141
|
+
{ command: "check", processId: process.pid },
|
|
142
|
+
() => checkRepository({
|
|
143
|
+
commit: options.commit,
|
|
144
|
+
remote: options.remote,
|
|
145
|
+
root: options.root,
|
|
146
|
+
staged: options.staged,
|
|
147
|
+
worktree: options.worktree,
|
|
148
|
+
}),
|
|
149
|
+
);
|
|
184
150
|
render(report, options.json, io);
|
|
185
|
-
program.setOptionValue(
|
|
151
|
+
program.setOptionValue(
|
|
152
|
+
"resultCode",
|
|
153
|
+
report.observation.state === "project-ready" ? 0 : 1,
|
|
154
|
+
);
|
|
186
155
|
});
|
|
187
156
|
|
|
188
157
|
return program;
|
|
@@ -194,8 +163,12 @@ export async function runCli(args, io = console) {
|
|
|
194
163
|
await program.parseAsync(args.length === 0 ? ["--help"] : args, { from: "user" });
|
|
195
164
|
} catch (caught) {
|
|
196
165
|
if (caught instanceof CommanderError) return caught.exitCode === 0 ? 0 : 2;
|
|
166
|
+
if (caught.exitCode === 2) {
|
|
167
|
+
io.error(`ERROR usage: ${caught.message}`);
|
|
168
|
+
return 2;
|
|
169
|
+
}
|
|
197
170
|
io.error(`ERROR command.failed: ${caught.message}`);
|
|
198
171
|
return 1;
|
|
199
172
|
}
|
|
200
173
|
return program.getOptionValue("resultCode") ?? 0;
|
|
201
|
-
}
|
|
174
|
+
}
|