silvermoon 0.2.1 → 0.3.0-rc.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/LICENSE +21 -0
- package/README.md +115 -121
- package/README.zh-CN.md +96 -95
- package/bin/silvermoon.js +7 -2
- package/docs/getting-started.md +23 -11
- package/docs/maintaining.md +83 -6
- package/docs/npm-package-releases.md +33 -4
- package/docs/operations.md +11 -7
- package/docs/reference.md +131 -32
- package/docs/repository-tasks.md +43 -15
- package/package.json +46 -8
- package/skills/silvermoon/SKILL.md +224 -123
- package/skills/silvermoon/references/adoption.md +3 -2
- package/src/adoption.js +51 -35
- package/src/cli.js +148 -39
- package/src/config.js +76 -16
- package/src/create-idea.js +22 -10
- package/src/git-snapshot.js +212 -0
- package/src/git.js +209 -18
- package/src/guidance.js +65 -23
- package/src/idea-layout.js +59 -23
- package/src/idea-metadata.js +48 -0
- package/src/idea-templates.js +133 -0
- package/src/index.js +64 -53
- package/src/language.js +4 -0
- package/src/list-ideas.js +118 -55
- package/src/observation.js +9 -1
- package/src/repository.js +2 -8
- package/src/response.js +93 -95
- package/src/subprocess.js +27 -0
- package/src/trace.js +37 -2
- package/src/tui-table.js +71 -0
- package/src/tui.js +205 -0
- package/src/whatsnext.js +71 -18
- package/assets/silvermoon-avatar.svg +0 -395
- package/assets/silvermoon.svg +0 -176
- package/docs/assets/silvermoon-avatar.svg +0 -395
package/src/guidance.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { TextDecoder } from "node:util";
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
listTreeEntries,
|
|
7
|
-
readGitBlob,
|
|
4
|
+
inspectTree,
|
|
5
|
+
readGitBlobs,
|
|
8
6
|
} from "./git.js";
|
|
9
7
|
import {
|
|
10
8
|
GUIDANCE_PHASES,
|
|
@@ -50,22 +48,20 @@ function unexpectedEntry(name) {
|
|
|
50
48
|
);
|
|
51
49
|
}
|
|
52
50
|
|
|
53
|
-
function
|
|
51
|
+
function contentSizeDiagnostic(entry, phase) {
|
|
54
52
|
const path = phaseGuidancePath(phase);
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
),
|
|
64
|
-
};
|
|
65
|
-
}
|
|
53
|
+
if (entry.size <= MAX_PHASE_GUIDANCE_BYTES) return null;
|
|
54
|
+
return error(
|
|
55
|
+
"guidance.file.too-large",
|
|
56
|
+
path,
|
|
57
|
+
`Phase guidance exceeds the ${MAX_PHASE_GUIDANCE_BYTES}-byte limit: ${path}`,
|
|
58
|
+
`Reduce ${path} to at most ${MAX_PHASE_GUIDANCE_BYTES} UTF-8 bytes.`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
66
61
|
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
function inspectContent(bytes, entry, phase) {
|
|
63
|
+
const path = phaseGuidancePath(phase);
|
|
64
|
+
if (bytes.length !== entry.size) {
|
|
69
65
|
throw new Error(
|
|
70
66
|
`Git blob ${entry.object} changed size while reading ${path}`,
|
|
71
67
|
);
|
|
@@ -121,11 +117,20 @@ function inspectContent(gitRoot, entry, phase) {
|
|
|
121
117
|
}
|
|
122
118
|
|
|
123
119
|
async function inspectGuidanceInternal({
|
|
120
|
+
filesystem,
|
|
124
121
|
gitRoot,
|
|
125
122
|
phase,
|
|
126
123
|
snapshotTree,
|
|
127
124
|
}) {
|
|
128
|
-
const
|
|
125
|
+
const treeEntries = filesystem === undefined
|
|
126
|
+
? inspectTree(gitRoot, snapshotTree, GUIDANCE_ROOT)
|
|
127
|
+
: null;
|
|
128
|
+
const rootEntry = treeEntries === null
|
|
129
|
+
? filesystem.snapshotEntry(GUIDANCE_ROOT)
|
|
130
|
+
: treeEntries.find(({ name }) => name === GUIDANCE_ROOT);
|
|
131
|
+
if (rootEntry === undefined) {
|
|
132
|
+
return { state: "absent", diagnostics: [] };
|
|
133
|
+
}
|
|
129
134
|
if (rootEntry === null) {
|
|
130
135
|
return { state: "absent", diagnostics: [] };
|
|
131
136
|
}
|
|
@@ -133,7 +138,15 @@ async function inspectGuidanceInternal({
|
|
|
133
138
|
return { state: "invalid", diagnostics: [invalidDirectory()] };
|
|
134
139
|
}
|
|
135
140
|
|
|
136
|
-
const
|
|
141
|
+
const prefix = `${GUIDANCE_ROOT}/`;
|
|
142
|
+
const entries = (
|
|
143
|
+
treeEntries === null
|
|
144
|
+
? filesystem.snapshotEntries(GUIDANCE_ROOT)
|
|
145
|
+
: treeEntries.filter(({ name }) =>
|
|
146
|
+
name.startsWith(prefix) && !name.slice(prefix.length).includes("/")
|
|
147
|
+
)
|
|
148
|
+
)
|
|
149
|
+
.map((entry) => ({ ...entry, name: entry.name.slice(prefix.length) }));
|
|
137
150
|
const entriesByName = new Map(entries.map((entry) => [entry.name, entry]));
|
|
138
151
|
const phases = phase === undefined ? GUIDANCE_PHASES : [phase];
|
|
139
152
|
const diagnostics = [];
|
|
@@ -153,6 +166,7 @@ async function inspectGuidanceInternal({
|
|
|
153
166
|
|
|
154
167
|
let selectedGuidance;
|
|
155
168
|
let selectedPresent = false;
|
|
169
|
+
const contentEntries = [];
|
|
156
170
|
for (const candidate of phases) {
|
|
157
171
|
const path = phaseGuidancePath(candidate);
|
|
158
172
|
const name = path.slice(`${GUIDANCE_ROOT}/`.length);
|
|
@@ -163,8 +177,29 @@ async function inspectGuidanceInternal({
|
|
|
163
177
|
diagnostics.push(invalidFile(path));
|
|
164
178
|
continue;
|
|
165
179
|
}
|
|
166
|
-
const
|
|
167
|
-
if (
|
|
180
|
+
const sizeDiagnostic = contentSizeDiagnostic(entry, candidate);
|
|
181
|
+
if (sizeDiagnostic) {
|
|
182
|
+
diagnostics.push(sizeDiagnostic);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
contentEntries.push({ candidate, entry });
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const blobs = filesystem === undefined
|
|
189
|
+
? readGitBlobs(
|
|
190
|
+
gitRoot,
|
|
191
|
+
contentEntries.map(({ entry }) => entry.object),
|
|
192
|
+
)
|
|
193
|
+
: new Map(await Promise.all(contentEntries.map(async ({ candidate, entry }) => [
|
|
194
|
+
entry.object,
|
|
195
|
+
await filesystem.snapshotFile(phaseGuidancePath(candidate)),
|
|
196
|
+
])));
|
|
197
|
+
for (const { candidate, entry } of contentEntries) {
|
|
198
|
+
const bytes = blobs.get(entry.object);
|
|
199
|
+
if (bytes === undefined) {
|
|
200
|
+
throw new Error(`Missing Git blob content for ${entry.object}`);
|
|
201
|
+
}
|
|
202
|
+
const inspected = inspectContent(bytes, entry, candidate);
|
|
168
203
|
if (inspected.diagnostics) diagnostics.push(...inspected.diagnostics);
|
|
169
204
|
if (phase !== undefined && inspected.guidance) {
|
|
170
205
|
selectedGuidance = inspected.guidance;
|
|
@@ -183,6 +218,7 @@ async function inspectGuidanceInternal({
|
|
|
183
218
|
}
|
|
184
219
|
|
|
185
220
|
export async function inspectPhaseGuidance({
|
|
221
|
+
filesystem,
|
|
186
222
|
gitRoot,
|
|
187
223
|
phase,
|
|
188
224
|
snapshotTree,
|
|
@@ -194,6 +230,7 @@ export async function inspectPhaseGuidance({
|
|
|
194
230
|
async () => {
|
|
195
231
|
try {
|
|
196
232
|
return await inspectGuidanceInternal({
|
|
233
|
+
filesystem,
|
|
197
234
|
gitRoot,
|
|
198
235
|
phase,
|
|
199
236
|
snapshotTree,
|
|
@@ -214,6 +251,7 @@ export async function inspectPhaseGuidance({
|
|
|
214
251
|
}
|
|
215
252
|
|
|
216
253
|
export async function inspectAllGuidance({
|
|
254
|
+
filesystem,
|
|
217
255
|
gitRoot,
|
|
218
256
|
snapshotTree,
|
|
219
257
|
}) {
|
|
@@ -222,7 +260,11 @@ export async function inspectAllGuidance({
|
|
|
222
260
|
{ scope: "all" },
|
|
223
261
|
async () => {
|
|
224
262
|
try {
|
|
225
|
-
return await inspectGuidanceInternal({
|
|
263
|
+
return await inspectGuidanceInternal({
|
|
264
|
+
filesystem,
|
|
265
|
+
gitRoot,
|
|
266
|
+
snapshotTree,
|
|
267
|
+
});
|
|
226
268
|
} catch (caught) {
|
|
227
269
|
return {
|
|
228
270
|
state: "invalid",
|
package/src/idea-layout.js
CHANGED
|
@@ -5,6 +5,8 @@ import { deriveIdeaState, isValidUlid, parseIdeaStatus } from "./ideas.js";
|
|
|
5
5
|
import { inspectTreePaths, worktreeSnapshot } from "./git.js";
|
|
6
6
|
import { IDEAS_ROOT, ideaPaths } from "./layout.js";
|
|
7
7
|
|
|
8
|
+
const DEFAULT_FILESYSTEM = { lstat, readFile, readdir };
|
|
9
|
+
|
|
8
10
|
function displayPath(root, path) {
|
|
9
11
|
return relative(root, path).replaceAll("\\", "/");
|
|
10
12
|
}
|
|
@@ -13,18 +15,18 @@ function error(code, path, message, remediation) {
|
|
|
13
15
|
return { code, level: "error", path, message, remediation };
|
|
14
16
|
}
|
|
15
17
|
|
|
16
|
-
async function metadata(path) {
|
|
18
|
+
async function metadata(path, filesystem) {
|
|
17
19
|
try {
|
|
18
|
-
return await lstat(path);
|
|
20
|
+
return await filesystem.lstat(path);
|
|
19
21
|
} catch (caught) {
|
|
20
22
|
if (caught.code === "ENOENT") return null;
|
|
21
23
|
throw caught;
|
|
22
24
|
}
|
|
23
25
|
}
|
|
24
26
|
|
|
25
|
-
async function requireDirectory(root, path, diagnostics) {
|
|
27
|
+
async function requireDirectory(root, path, diagnostics, filesystem) {
|
|
26
28
|
const absolute = resolve(root, path);
|
|
27
|
-
const value = await metadata(absolute);
|
|
29
|
+
const value = await metadata(absolute, filesystem);
|
|
28
30
|
if (!value || !value.isDirectory() || value.isSymbolicLink()) {
|
|
29
31
|
diagnostics.push(error(
|
|
30
32
|
"idea.world.invalid-directory",
|
|
@@ -37,9 +39,9 @@ async function requireDirectory(root, path, diagnostics) {
|
|
|
37
39
|
return true;
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
async function requireDocument(root, path, diagnostics) {
|
|
42
|
+
async function requireDocument(root, path, diagnostics, filesystem) {
|
|
41
43
|
const absolute = resolve(root, path);
|
|
42
|
-
const value = await metadata(absolute);
|
|
44
|
+
const value = await metadata(absolute, filesystem);
|
|
43
45
|
if (!value || !value.isFile() || value.isSymbolicLink()) {
|
|
44
46
|
diagnostics.push(error(
|
|
45
47
|
"idea.world.missing-document",
|
|
@@ -52,8 +54,8 @@ async function requireDocument(root, path, diagnostics) {
|
|
|
52
54
|
return true;
|
|
53
55
|
}
|
|
54
56
|
|
|
55
|
-
async function requireLedger(root, path, diagnostics) {
|
|
56
|
-
const value = await metadata(resolve(root, path));
|
|
57
|
+
async function requireLedger(root, path, diagnostics, filesystem) {
|
|
58
|
+
const value = await metadata(resolve(root, path), filesystem);
|
|
57
59
|
if (!value) {
|
|
58
60
|
diagnostics.push(error(
|
|
59
61
|
"idea.ledger.missing-file",
|
|
@@ -75,11 +77,13 @@ async function requireLedger(root, path, diagnostics) {
|
|
|
75
77
|
return true;
|
|
76
78
|
}
|
|
77
79
|
|
|
78
|
-
async function rejectSymlinks(root, path, diagnostics) {
|
|
80
|
+
async function rejectSymlinks(root, path, diagnostics, filesystem) {
|
|
79
81
|
const absolute = resolve(root, path);
|
|
80
|
-
for (
|
|
82
|
+
for (
|
|
83
|
+
const entry of await filesystem.readdir(absolute, { withFileTypes: true })
|
|
84
|
+
) {
|
|
81
85
|
const entryPath = `${path}/${entry.name}`;
|
|
82
|
-
const value = await lstat(resolve(root, entryPath));
|
|
86
|
+
const value = await filesystem.lstat(resolve(root, entryPath));
|
|
83
87
|
if (value.isSymbolicLink()) {
|
|
84
88
|
diagnostics.push(error(
|
|
85
89
|
"idea.world.symlink",
|
|
@@ -89,7 +93,9 @@ async function rejectSymlinks(root, path, diagnostics) {
|
|
|
89
93
|
));
|
|
90
94
|
continue;
|
|
91
95
|
}
|
|
92
|
-
if (value.isDirectory())
|
|
96
|
+
if (value.isDirectory()) {
|
|
97
|
+
await rejectSymlinks(root, entryPath, diagnostics, filesystem);
|
|
98
|
+
}
|
|
93
99
|
}
|
|
94
100
|
}
|
|
95
101
|
|
|
@@ -109,13 +115,14 @@ function requiredTree(objects, path) {
|
|
|
109
115
|
|
|
110
116
|
export async function inspectIdeaLayout({
|
|
111
117
|
config: _config,
|
|
118
|
+
filesystem = DEFAULT_FILESYSTEM,
|
|
112
119
|
root,
|
|
113
120
|
gitRoot = root,
|
|
114
121
|
snapshotTree,
|
|
115
122
|
}) {
|
|
116
123
|
const diagnostics = [];
|
|
117
124
|
const ideasRoot = resolve(root, IDEAS_ROOT);
|
|
118
|
-
const ideasRootMetadata = await metadata(ideasRoot);
|
|
125
|
+
const ideasRootMetadata = await metadata(ideasRoot, filesystem);
|
|
119
126
|
if (!ideasRootMetadata) {
|
|
120
127
|
return {
|
|
121
128
|
diagnostics: [error(
|
|
@@ -160,7 +167,9 @@ export async function inspectIdeaLayout({
|
|
|
160
167
|
const aliases = new Map();
|
|
161
168
|
const ideas = [];
|
|
162
169
|
const caseNames = new Map();
|
|
163
|
-
const entries = (
|
|
170
|
+
const entries = (
|
|
171
|
+
await filesystem.readdir(ideasRoot, { withFileTypes: true })
|
|
172
|
+
)
|
|
164
173
|
.sort((left, right) => left.name.localeCompare(right.name));
|
|
165
174
|
const ideaIds = new Set(
|
|
166
175
|
entries.filter((entry) => isValidUlid(entry.name)).map((entry) => entry.name),
|
|
@@ -171,7 +180,18 @@ export async function inspectIdeaLayout({
|
|
|
171
180
|
});
|
|
172
181
|
let worldObjects;
|
|
173
182
|
try {
|
|
174
|
-
worldObjects =
|
|
183
|
+
worldObjects = typeof filesystem.snapshotEntry === "function"
|
|
184
|
+
? new Map(worldPaths.map((path) => {
|
|
185
|
+
const entry = filesystem.snapshotEntry(path);
|
|
186
|
+
return [
|
|
187
|
+
path,
|
|
188
|
+
{
|
|
189
|
+
object: entry?.object ?? null,
|
|
190
|
+
type: entry?.type ?? "missing",
|
|
191
|
+
},
|
|
192
|
+
];
|
|
193
|
+
}))
|
|
194
|
+
: inspectTreePaths(gitRoot, resolvedSnapshotTree, worldPaths);
|
|
175
195
|
} catch (caught) {
|
|
176
196
|
return {
|
|
177
197
|
diagnostics: [error(
|
|
@@ -185,7 +205,7 @@ export async function inspectIdeaLayout({
|
|
|
185
205
|
}
|
|
186
206
|
for (const entry of entries) {
|
|
187
207
|
const folderPath = resolve(ideasRoot, entry.name);
|
|
188
|
-
const folderMetadata = await lstat(folderPath);
|
|
208
|
+
const folderMetadata = await filesystem.lstat(folderPath);
|
|
189
209
|
const relativePath = displayPath(root, folderPath);
|
|
190
210
|
const folded = entry.name.toUpperCase();
|
|
191
211
|
const existingCase = caseNames.get(folded);
|
|
@@ -219,7 +239,9 @@ export async function inspectIdeaLayout({
|
|
|
219
239
|
}
|
|
220
240
|
|
|
221
241
|
const paths = ideaPaths(entry.name);
|
|
222
|
-
const children = (
|
|
242
|
+
const children = (
|
|
243
|
+
await filesystem.readdir(folderPath, { withFileTypes: true })
|
|
244
|
+
)
|
|
223
245
|
.sort((left, right) => left.name.localeCompare(right.name));
|
|
224
246
|
for (const child of children) {
|
|
225
247
|
if (
|
|
@@ -236,7 +258,7 @@ export async function inspectIdeaLayout({
|
|
|
236
258
|
"Keep only status.yaml, ledger.md, and outer/ at the idea root; put supporting files in their world.",
|
|
237
259
|
));
|
|
238
260
|
}
|
|
239
|
-
await requireLedger(root, paths.ledgerPath, diagnostics);
|
|
261
|
+
await requireLedger(root, paths.ledgerPath, diagnostics, filesystem);
|
|
240
262
|
const requiredDirectories = [
|
|
241
263
|
paths.outerPath,
|
|
242
264
|
paths.innerPath,
|
|
@@ -248,14 +270,28 @@ export async function inspectIdeaLayout({
|
|
|
248
270
|
paths.ideaDocumentPath,
|
|
249
271
|
];
|
|
250
272
|
const validDirectories = (await Promise.all(
|
|
251
|
-
requiredDirectories.map((path) =>
|
|
273
|
+
requiredDirectories.map((path) =>
|
|
274
|
+
requireDirectory(root, path, diagnostics, filesystem)
|
|
275
|
+
),
|
|
252
276
|
)).every(Boolean);
|
|
253
277
|
const validDocuments = (await Promise.all(
|
|
254
|
-
requiredDocuments.map((path) =>
|
|
278
|
+
requiredDocuments.map((path) =>
|
|
279
|
+
requireDocument(root, path, diagnostics, filesystem)
|
|
280
|
+
),
|
|
255
281
|
)).every(Boolean);
|
|
256
|
-
if (validDirectories)
|
|
282
|
+
if (validDirectories) {
|
|
283
|
+
await rejectSymlinks(
|
|
284
|
+
root,
|
|
285
|
+
paths.outerPath,
|
|
286
|
+
diagnostics,
|
|
287
|
+
filesystem,
|
|
288
|
+
);
|
|
289
|
+
}
|
|
257
290
|
|
|
258
|
-
const statusMetadata = await metadata(
|
|
291
|
+
const statusMetadata = await metadata(
|
|
292
|
+
resolve(root, paths.statusPath),
|
|
293
|
+
filesystem,
|
|
294
|
+
);
|
|
259
295
|
if (!statusMetadata || !statusMetadata.isFile() || statusMetadata.isSymbolicLink()) {
|
|
260
296
|
diagnostics.push(error(
|
|
261
297
|
"idea.status.invalid-file",
|
|
@@ -269,7 +305,7 @@ export async function inspectIdeaLayout({
|
|
|
269
305
|
let status;
|
|
270
306
|
try {
|
|
271
307
|
status = parseIdeaStatus(
|
|
272
|
-
await readFile(resolve(root, paths.statusPath), "utf8"),
|
|
308
|
+
await filesystem.readFile(resolve(root, paths.statusPath), "utf8"),
|
|
273
309
|
{ objectIdLength: repositoryObjectIdLength },
|
|
274
310
|
);
|
|
275
311
|
} catch (caught) {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { extractIdeaTitle, ideaInventoryItem } from "./idea-query.js";
|
|
5
|
+
import { localize } from "./language.js";
|
|
6
|
+
|
|
7
|
+
export async function readIdeaInventoryItem(root, idea) {
|
|
8
|
+
const documentPath = idea.worlds.idealRevision.documentPath;
|
|
9
|
+
try {
|
|
10
|
+
const source = await readFile(
|
|
11
|
+
resolve(root, ...documentPath.split("/")),
|
|
12
|
+
"utf8",
|
|
13
|
+
);
|
|
14
|
+
return ideaInventoryItem(idea, extractIdeaTitle(source));
|
|
15
|
+
} catch (caught) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
`Cannot read idea metadata for ${idea.id} at ${documentPath}: ${caught.message}`,
|
|
18
|
+
{ cause: caught },
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function metadataFailureObservation(observed, caught, command) {
|
|
24
|
+
const { ideas: _ideas, ...base } = observed.observation;
|
|
25
|
+
const problem = {
|
|
26
|
+
type: "idea-metadata-unavailable",
|
|
27
|
+
summary: localize(
|
|
28
|
+
observed.outputLanguage,
|
|
29
|
+
caught.message,
|
|
30
|
+
`无法读取 idea metadata:${caught.message}`,
|
|
31
|
+
),
|
|
32
|
+
};
|
|
33
|
+
return {
|
|
34
|
+
observation: {
|
|
35
|
+
...base,
|
|
36
|
+
state: "project-setup-required",
|
|
37
|
+
observedThrough: "configuration",
|
|
38
|
+
problems: [problem],
|
|
39
|
+
},
|
|
40
|
+
responseContext: {
|
|
41
|
+
nextSteps: localize(
|
|
42
|
+
observed.outputLanguage,
|
|
43
|
+
`Repair the reported idea document and retry \`${command}\`.`,
|
|
44
|
+
`修复报告的 idea document 后,重新运行 \`${command}\`。`,
|
|
45
|
+
),
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
package/src/idea-templates.js
CHANGED
|
@@ -107,3 +107,136 @@ export const LEDGER_TEMPLATE = `# Ledger
|
|
|
107
107
|
|
|
108
108
|
- [ ] **D-AC01:** Criterion title
|
|
109
109
|
`;
|
|
110
|
+
|
|
111
|
+
const CHINESE_IDEA_TEMPLATE = `# 将标题替换为这个 idea 的具体名称
|
|
112
|
+
|
|
113
|
+
## 意图
|
|
114
|
+
|
|
115
|
+
<!-- 用一两句话说明期望结果。 -->
|
|
116
|
+
|
|
117
|
+
## 背景
|
|
118
|
+
|
|
119
|
+
<!-- 描述当前问题、情境或机会。 -->
|
|
120
|
+
|
|
121
|
+
## 期望结果
|
|
122
|
+
|
|
123
|
+
<!-- 描述应当实现的、对外有意义的状态。 -->
|
|
124
|
+
|
|
125
|
+
## 范围
|
|
126
|
+
|
|
127
|
+
### 范围内
|
|
128
|
+
|
|
129
|
+
<!-- 描述这个 idea 包含的内容。 -->
|
|
130
|
+
|
|
131
|
+
### 范围外
|
|
132
|
+
|
|
133
|
+
<!-- 描述这个 idea 有意排除的相邻工作。 -->
|
|
134
|
+
|
|
135
|
+
## 约束
|
|
136
|
+
|
|
137
|
+
<!-- 记录重要的产品、仓库、兼容性或操作约束。 -->
|
|
138
|
+
|
|
139
|
+
## 待解决问题
|
|
140
|
+
|
|
141
|
+
<!-- 记录尚未解决的决定;没有时删除本节。 -->
|
|
142
|
+
`;
|
|
143
|
+
|
|
144
|
+
const CHINESE_IMPLEMENTATION_TEMPLATE = `# Implementation
|
|
145
|
+
|
|
146
|
+
## Steps
|
|
147
|
+
|
|
148
|
+
<!--
|
|
149
|
+
为每个步骤分配稳定的 I-Sxx 标识符和三级标题。
|
|
150
|
+
说明修改内容、边界和重要设计细节。
|
|
151
|
+
不要在本文档中使用任务列表复选框。
|
|
152
|
+
-->
|
|
153
|
+
|
|
154
|
+
### I-S01: 步骤标题
|
|
155
|
+
|
|
156
|
+
<!-- 描述该实施步骤。 -->
|
|
157
|
+
|
|
158
|
+
## Acceptance criteria
|
|
159
|
+
|
|
160
|
+
<!--
|
|
161
|
+
为每项标准分配稳定的 I-ACxx 标识符和三级标题。
|
|
162
|
+
同时说明可观察结果及其证明方法。
|
|
163
|
+
不要创建单独的验证章节,也不要使用任务列表复选框。
|
|
164
|
+
-->
|
|
165
|
+
|
|
166
|
+
### I-AC01: 标准标题
|
|
167
|
+
|
|
168
|
+
<!-- 描述所需结果以及 Agent 如何证明该结果。 -->
|
|
169
|
+
`;
|
|
170
|
+
|
|
171
|
+
const CHINESE_DEPLOYMENT_TEMPLATE = `# Deployment
|
|
172
|
+
|
|
173
|
+
## Steps
|
|
174
|
+
|
|
175
|
+
<!--
|
|
176
|
+
为每个步骤分配稳定的 D-Sxx 标识符和三级标题。
|
|
177
|
+
描述部署或现实世界验证工作。
|
|
178
|
+
不要在本文档中使用任务列表复选框。
|
|
179
|
+
-->
|
|
180
|
+
|
|
181
|
+
### D-S01: 步骤标题
|
|
182
|
+
|
|
183
|
+
<!-- 描述该部署或外部验证步骤。 -->
|
|
184
|
+
|
|
185
|
+
## Acceptance criteria
|
|
186
|
+
|
|
187
|
+
<!--
|
|
188
|
+
为每项标准分配稳定的 D-ACxx 标识符和三级标题。
|
|
189
|
+
同时说明可观察的外部结果及其证明方法。
|
|
190
|
+
不要创建单独的验证章节,也不要使用任务列表复选框。
|
|
191
|
+
-->
|
|
192
|
+
|
|
193
|
+
### D-AC01: 标准标题
|
|
194
|
+
|
|
195
|
+
<!-- 描述所需外部结果以及 Agent 如何证明该结果。 -->
|
|
196
|
+
`;
|
|
197
|
+
|
|
198
|
+
const CHINESE_LEDGER_TEMPLATE = `# Ledger
|
|
199
|
+
|
|
200
|
+
## Implementation
|
|
201
|
+
|
|
202
|
+
### Implementation steps
|
|
203
|
+
|
|
204
|
+
- [ ] **I-S01:** 步骤标题
|
|
205
|
+
|
|
206
|
+
### Implementation acceptance criteria
|
|
207
|
+
|
|
208
|
+
- [ ] **I-AC01:** 标准标题
|
|
209
|
+
|
|
210
|
+
## Deployment
|
|
211
|
+
|
|
212
|
+
### Deployment steps
|
|
213
|
+
|
|
214
|
+
- [ ] **D-S01:** 步骤标题
|
|
215
|
+
|
|
216
|
+
### Deployment acceptance criteria
|
|
217
|
+
|
|
218
|
+
- [ ] **D-AC01:** 标准标题
|
|
219
|
+
`;
|
|
220
|
+
|
|
221
|
+
export function ideaTemplates(contentLanguage) {
|
|
222
|
+
const normalized = contentLanguage.toLowerCase();
|
|
223
|
+
if (normalized === "zh" || normalized.startsWith("zh-")) {
|
|
224
|
+
return {
|
|
225
|
+
idea: CHINESE_IDEA_TEMPLATE,
|
|
226
|
+
implementation: CHINESE_IMPLEMENTATION_TEMPLATE,
|
|
227
|
+
deployment: CHINESE_DEPLOYMENT_TEMPLATE,
|
|
228
|
+
ledger: CHINESE_LEDGER_TEMPLATE,
|
|
229
|
+
localized: true,
|
|
230
|
+
templateLanguage: "zh-CN",
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
const localized = normalized === "en" || normalized.startsWith("en-");
|
|
234
|
+
return {
|
|
235
|
+
idea: IDEA_TEMPLATE,
|
|
236
|
+
implementation: IMPLEMENTATION_TEMPLATE,
|
|
237
|
+
deployment: DEPLOYMENT_TEMPLATE,
|
|
238
|
+
ledger: LEDGER_TEMPLATE,
|
|
239
|
+
localized,
|
|
240
|
+
templateLanguage: "en-US",
|
|
241
|
+
};
|
|
242
|
+
}
|