silvermoon 0.2.2 → 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 +21 -9
- 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/src/adoption.js +30 -16
- 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/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
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
2
|
|
|
3
|
+
import { REPOSITORY_SKILL_PATH } from "./adoption.js";
|
|
3
4
|
import {
|
|
4
5
|
diagnosticProblem,
|
|
5
6
|
} from "./dialogue.js";
|
|
6
|
-
import {
|
|
7
|
+
import { loadConfigSnapshot } from "./config.js";
|
|
7
8
|
import { createCommandRun } from "./domain.js";
|
|
8
9
|
import {
|
|
9
10
|
fetchPrimary,
|
|
10
11
|
indexSnapshot,
|
|
11
|
-
|
|
12
|
+
resolveSnapshot,
|
|
12
13
|
runGit,
|
|
13
14
|
sanitizeGitMessage,
|
|
14
15
|
worktreeSnapshot,
|
|
15
|
-
withTemporaryTree,
|
|
16
|
-
withTemporaryWorktree,
|
|
17
16
|
} from "./git.js";
|
|
17
|
+
import { createGitSnapshotFileSystem } from "./git-snapshot.js";
|
|
18
18
|
import {
|
|
19
19
|
canonicalizeOutputLanguage,
|
|
20
20
|
DEFAULT_LANGUAGE,
|
|
@@ -23,6 +23,24 @@ import {
|
|
|
23
23
|
observeSnapshot,
|
|
24
24
|
unavailableObservation,
|
|
25
25
|
} from "./observation.js";
|
|
26
|
+
import {
|
|
27
|
+
CONFIG_PATH,
|
|
28
|
+
GUIDANCE_ROOT,
|
|
29
|
+
IDEAS_ROOT,
|
|
30
|
+
} from "./layout.js";
|
|
31
|
+
|
|
32
|
+
function preloadCheckBlob({ name }) {
|
|
33
|
+
return (
|
|
34
|
+
name === CONFIG_PATH
|
|
35
|
+
|| name === "package.json"
|
|
36
|
+
|| name.startsWith(`${REPOSITORY_SKILL_PATH}/`)
|
|
37
|
+
|| name.startsWith(`${GUIDANCE_ROOT}/`)
|
|
38
|
+
|| (
|
|
39
|
+
name.startsWith(`${IDEAS_ROOT}/`)
|
|
40
|
+
&& name.endsWith("/status.yaml")
|
|
41
|
+
)
|
|
42
|
+
);
|
|
43
|
+
}
|
|
26
44
|
|
|
27
45
|
function targetArgument({ commit, remote, staged, worktree }) {
|
|
28
46
|
if (remote) return { type: "remote" };
|
|
@@ -59,18 +77,23 @@ async function inspectTree({
|
|
|
59
77
|
userHome,
|
|
60
78
|
version,
|
|
61
79
|
}) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
80
|
+
const filesystem = createGitSnapshotFileSystem({
|
|
81
|
+
gitRoot,
|
|
82
|
+
preload: preloadCheckBlob,
|
|
83
|
+
root,
|
|
84
|
+
tree,
|
|
85
|
+
});
|
|
86
|
+
return observeSnapshot({
|
|
87
|
+
contentRoot: root,
|
|
88
|
+
filesystem,
|
|
89
|
+
gitRoot,
|
|
90
|
+
outputLanguage,
|
|
91
|
+
root,
|
|
92
|
+
projectOnly: true,
|
|
93
|
+
snapshotTree: tree,
|
|
94
|
+
userHome,
|
|
95
|
+
version,
|
|
96
|
+
});
|
|
74
97
|
}
|
|
75
98
|
|
|
76
99
|
function finishCheck(runtime, observed) {
|
|
@@ -133,7 +156,7 @@ export async function checkRepository({
|
|
|
133
156
|
if (remote) {
|
|
134
157
|
let head;
|
|
135
158
|
try {
|
|
136
|
-
head =
|
|
159
|
+
head = resolveSnapshot(repositoryRoot, "HEAD");
|
|
137
160
|
} catch (caught) {
|
|
138
161
|
return failureReport({
|
|
139
162
|
runtime,
|
|
@@ -148,11 +171,10 @@ export async function checkRepository({
|
|
|
148
171
|
}
|
|
149
172
|
let bootstrap;
|
|
150
173
|
try {
|
|
151
|
-
bootstrap = await
|
|
152
|
-
repositoryRoot,
|
|
153
|
-
head,
|
|
154
|
-
|
|
155
|
-
);
|
|
174
|
+
bootstrap = await loadConfigSnapshot({
|
|
175
|
+
gitRoot: repositoryRoot,
|
|
176
|
+
tree: head.tree,
|
|
177
|
+
});
|
|
156
178
|
} catch (caught) {
|
|
157
179
|
return failureReport({
|
|
158
180
|
runtime,
|
|
@@ -205,20 +227,15 @@ export async function checkRepository({
|
|
|
205
227
|
}
|
|
206
228
|
const primary = fetched.result.commit;
|
|
207
229
|
try {
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
snapshotTree: tree,
|
|
218
|
-
userHome,
|
|
219
|
-
version: { type: "remote", commit: primary },
|
|
220
|
-
}),
|
|
221
|
-
);
|
|
230
|
+
const snapshot = resolveSnapshot(repositoryRoot, primary);
|
|
231
|
+
const observed = await inspectTree({
|
|
232
|
+
gitRoot: repositoryRoot,
|
|
233
|
+
outputLanguage: canonicalLanguage,
|
|
234
|
+
root: repositoryRoot,
|
|
235
|
+
tree: snapshot.tree,
|
|
236
|
+
userHome,
|
|
237
|
+
version: { type: "remote", commit: primary },
|
|
238
|
+
});
|
|
222
239
|
return finishCheck(runtime, observed);
|
|
223
240
|
} catch (caught) {
|
|
224
241
|
return failureReport({
|
|
@@ -236,9 +253,9 @@ export async function checkRepository({
|
|
|
236
253
|
|
|
237
254
|
if (commit !== undefined || (!staged && !worktree)) {
|
|
238
255
|
const revision = commit ?? "HEAD";
|
|
239
|
-
let
|
|
256
|
+
let snapshot;
|
|
240
257
|
try {
|
|
241
|
-
|
|
258
|
+
snapshot = resolveSnapshot(repositoryRoot, revision);
|
|
242
259
|
} catch (caught) {
|
|
243
260
|
return failureReport({
|
|
244
261
|
runtime,
|
|
@@ -252,20 +269,14 @@ export async function checkRepository({
|
|
|
252
269
|
});
|
|
253
270
|
}
|
|
254
271
|
try {
|
|
255
|
-
const observed = await
|
|
256
|
-
repositoryRoot,
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
projectOnly: true,
|
|
264
|
-
snapshotTree: tree,
|
|
265
|
-
userHome,
|
|
266
|
-
version: { type: "commit", commit: resolvedCommit },
|
|
267
|
-
}),
|
|
268
|
-
);
|
|
272
|
+
const observed = await inspectTree({
|
|
273
|
+
gitRoot: repositoryRoot,
|
|
274
|
+
outputLanguage: canonicalLanguage,
|
|
275
|
+
root: repositoryRoot,
|
|
276
|
+
tree: snapshot.tree,
|
|
277
|
+
userHome,
|
|
278
|
+
version: { type: "commit", commit: snapshot.commit },
|
|
279
|
+
});
|
|
269
280
|
return finishCheck(runtime, observed);
|
|
270
281
|
} catch (caught) {
|
|
271
282
|
return failureReport({
|
|
@@ -276,7 +287,7 @@ export async function checkRepository({
|
|
|
276
287
|
summary: sanitizeGitMessage(caught.message),
|
|
277
288
|
},
|
|
278
289
|
root: repositoryRoot,
|
|
279
|
-
version: { type: "commit", commit:
|
|
290
|
+
version: { type: "commit", commit: snapshot.commit },
|
|
280
291
|
});
|
|
281
292
|
}
|
|
282
293
|
}
|
package/src/language.js
CHANGED
|
@@ -40,6 +40,10 @@ export function canonicalizeOutputLanguage(value) {
|
|
|
40
40
|
caught.exitCode = 2;
|
|
41
41
|
throw caught;
|
|
42
42
|
}
|
|
43
|
+
canonical = {
|
|
44
|
+
en: "en-US",
|
|
45
|
+
zh: "zh-CN",
|
|
46
|
+
}[canonical.toLowerCase()] ?? canonical;
|
|
43
47
|
if (!OUTPUT_LANGUAGE_SET.has(canonical)) {
|
|
44
48
|
const error = new Error(
|
|
45
49
|
`unsupported output language: ${canonical}; expected ${OUTPUT_LANGUAGES.join(" or ")}`,
|