silvermoon 0.0.3 → 0.1.1
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 +36 -13
- package/README.zh-CN.md +32 -13
- package/assets/silvermoon-avatar.svg +395 -0
- package/docs/core-concepts.md +14 -12
- package/docs/getting-started.md +71 -29
- package/docs/maintaining.md +15 -3
- package/docs/npm-package-releases.md +25 -7
- package/docs/operations.md +96 -37
- package/docs/reference.md +223 -38
- package/docs/repository-tasks.md +34 -13
- package/package.json +2 -2
- package/schema/v1/idea-status.schema.json +3 -3
- package/skills/silvermoon/SKILL.md +147 -187
- package/skills/silvermoon/references/adoption.md +34 -20
- package/src/adoption.js +136 -260
- package/src/cli.js +123 -110
- package/src/create-idea.js +222 -110
- package/src/dialogue.js +327 -0
- package/src/git.js +172 -97
- package/src/idea-layout.js +59 -123
- package/src/index.js +240 -170
- package/src/language.js +37 -0
- package/src/observation.js +350 -0
- package/src/trace.js +171 -0
- package/src/whatsnext.js +541 -378
package/src/whatsnext.js
CHANGED
|
@@ -1,454 +1,617 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
2
|
|
|
3
|
-
import { inspectAdoption } from "./adoption.js";
|
|
4
|
-
import { loadConfig } from "./config.js";
|
|
5
3
|
import {
|
|
4
|
+
createEnvelope,
|
|
5
|
+
dialogueReadyObservation,
|
|
6
|
+
localize,
|
|
7
|
+
outcome,
|
|
8
|
+
} from "./dialogue.js";
|
|
9
|
+
import {
|
|
10
|
+
compareCommits,
|
|
6
11
|
fetchPrimary,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
12
|
+
inspectCurrentBranch,
|
|
13
|
+
inspectWorktreeChanges,
|
|
14
|
+
resolveHead,
|
|
15
|
+
sanitizeGitMessage,
|
|
10
16
|
} from "./git.js";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
|
|
17
|
+
import { canonicalizeOutputLanguage } from "./language.js";
|
|
18
|
+
import {
|
|
19
|
+
observeSnapshot,
|
|
20
|
+
repositoryProblemObservation,
|
|
21
|
+
withObservationLanguage,
|
|
22
|
+
} from "./observation.js";
|
|
23
|
+
import { traceAsync } from "./trace.js";
|
|
14
24
|
|
|
15
|
-
const
|
|
25
|
+
export const CHANGE_SAMPLE_ITEM_LIMIT = 12;
|
|
26
|
+
export const CHANGE_SAMPLE_BYTE_LIMIT = 768;
|
|
16
27
|
|
|
17
|
-
function
|
|
18
|
-
return
|
|
28
|
+
function ideaName(idea) {
|
|
29
|
+
return idea.alias ?? idea.id;
|
|
19
30
|
}
|
|
20
31
|
|
|
21
|
-
function
|
|
22
|
-
return {
|
|
23
|
-
command: "whats-next",
|
|
24
|
-
ok: false,
|
|
25
|
-
root,
|
|
26
|
-
diagnostics,
|
|
27
|
-
result: request
|
|
28
|
-
? { observedPrimaryCommit, request, selectedIdea: null, action: null }
|
|
29
|
-
: null,
|
|
30
|
-
};
|
|
32
|
+
function command(root, value) {
|
|
33
|
+
return `\`${value.replace("<root>", root)}\``;
|
|
31
34
|
}
|
|
32
35
|
|
|
33
|
-
function
|
|
34
|
-
return
|
|
36
|
+
function joinInstructions(lines) {
|
|
37
|
+
return lines.filter(Boolean).join("\n");
|
|
35
38
|
}
|
|
36
39
|
|
|
37
|
-
function
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
implementationRevision: idea.implementationRevision,
|
|
42
|
-
deploymentRevision: idea.deploymentRevision,
|
|
43
|
-
ledgerPath: idea.ledgerPath,
|
|
44
|
-
state: idea.state,
|
|
45
|
-
};
|
|
46
|
-
if (idea.alias !== undefined) value.alias = idea.alias;
|
|
47
|
-
return value;
|
|
40
|
+
function formatInstructionSteps(steps) {
|
|
41
|
+
return steps.length === 1
|
|
42
|
+
? steps
|
|
43
|
+
: steps.map((step, index) => `${index + 1}. ${step}`);
|
|
48
44
|
}
|
|
49
45
|
|
|
50
|
-
function
|
|
51
|
-
|
|
46
|
+
export function projectInstructions(
|
|
47
|
+
observed,
|
|
48
|
+
root,
|
|
49
|
+
language,
|
|
50
|
+
recheckCommand = "silvermoon whats-next",
|
|
51
|
+
) {
|
|
52
|
+
return joinInstructions([
|
|
53
|
+
...formatInstructionSteps(observed.findings.map(({ instruction }) => instruction)),
|
|
54
|
+
localize(
|
|
55
|
+
language,
|
|
56
|
+
`After completing the applicable steps, run ${command(root, recheckCommand)} again.`,
|
|
57
|
+
`完成适用步骤后,再运行 ${command(root, recheckCommand)}。`,
|
|
58
|
+
),
|
|
59
|
+
]);
|
|
52
60
|
}
|
|
53
61
|
|
|
54
|
-
function
|
|
62
|
+
function sampleChanges(changes) {
|
|
63
|
+
const entries = [
|
|
64
|
+
...changes.conflicted.map(({ path }) => `conflicted:${path}`),
|
|
65
|
+
...changes.staged.map(({ path }) => `staged:${path}`),
|
|
66
|
+
...changes.unstaged.map(({ path }) => `unstaged:${path}`),
|
|
67
|
+
...changes.untracked.map(({ path }) => `untracked:${path}`),
|
|
68
|
+
];
|
|
69
|
+
const samples = [];
|
|
70
|
+
let bytes = 0;
|
|
71
|
+
for (const entry of entries) {
|
|
72
|
+
if (samples.length >= CHANGE_SAMPLE_ITEM_LIMIT) break;
|
|
73
|
+
const nextBytes = Buffer.byteLength(
|
|
74
|
+
samples.length === 0 ? entry : `, ${entry}`,
|
|
75
|
+
"utf8",
|
|
76
|
+
);
|
|
77
|
+
if (bytes + nextBytes > CHANGE_SAMPLE_BYTE_LIMIT) break;
|
|
78
|
+
samples.push(entry);
|
|
79
|
+
bytes += nextBytes;
|
|
80
|
+
}
|
|
55
81
|
return {
|
|
56
|
-
|
|
57
|
-
|
|
82
|
+
counts: {
|
|
83
|
+
conflicted: changes.conflicted.length,
|
|
84
|
+
staged: changes.staged.length,
|
|
85
|
+
unstaged: changes.unstaged.length,
|
|
86
|
+
untracked: changes.untracked.length,
|
|
87
|
+
},
|
|
88
|
+
omitted: entries.length - samples.length,
|
|
89
|
+
samples,
|
|
58
90
|
};
|
|
59
91
|
}
|
|
60
92
|
|
|
61
|
-
function
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
93
|
+
export function summarizeWorktreeChanges(changes, language = "en-US") {
|
|
94
|
+
const summary = sampleChanges(changes);
|
|
95
|
+
const labels = {
|
|
96
|
+
conflicted: localize(language, "conflicted", "冲突"),
|
|
97
|
+
staged: localize(language, "staged", "已暂存"),
|
|
98
|
+
unstaged: localize(language, "unstaged", "未暂存"),
|
|
99
|
+
untracked: localize(language, "untracked", "未跟踪"),
|
|
68
100
|
};
|
|
101
|
+
const counts = Object.entries(summary.counts)
|
|
102
|
+
.map(([kind, count]) => `${labels[kind]}=${count}`)
|
|
103
|
+
.join(localize(language, ", ", ","));
|
|
104
|
+
const samples = summary.samples.length === 0
|
|
105
|
+
? localize(language, "none", "无")
|
|
106
|
+
: summary.samples.join(localize(language, ", ", ","));
|
|
107
|
+
return localize(
|
|
108
|
+
language,
|
|
109
|
+
`${counts}; samples=[${samples}]; omitted=${summary.omitted}`,
|
|
110
|
+
`${counts};样例=[${samples}];省略=${summary.omitted}`,
|
|
111
|
+
);
|
|
69
112
|
}
|
|
70
113
|
|
|
71
|
-
function
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
root,
|
|
76
|
-
diagnostics: [],
|
|
77
|
-
result: {
|
|
78
|
-
observedPrimaryCommit,
|
|
79
|
-
request,
|
|
114
|
+
function worktreeInstructionSteps(changes, language) {
|
|
115
|
+
const lines = [];
|
|
116
|
+
if (changes.conflicted.length > 0) {
|
|
117
|
+
lines.push(localize(
|
|
80
118
|
language,
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
119
|
+
"Inspect every conflicted path and its contents, then resolve the conflicts without discarding either side.",
|
|
120
|
+
"检查全部冲突路径及其内容,再在不丢弃任一方内容的前提下解决冲突。",
|
|
121
|
+
));
|
|
122
|
+
}
|
|
123
|
+
const hasOrdinaryChanges =
|
|
124
|
+
changes.staged.length > 0
|
|
125
|
+
|| changes.unstaged.length > 0
|
|
126
|
+
|| changes.untracked.length > 0;
|
|
127
|
+
if (hasOrdinaryChanges) {
|
|
128
|
+
lines.push(localize(
|
|
129
|
+
language,
|
|
130
|
+
"Inspect all staged, unstaged, and untracked paths and their changes, not just the samples above. Preserve unknown work, then commit, isolate, or explicitly discard each change.",
|
|
131
|
+
"检查全部 staged、unstaged 和 untracked 路径及其修改内容,不要只依据上述样例。保留未知工作,再逐项提交、隔离,或在获得明确授权后放弃。",
|
|
132
|
+
));
|
|
133
|
+
}
|
|
134
|
+
return lines;
|
|
85
135
|
}
|
|
86
136
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
137
|
+
function localRepositoryInstructions(root, steps, language, recheckCommand) {
|
|
138
|
+
return joinInstructions([
|
|
139
|
+
...formatInstructionSteps(steps),
|
|
140
|
+
localize(
|
|
141
|
+
language,
|
|
142
|
+
`After completing every applicable step, run ${command(root, recheckCommand)} again.`,
|
|
143
|
+
`完成全部适用步骤后,再运行 ${command(root, recheckCommand)}。`,
|
|
144
|
+
),
|
|
145
|
+
]);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function selectIdea(ideas, selector) {
|
|
149
|
+
if (selector === undefined) return null;
|
|
150
|
+
return ideas.find(({ id }) => id === selector)
|
|
151
|
+
?? ideas.find(({ alias }) => alias === selector)
|
|
152
|
+
?? null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function lifecycleInstruction(idea, language) {
|
|
156
|
+
const name = ideaName(idea);
|
|
95
157
|
if (idea.state === "preparing") {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
{
|
|
101
|
-
ideaPath: idea.relativePath,
|
|
102
|
-
statusPath: idea.statusPath,
|
|
103
|
-
ledgerPath: idea.ledgerPath,
|
|
104
|
-
world: {
|
|
105
|
-
...world,
|
|
106
|
-
auxiliaryRoot: world.path,
|
|
107
|
-
revision: idea.idealRevision,
|
|
108
|
-
decisionField: "approvedRevision",
|
|
109
|
-
cascade: "Changes invalidate approval, implementation acceptance, and deployment acceptance.",
|
|
110
|
-
},
|
|
111
|
-
},
|
|
158
|
+
return localize(
|
|
159
|
+
language,
|
|
160
|
+
`Continue idea ${name} in ${idea.worlds.idealRevision.documentPath} and ${idea.ledgerPath}. Preserve the other worlds. When the Ideal World is ready, ask the user to approve exact revision ${idea.idealRevision}; only after explicit approval write it to approvedRevision in ${idea.statusPath}.`,
|
|
161
|
+
`继续在 ${idea.worlds.idealRevision.documentPath} 和 ${idea.ledgerPath} 推进 idea ${name},并保留其他世界。理想契约就绪后,请用户明确批准精确 revision ${idea.idealRevision};只有获得明确批准后,才将其写入 ${idea.statusPath} 的 approvedRevision。`,
|
|
112
162
|
);
|
|
113
163
|
}
|
|
114
164
|
if (idea.state === "implementing") {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
{
|
|
120
|
-
ideaPath: idea.relativePath,
|
|
121
|
-
statusPath: idea.statusPath,
|
|
122
|
-
ledgerPath: idea.ledgerPath,
|
|
123
|
-
world: {
|
|
124
|
-
...world,
|
|
125
|
-
auxiliaryRoot: world.path,
|
|
126
|
-
nestedWorldPath: idea.worlds.idealRevision.path,
|
|
127
|
-
revision: idea.implementationRevision,
|
|
128
|
-
decisionField: "implementationAcceptedRevision",
|
|
129
|
-
cascade: "Inner World changes invalidate implementation and deployment acceptance; Ideal World changes return to preparing.",
|
|
130
|
-
},
|
|
131
|
-
},
|
|
165
|
+
return localize(
|
|
166
|
+
language,
|
|
167
|
+
`Continue idea ${name} in ${idea.worlds.implementationRevision.documentPath}, its supporting files under ${idea.worlds.implementationRevision.path}, and ${idea.ledgerPath}. Do not change ${idea.worlds.idealRevision.path} unless the Ideal World must change. After all implementation evidence is published, ask the user to accept exact revision ${idea.implementationRevision}; only then write it to implementationAcceptedRevision in ${idea.statusPath}.`,
|
|
168
|
+
`继续在 ${idea.worlds.implementationRevision.documentPath}、${idea.worlds.implementationRevision.path} 下的辅助文件和 ${idea.ledgerPath} 推进 idea ${name}。除非理想契约确实需要变化,否则不要修改 ${idea.worlds.idealRevision.path}。全部实现证据发布后,请用户明确验收精确 revision ${idea.implementationRevision};只有获得明确验收后,才将其写入 ${idea.statusPath} 的 implementationAcceptedRevision。`,
|
|
132
169
|
);
|
|
133
170
|
}
|
|
134
171
|
if (idea.state === "deploying") {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
{
|
|
140
|
-
ideaPath: idea.relativePath,
|
|
141
|
-
statusPath: idea.statusPath,
|
|
142
|
-
ledgerPath: idea.ledgerPath,
|
|
143
|
-
world: {
|
|
144
|
-
...world,
|
|
145
|
-
auxiliaryRoot: world.path,
|
|
146
|
-
nestedWorldPath: idea.worlds.implementationRevision.path,
|
|
147
|
-
revision: idea.deploymentRevision,
|
|
148
|
-
decisionField: "deploymentAcceptedRevision",
|
|
149
|
-
cascade: "Outer World changes invalidate deployment acceptance only; nested world changes cascade inward.",
|
|
150
|
-
},
|
|
151
|
-
},
|
|
172
|
+
return localize(
|
|
173
|
+
language,
|
|
174
|
+
`Continue idea ${name} from ${idea.worlds.deploymentRevision.documentPath}, its supporting files under ${idea.worlds.deploymentRevision.path}, and ${idea.ledgerPath}. Preserve nested worlds. After external evidence is complete and published, ask the user to accept exact revision ${idea.deploymentRevision}; only then write it to deploymentAcceptedRevision in ${idea.statusPath}.`,
|
|
175
|
+
`从 ${idea.worlds.deploymentRevision.documentPath}、${idea.worlds.deploymentRevision.path} 下的辅助文件和 ${idea.ledgerPath} 继续推进 idea ${name},并保留内层世界。外部证据完成且发布后,请用户明确验收精确 revision ${idea.deploymentRevision};只有获得明确验收后,才将其写入 ${idea.statusPath} 的 deploymentAcceptedRevision。`,
|
|
152
176
|
);
|
|
153
177
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
178
|
+
if (idea.state === "abandoned") {
|
|
179
|
+
return localize(
|
|
180
|
+
language,
|
|
181
|
+
`Review abandoned idea ${name} at ${idea.relativePath}. Keep abandoned: true in ${idea.statusPath}, remove it only after an explicit decision to resume, or discuss a different goal and run silvermoon create-idea.`,
|
|
182
|
+
`复查位于 ${idea.relativePath} 的已放弃 idea ${name}。保持 ${idea.statusPath} 中的 abandoned: true;只有明确决定恢复时才移除它,或者讨论另一个目标并运行 silvermoon create-idea。`,
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
return localize(
|
|
186
|
+
language,
|
|
187
|
+
`Review completed idea ${name} at ${idea.relativePath}. If its definition must change, revise the existing idea; otherwise discuss a new goal and run silvermoon create-idea.`,
|
|
188
|
+
`复查位于 ${idea.relativePath} 的已完成 idea ${name}。若其定义需要变化则修订现有 idea;否则讨论新目标并运行 silvermoon create-idea。`,
|
|
163
189
|
);
|
|
164
190
|
}
|
|
165
191
|
|
|
166
|
-
function
|
|
167
|
-
|
|
168
|
-
|
|
192
|
+
function navigationInstruction(ideas, language) {
|
|
193
|
+
const hasActiveIdea = ideas.some(({ state }) =>
|
|
194
|
+
state === "preparing" || state === "implementing" || state === "deploying"
|
|
195
|
+
);
|
|
196
|
+
return joinInstructions([
|
|
197
|
+
localize(
|
|
198
|
+
language,
|
|
199
|
+
"Choose explicitly whether to continue an active idea or create a new one.",
|
|
200
|
+
"请明确选择继续一个 active idea,或创建一个新 idea。",
|
|
201
|
+
),
|
|
202
|
+
hasActiveIdea
|
|
203
|
+
? null
|
|
204
|
+
: localize(language, "No active ideas are available.", "当前没有 active idea。"),
|
|
205
|
+
localize(
|
|
206
|
+
language,
|
|
207
|
+
"To continue, run `silvermoon whats-next <ULID-or-alias>`. To start something else, discuss the goal and run `silvermoon create-idea`.",
|
|
208
|
+
"如需继续,运行 `silvermoon whats-next <ULID-or-alias>`;如需开始其他工作,先讨论目标,再运行 `silvermoon create-idea`。",
|
|
209
|
+
),
|
|
210
|
+
]);
|
|
169
211
|
}
|
|
170
212
|
|
|
171
|
-
function
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const status = runGit(root, ["status", "--porcelain=v2", "-z", "--untracked-files=all"]);
|
|
183
|
-
if (!status.ok) {
|
|
184
|
-
return action(
|
|
185
|
-
"inspect-worktree-changes",
|
|
186
|
-
"Worktree changes could not be inspected; repair Git state before continuing.",
|
|
187
|
-
{ error: status.stderr },
|
|
188
|
-
);
|
|
189
|
-
}
|
|
213
|
+
async function assessRepositoryReadinessInternal({
|
|
214
|
+
observed,
|
|
215
|
+
outcomes,
|
|
216
|
+
requirePrimaryBranch = false,
|
|
217
|
+
recheckCommand = "silvermoon whats-next",
|
|
218
|
+
root,
|
|
219
|
+
synchronizePrimary = true,
|
|
220
|
+
}) {
|
|
221
|
+
const language = observed.outputLanguage;
|
|
190
222
|
let changes;
|
|
191
223
|
try {
|
|
192
|
-
changes =
|
|
224
|
+
changes = inspectWorktreeChanges(root);
|
|
193
225
|
} catch (caught) {
|
|
194
|
-
|
|
195
|
-
"
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
226
|
+
const problem = {
|
|
227
|
+
type: "worktree-inspection-failed",
|
|
228
|
+
summary: sanitizeGitMessage(caught.message),
|
|
229
|
+
};
|
|
230
|
+
return {
|
|
231
|
+
observation: repositoryProblemObservation(observed.observation, [problem]),
|
|
232
|
+
instructions: localize(
|
|
233
|
+
language,
|
|
234
|
+
`Repair the local Git state, verify that all changes can be inspected, then run ${command(root, recheckCommand)} again.`,
|
|
235
|
+
`修复本地 Git 状态,确认可检查全部修改后,再运行 ${command(root, recheckCommand)}。`,
|
|
236
|
+
),
|
|
237
|
+
ready: false,
|
|
238
|
+
};
|
|
206
239
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
240
|
+
|
|
241
|
+
const dirty =
|
|
242
|
+
changes.conflicted.length > 0
|
|
243
|
+
|| changes.staged.length > 0
|
|
244
|
+
|| changes.unstaged.length > 0
|
|
245
|
+
|| changes.untracked.length > 0;
|
|
246
|
+
const localProblems = [];
|
|
247
|
+
const localSteps = [];
|
|
248
|
+
if (dirty) {
|
|
249
|
+
if (changes.conflicted.length > 0) {
|
|
250
|
+
localProblems.push({
|
|
251
|
+
type: "worktree-conflicts",
|
|
252
|
+
summary: localize(
|
|
253
|
+
language,
|
|
254
|
+
`${changes.conflicted.length} conflicted path(s); ${summarizeWorktreeChanges({
|
|
255
|
+
conflicted: changes.conflicted,
|
|
256
|
+
staged: [],
|
|
257
|
+
unstaged: [],
|
|
258
|
+
untracked: [],
|
|
259
|
+
}, language)}`,
|
|
260
|
+
`${changes.conflicted.length} 个冲突路径;${summarizeWorktreeChanges({
|
|
261
|
+
conflicted: changes.conflicted,
|
|
262
|
+
staged: [],
|
|
263
|
+
unstaged: [],
|
|
264
|
+
untracked: [],
|
|
265
|
+
}, language)}`,
|
|
266
|
+
),
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
if (
|
|
270
|
+
changes.staged.length > 0
|
|
271
|
+
|| changes.unstaged.length > 0
|
|
272
|
+
|| changes.untracked.length > 0
|
|
273
|
+
) {
|
|
274
|
+
localProblems.push({
|
|
275
|
+
type: "worktree-changes",
|
|
276
|
+
summary: summarizeWorktreeChanges({
|
|
277
|
+
conflicted: [],
|
|
278
|
+
staged: changes.staged,
|
|
279
|
+
unstaged: changes.unstaged,
|
|
280
|
+
untracked: changes.untracked,
|
|
281
|
+
}, language),
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
localSteps.push(...worktreeInstructionSteps(changes, language));
|
|
213
285
|
}
|
|
214
286
|
|
|
215
|
-
const
|
|
216
|
-
if (
|
|
217
|
-
|
|
218
|
-
"
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
287
|
+
const head = resolveHead(root);
|
|
288
|
+
if (head === null) {
|
|
289
|
+
localProblems.push({
|
|
290
|
+
type: "head-missing",
|
|
291
|
+
summary: localize(
|
|
292
|
+
language,
|
|
293
|
+
"The repository has no commit at HEAD.",
|
|
294
|
+
"repository 的 HEAD 尚无 commit。",
|
|
295
|
+
),
|
|
296
|
+
});
|
|
297
|
+
localSteps.push(localize(
|
|
298
|
+
language,
|
|
299
|
+
"After resolving conflicts and deciding which local changes belong, create the initial commit on the intended branch.",
|
|
300
|
+
"解决冲突并确认应保留的本地修改后,在预期分支创建初始 commit。",
|
|
301
|
+
));
|
|
222
302
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
303
|
+
|
|
304
|
+
const branch = inspectCurrentBranch(root);
|
|
305
|
+
if (branch.branch === null) {
|
|
306
|
+
localProblems.push({
|
|
307
|
+
type: "detached-head",
|
|
308
|
+
summary: localize(
|
|
309
|
+
language,
|
|
310
|
+
head === null
|
|
311
|
+
? "The repository has no current local branch."
|
|
312
|
+
: `HEAD ${head} is detached and has no current branch.`,
|
|
313
|
+
head === null
|
|
314
|
+
? "repository 当前没有本地分支。"
|
|
315
|
+
: `HEAD ${head} 处于 detached 状态,没有当前分支。`,
|
|
316
|
+
),
|
|
317
|
+
});
|
|
318
|
+
localSteps.push(localize(
|
|
319
|
+
language,
|
|
320
|
+
`Preserve current work, then switch to or create the intended local branch whose upstream is ${observed.config.primaryRepository}#${observed.config.primaryBranch}.`,
|
|
321
|
+
`保留当前工作,然后切换或创建 upstream 为 ${observed.config.primaryRepository}#${observed.config.primaryBranch} 的预期本地分支。`,
|
|
322
|
+
));
|
|
323
|
+
} else {
|
|
324
|
+
if (requirePrimaryBranch && branch.branch !== observed.config.primaryBranch) {
|
|
325
|
+
localProblems.push({
|
|
326
|
+
type: "primary-branch-mismatch",
|
|
327
|
+
summary: localize(
|
|
328
|
+
language,
|
|
329
|
+
`Current branch is ${branch.branch}; expected configured primary branch ${observed.config.primaryBranch}.`,
|
|
330
|
+
`当前分支为 ${branch.branch};预期为 configured primary branch ${observed.config.primaryBranch}。`,
|
|
331
|
+
),
|
|
332
|
+
});
|
|
333
|
+
localSteps.push(localize(
|
|
334
|
+
language,
|
|
335
|
+
`Preserve current work, then switch to configured primary branch ${observed.config.primaryBranch}.`,
|
|
336
|
+
`保留当前工作,然后切换到 configured primary branch ${observed.config.primaryBranch}。`,
|
|
337
|
+
));
|
|
338
|
+
}
|
|
339
|
+
if (
|
|
340
|
+
branch.repository !== observed.config.primaryRepository
|
|
341
|
+
|| branch.upstreamBranch !== observed.config.primaryBranch
|
|
342
|
+
) {
|
|
343
|
+
const actual = branch.remote === null
|
|
344
|
+
? localize(language, "none", "无")
|
|
345
|
+
: `${branch.repository ?? branch.remote}#${branch.upstreamBranch ?? localize(language, "unknown", "未知")}`;
|
|
346
|
+
localProblems.push({
|
|
347
|
+
type: "primary-upstream-mismatch",
|
|
348
|
+
summary: localize(
|
|
349
|
+
language,
|
|
350
|
+
`Branch ${branch.branch} has upstream ${actual}; expected ${observed.config.primaryRepository}#${observed.config.primaryBranch}.`,
|
|
351
|
+
`分支 ${branch.branch} 的 upstream 是 ${actual};预期为 ${observed.config.primaryRepository}#${observed.config.primaryBranch}。`,
|
|
352
|
+
),
|
|
353
|
+
});
|
|
354
|
+
localSteps.push(localize(
|
|
355
|
+
language,
|
|
356
|
+
`Configure a named remote for ${observed.config.primaryRepository}, then set branch ${branch.branch} to track that remote's ${observed.config.primaryBranch} branch. Verify with ${command(root, 'git -C "<root>" rev-parse --abbrev-ref --symbolic-full-name \'@{upstream}\'')}.`,
|
|
357
|
+
`为 ${observed.config.primaryRepository} 配置 named remote,再将分支 ${branch.branch} 的 upstream 设为该 remote 的 ${observed.config.primaryBranch}。使用 ${command(root, 'git -C "<root>" rev-parse --abbrev-ref --symbolic-full-name \'@{upstream}\'')} 验证。`,
|
|
358
|
+
));
|
|
359
|
+
}
|
|
230
360
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
361
|
+
|
|
362
|
+
if (localProblems.length > 0) {
|
|
363
|
+
return {
|
|
364
|
+
branch,
|
|
365
|
+
changes,
|
|
366
|
+
head,
|
|
367
|
+
observation: repositoryProblemObservation(
|
|
368
|
+
observed.observation,
|
|
369
|
+
localProblems,
|
|
370
|
+
),
|
|
371
|
+
instructions: localRepositoryInstructions(
|
|
372
|
+
root,
|
|
373
|
+
localSteps,
|
|
374
|
+
language,
|
|
375
|
+
recheckCommand,
|
|
376
|
+
),
|
|
377
|
+
ready: false,
|
|
378
|
+
};
|
|
243
379
|
}
|
|
244
|
-
return action(
|
|
245
|
-
"integrate-primary",
|
|
246
|
-
"Preserve both local and remote primary histories and integrate them without force-pushing.",
|
|
247
|
-
{ local: local.stdout, remote: observedPrimaryCommit },
|
|
248
|
-
);
|
|
249
|
-
}
|
|
250
380
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
? { kind: "create-idea" }
|
|
260
|
-
: selector === undefined
|
|
261
|
-
? { kind: "navigate" }
|
|
262
|
-
: { kind: "select-idea", selector };
|
|
263
|
-
let local;
|
|
264
|
-
try {
|
|
265
|
-
local = await withTemporaryWorktree(repositoryRoot, "HEAD", (worktree) =>
|
|
266
|
-
loadConfig({ root: worktree }),
|
|
267
|
-
);
|
|
268
|
-
} catch (caught) {
|
|
269
|
-
return failed(repositoryRoot, [diagnostic(
|
|
270
|
-
"head.observation-failed",
|
|
271
|
-
caught.message,
|
|
272
|
-
"Restore a valid committed HEAD snapshot and retry.",
|
|
273
|
-
)], request);
|
|
381
|
+
if (!synchronizePrimary) {
|
|
382
|
+
return {
|
|
383
|
+
branch,
|
|
384
|
+
changes,
|
|
385
|
+
head,
|
|
386
|
+
observation: observed.observation,
|
|
387
|
+
ready: true,
|
|
388
|
+
};
|
|
274
389
|
}
|
|
275
|
-
if (!local.config) return failed(repositoryRoot, local.diagnostics, request);
|
|
276
390
|
|
|
277
|
-
let
|
|
391
|
+
let primary;
|
|
278
392
|
try {
|
|
279
|
-
|
|
393
|
+
primary = fetchPrimary(root, observed.config);
|
|
394
|
+
outcomes.push(outcome(
|
|
395
|
+
"fetch-primary",
|
|
396
|
+
"success",
|
|
397
|
+
localize(
|
|
398
|
+
language,
|
|
399
|
+
`Fetched ${observed.config.primaryRepository}#${observed.config.primaryBranch} at ${primary} without moving the worktree, index, branch, or named refs.`,
|
|
400
|
+
`已 fetch ${observed.config.primaryRepository}#${observed.config.primaryBranch} 的 ${primary},未移动 worktree、index、branch 或 named refs。`,
|
|
401
|
+
),
|
|
402
|
+
));
|
|
280
403
|
} catch (caught) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
"
|
|
285
|
-
|
|
404
|
+
const message = sanitizeGitMessage(caught.message);
|
|
405
|
+
outcomes.push(outcome(
|
|
406
|
+
"fetch-primary",
|
|
407
|
+
"failure",
|
|
408
|
+
localize(
|
|
409
|
+
language,
|
|
410
|
+
`Could not fetch configured primary: ${message}`,
|
|
411
|
+
`无法 fetch configured primary:${message}`,
|
|
412
|
+
),
|
|
413
|
+
));
|
|
414
|
+
return {
|
|
415
|
+
observation: repositoryProblemObservation(observed.observation, [{
|
|
416
|
+
type: "primary-fetch-failed",
|
|
417
|
+
summary: message,
|
|
418
|
+
}]),
|
|
419
|
+
instructions: localize(
|
|
420
|
+
language,
|
|
421
|
+
`Check network access, authorization, ${observed.config.primaryRepository}, and branch ${observed.config.primaryBranch}; after an observable fix, run ${command(root, recheckCommand)} again.`,
|
|
422
|
+
`检查网络、授权、${observed.config.primaryRepository} 和分支 ${observed.config.primaryBranch};产生可观察修复后,再运行 ${command(root, recheckCommand)}。`,
|
|
423
|
+
),
|
|
424
|
+
ready: false,
|
|
425
|
+
};
|
|
286
426
|
}
|
|
287
427
|
|
|
288
|
-
let
|
|
428
|
+
let relation;
|
|
289
429
|
try {
|
|
290
|
-
|
|
291
|
-
repositoryRoot,
|
|
292
|
-
observedPrimaryCommit,
|
|
293
|
-
async (worktree, tree) => {
|
|
294
|
-
const loaded = await loadConfig({ root: worktree });
|
|
295
|
-
if (!loaded.config) return { config: null, diagnostics: loaded.diagnostics, ideas: [] };
|
|
296
|
-
const layout = await inspectIdeaLayout({
|
|
297
|
-
config: loaded.config,
|
|
298
|
-
gitRoot: repositoryRoot,
|
|
299
|
-
historyCommit: observedPrimaryCommit,
|
|
300
|
-
root: worktree,
|
|
301
|
-
snapshotTree: tree,
|
|
302
|
-
});
|
|
303
|
-
return { config: loaded.config, ...layout };
|
|
304
|
-
},
|
|
305
|
-
);
|
|
430
|
+
relation = compareCommits(root, head, primary);
|
|
306
431
|
} catch (caught) {
|
|
307
|
-
return
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
432
|
+
return {
|
|
433
|
+
branch,
|
|
434
|
+
head,
|
|
435
|
+
observation: repositoryProblemObservation(observed.observation, [{
|
|
436
|
+
type: "primary-ancestry-inspection-failed",
|
|
437
|
+
summary: sanitizeGitMessage(caught.message),
|
|
438
|
+
}]),
|
|
439
|
+
primary,
|
|
440
|
+
instructions: localize(
|
|
441
|
+
language,
|
|
442
|
+
`Repair or deepen local Git history until ${head} and ${primary} can be compared, then run ${command(root, recheckCommand)} again.`,
|
|
443
|
+
`修复或补全本地 Git 历史,直到可以比较 ${head} 和 ${primary},然后再运行 ${command(root, recheckCommand)}。`,
|
|
444
|
+
),
|
|
445
|
+
ready: false,
|
|
446
|
+
};
|
|
318
447
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
if (loadedUserConfig.diagnostics.length > 0) {
|
|
322
|
-
return failed(
|
|
323
|
-
repositoryRoot,
|
|
324
|
-
loadedUserConfig.diagnostics,
|
|
325
|
-
request,
|
|
326
|
-
observedPrimaryCommit,
|
|
327
|
-
);
|
|
448
|
+
if (relation === "aligned") {
|
|
449
|
+
return { branch, head, observation: observed.observation, primary, ready: true };
|
|
328
450
|
}
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
451
|
+
const problem = {
|
|
452
|
+
type: relation === "unknown"
|
|
453
|
+
? "primary-history-incomplete"
|
|
454
|
+
: `primary-${relation}`,
|
|
455
|
+
summary: localize(
|
|
456
|
+
language,
|
|
457
|
+
`Local HEAD is ${head}; observed primary is ${primary}; relationship is ${relation}.`,
|
|
458
|
+
`本地 HEAD 为 ${head};观测到的 primary 为 ${primary};两者关系为 ${relation}。`,
|
|
459
|
+
),
|
|
333
460
|
};
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
461
|
+
let instructions;
|
|
462
|
+
if (relation === "unknown") {
|
|
463
|
+
instructions = localize(
|
|
464
|
+
language,
|
|
465
|
+
`Deepen the shallow history from remote ${branch.remote} until ${head} and ${primary} can be compared, then run ${command(root, recheckCommand)} again.`,
|
|
466
|
+
`从 remote ${branch.remote} 补全 shallow history,直到可以比较 ${head} 和 ${primary},然后再运行 ${command(root, recheckCommand)}。`,
|
|
467
|
+
);
|
|
468
|
+
} else if (relation === "behind") {
|
|
469
|
+
instructions = localize(
|
|
470
|
+
language,
|
|
471
|
+
`Fast-forward branch ${branch.branch} to observed primary ${primary} with ${command(root, `git -C "<root>" merge --ff-only ${primary}`)} without rewriting history, then run ${command(root, recheckCommand)} again.`,
|
|
472
|
+
`使用 ${command(root, `git -C "<root>" merge --ff-only ${primary}`)} 将分支 ${branch.branch} fast-forward 到已观察 primary ${primary},不要改写历史,然后再运行 ${command(root, recheckCommand)}。`,
|
|
473
|
+
);
|
|
474
|
+
} else if (relation === "ahead") {
|
|
475
|
+
instructions = localize(
|
|
476
|
+
language,
|
|
477
|
+
`Validate local commit ${head}, confirm the remote tip is still ${primary}, then push normally to ${branch.remote}/${observed.config.primaryBranch} without force. Re-run ${command(root, recheckCommand)} after the push.`,
|
|
478
|
+
`验证本地 commit ${head},确认 remote tip 仍为 ${primary},再普通 push 到 ${branch.remote}/${observed.config.primaryBranch},不要 force。push 后重新运行 ${command(root, recheckCommand)}。`,
|
|
479
|
+
);
|
|
480
|
+
} else {
|
|
481
|
+
instructions = localize(
|
|
482
|
+
language,
|
|
483
|
+
`Preserve local ${head} and remote ${primary}, integrate both histories without force-pushing, resolve and validate the result, then run ${command(root, recheckCommand)} again.`,
|
|
484
|
+
`保留本地 ${head} 与 remote ${primary},在不 force-push 的前提下整合两边历史,解决并验证结果,然后再运行 ${command(root, recheckCommand)}。`,
|
|
348
485
|
);
|
|
349
486
|
}
|
|
487
|
+
return {
|
|
488
|
+
branch,
|
|
489
|
+
head,
|
|
490
|
+
observation: repositoryProblemObservation(observed.observation, [problem]),
|
|
491
|
+
primary,
|
|
492
|
+
instructions,
|
|
493
|
+
ready: false,
|
|
494
|
+
};
|
|
495
|
+
}
|
|
350
496
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
)
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
if (!selector) {
|
|
359
|
-
const active = observed.ideas.filter(({ state }) => ACTIVE_STATES.has(state));
|
|
360
|
-
if (active.length > 1) {
|
|
361
|
-
return success(repositoryRoot, observedPrimaryCommit, request, null, action(
|
|
362
|
-
"select-active-idea",
|
|
363
|
-
"Select one active idea by ULID or unique alias.",
|
|
364
|
-
{ ideas: active.map(summary) },
|
|
365
|
-
), languageFor());
|
|
366
|
-
}
|
|
367
|
-
if (active.length === 1) {
|
|
368
|
-
return success(repositoryRoot, observedPrimaryCommit, request, active[0], action(
|
|
369
|
-
"continue-active-idea",
|
|
370
|
-
`Continue active idea ${ideaName(active[0])}.`,
|
|
371
|
-
{ idea: summary(active[0]) },
|
|
372
|
-
), languageFor(active[0]));
|
|
373
|
-
}
|
|
374
|
-
return success(repositoryRoot, observedPrimaryCommit, request, null, action(
|
|
375
|
-
"create-idea",
|
|
376
|
-
"Discuss the next goal and create one self-contained three-world idea.",
|
|
377
|
-
), languageFor());
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
const selected = requestedIdea;
|
|
381
|
-
if (!selected) {
|
|
382
|
-
return failed(repositoryRoot, [diagnostic(
|
|
383
|
-
"idea.not-found",
|
|
384
|
-
`Idea ${selector} does not exist in observed primary ${observedPrimaryCommit}.`,
|
|
385
|
-
"Choose a ULID or exact alias reported by silvermoon whats-next.",
|
|
386
|
-
selector,
|
|
387
|
-
)], request, observedPrimaryCommit);
|
|
388
|
-
}
|
|
497
|
+
export async function assessRepositoryReadiness(options) {
|
|
498
|
+
return traceAsync(
|
|
499
|
+
"repository.assess-readiness",
|
|
500
|
+
{},
|
|
501
|
+
() => assessRepositoryReadinessInternal(options),
|
|
502
|
+
);
|
|
503
|
+
}
|
|
389
504
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
505
|
+
export async function assessIdeaCreationReadiness(options) {
|
|
506
|
+
return traceAsync(
|
|
507
|
+
"repository.assess-creation-readiness",
|
|
508
|
+
{},
|
|
509
|
+
() => assessRepositoryReadinessInternal({
|
|
510
|
+
...options,
|
|
511
|
+
requirePrimaryBranch: true,
|
|
512
|
+
synchronizePrimary: false,
|
|
513
|
+
}),
|
|
397
514
|
);
|
|
398
515
|
}
|
|
399
516
|
|
|
400
517
|
export async function whatsNext({
|
|
401
|
-
create = false,
|
|
402
|
-
enforceOnboarding = false,
|
|
403
518
|
idea: selector,
|
|
519
|
+
language,
|
|
404
520
|
root = process.cwd(),
|
|
405
521
|
userHome,
|
|
406
522
|
} = {}) {
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
const
|
|
411
|
-
|
|
412
|
-
:
|
|
413
|
-
|
|
414
|
-
:
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
result: {
|
|
429
|
-
observedPrimaryCommit: null,
|
|
430
|
-
request,
|
|
431
|
-
language,
|
|
432
|
-
selectedIdea: null,
|
|
433
|
-
onboarding: gapReport,
|
|
434
|
-
action: actionWithLanguage(action(
|
|
435
|
-
"adopt-silvermoon",
|
|
436
|
-
"Complete the blocking Silvermoon onboarding gaps before idea lifecycle work.",
|
|
437
|
-
{
|
|
438
|
-
gaps: gapReport.gaps,
|
|
439
|
-
recommendedAction: gapReport.recommendedAction,
|
|
440
|
-
recheck: gapReport.recheck,
|
|
441
|
-
},
|
|
442
|
-
), language),
|
|
443
|
-
},
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
const report = await lifecycleWhatsNext({
|
|
447
|
-
create,
|
|
448
|
-
idea: selector,
|
|
449
|
-
root: repositoryRoot,
|
|
523
|
+
const canonicalLanguage = language === undefined
|
|
524
|
+
? undefined
|
|
525
|
+
: canonicalizeOutputLanguage(language);
|
|
526
|
+
const requestedRoot = resolve(root);
|
|
527
|
+
const intention = {
|
|
528
|
+
command: "whats-next",
|
|
529
|
+
args: {
|
|
530
|
+
idea: selector ?? null,
|
|
531
|
+
language: canonicalLanguage ?? null,
|
|
532
|
+
},
|
|
533
|
+
};
|
|
534
|
+
const baseRecheckCommand = selector === undefined
|
|
535
|
+
? "silvermoon whats-next"
|
|
536
|
+
: `silvermoon whats-next ${JSON.stringify(selector)}`;
|
|
537
|
+
const recheckCommand = canonicalLanguage === undefined
|
|
538
|
+
? baseRecheckCommand
|
|
539
|
+
: `${baseRecheckCommand} --language ${canonicalLanguage}`;
|
|
540
|
+
const outcomes = [];
|
|
541
|
+
let observed = await observeSnapshot({
|
|
542
|
+
outputLanguage: canonicalLanguage,
|
|
543
|
+
root: requestedRoot,
|
|
450
544
|
userHome,
|
|
545
|
+
version: { type: "worktree" },
|
|
546
|
+
});
|
|
547
|
+
if (!observed.projectReady) {
|
|
548
|
+
return createEnvelope(
|
|
549
|
+
intention,
|
|
550
|
+
observed.observation,
|
|
551
|
+
outcomes,
|
|
552
|
+
projectInstructions(
|
|
553
|
+
observed,
|
|
554
|
+
observed.observation.root,
|
|
555
|
+
observed.outputLanguage,
|
|
556
|
+
recheckCommand,
|
|
557
|
+
),
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const selected = selectIdea(observed.layout.ideas, selector);
|
|
562
|
+
if (selected?.status.language) {
|
|
563
|
+
observed = withObservationLanguage(observed, selected.status.language);
|
|
564
|
+
}
|
|
565
|
+
const readiness = await assessRepositoryReadiness({
|
|
566
|
+
observed,
|
|
567
|
+
outcomes,
|
|
568
|
+
recheckCommand,
|
|
569
|
+
root: observed.observation.root,
|
|
451
570
|
});
|
|
452
|
-
if (
|
|
453
|
-
|
|
454
|
-
|
|
571
|
+
if (!readiness.ready) {
|
|
572
|
+
return createEnvelope(
|
|
573
|
+
intention,
|
|
574
|
+
readiness.observation,
|
|
575
|
+
outcomes,
|
|
576
|
+
readiness.instructions,
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (selector === undefined) {
|
|
581
|
+
return createEnvelope(
|
|
582
|
+
intention,
|
|
583
|
+
{ ...readiness.observation, state: "navigation-ready" },
|
|
584
|
+
outcomes,
|
|
585
|
+
navigationInstruction(observed.layout.ideas, observed.outputLanguage),
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
if (!selected) {
|
|
589
|
+
return createEnvelope(
|
|
590
|
+
intention,
|
|
591
|
+
dialogueReadyObservation(readiness.observation, "idea-not-found", {
|
|
592
|
+
candidates: readiness.observation.ideas.activeIdeas,
|
|
593
|
+
}),
|
|
594
|
+
outcomes,
|
|
595
|
+
joinInstructions([
|
|
596
|
+
localize(
|
|
597
|
+
observed.outputLanguage,
|
|
598
|
+
`Idea ${selector} does not match an observed ULID or unique alias.`,
|
|
599
|
+
`Idea ${selector} 未匹配任何已观察到的 ULID 或唯一 alias。`,
|
|
600
|
+
),
|
|
601
|
+
navigationInstruction(observed.layout.ideas, observed.outputLanguage),
|
|
602
|
+
]),
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
return createEnvelope(
|
|
606
|
+
intention,
|
|
607
|
+
dialogueReadyObservation(readiness.observation, "idea-selected", {
|
|
608
|
+
selectedIdea: {
|
|
609
|
+
id: selected.id,
|
|
610
|
+
...(selected.alias === undefined ? {} : { alias: selected.alias }),
|
|
611
|
+
state: selected.state,
|
|
612
|
+
},
|
|
613
|
+
}),
|
|
614
|
+
outcomes,
|
|
615
|
+
lifecycleInstruction(selected, observed.outputLanguage),
|
|
616
|
+
);
|
|
617
|
+
}
|