silvermoon 0.0.1 → 0.0.3

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/src/index.js CHANGED
@@ -1,260 +1,260 @@
1
- import { resolve } from "node:path";
2
-
3
- import { loadConfig } from "./config.js";
4
- import {
5
- fetchPrimary,
6
- indexSnapshot,
7
- resolveCommit,
8
- runGit,
9
- worktreeSnapshot,
10
- withTemporaryTree,
11
- withTemporaryWorktree,
12
- } from "./git.js";
13
- import { inspectIdeaLayout } from "./idea-layout.js";
14
-
15
- function ideaSummary(idea) {
16
- const value = {
17
- id: idea.id,
18
- idealRevision: idea.idealRevision,
19
- implementationRevision: idea.implementationRevision,
20
- deploymentRevision: idea.deploymentRevision,
21
- state: idea.state,
22
- };
23
- if (idea.alias !== undefined) value.alias = idea.alias;
24
- return value;
25
- }
26
-
27
- async function inspectSnapshot({
28
- baseRevision,
29
- gitRoot = root,
30
- historyCommit,
31
- root,
32
- snapshotTree,
33
- target = "local",
34
- validateCandidate = false,
35
- }) {
36
- const repositoryRoot = resolve(root);
37
- const loaded = await loadConfig({ root: repositoryRoot });
38
- const layout = loaded.config
39
- ? await inspectIdeaLayout({
40
- baseRevision,
41
- config: loaded.config,
42
- gitRoot,
43
- historyCommit,
44
- root: repositoryRoot,
45
- snapshotTree,
46
- validateCandidate,
47
- })
48
- : { diagnostics: [], ideas: [] };
49
- const diagnostics = [
50
- ...loaded.diagnostics,
51
- ...layout.diagnostics,
52
- ];
53
-
54
- const report = {
55
- command: "check",
56
- ok: diagnostics.every(({ level }) => level !== "error"),
57
- root: repositoryRoot,
58
- diagnostics,
59
- result: {
60
- target,
61
- checked: layout.ideas.length,
62
- ideas: layout.ideas.map(ideaSummary),
63
- },
64
- };
65
- return { config: loaded.config, layout, report };
66
- }
67
-
68
- function targetFailure(root, code, message, remediation) {
69
- return {
70
- command: "check",
71
- ok: false,
72
- root,
73
- diagnostics: [{ code, level: "error", message, remediation }],
74
- result: null,
75
- };
76
- }
77
-
78
- function finishTarget({ commit, inspected, root, target }) {
79
- const report = inspected.report;
80
- report.root = root;
81
- report.result = { ...report.result, target };
82
- if (commit) report.result.commit = commit;
83
- report.ok = report.diagnostics.every(({ level }) => level !== "error");
84
- return report;
85
- }
86
-
87
- export async function checkRepository({
88
- commit,
89
- remote = false,
90
- root = process.cwd(),
91
- staged = false,
92
- worktree = false,
93
- } = {}) {
94
- const repositoryRoot = resolve(root);
95
- const targetCount = [remote, commit !== undefined, staged, worktree].filter(Boolean).length;
96
- if (targetCount > 1) {
97
- return targetFailure(
98
- repositoryRoot,
99
- "check.target.conflict",
100
- "Check targets are mutually exclusive.",
101
- "Choose exactly one of remote, commit, staged, or worktree.",
102
- );
103
- }
104
-
105
- if (remote) {
106
- try {
107
- const head = resolveCommit(repositoryRoot, "HEAD");
108
- const bootstrap = await withTemporaryWorktree(repositoryRoot, head, (worktreeRoot, tree) =>
109
- inspectSnapshot({
110
- gitRoot: repositoryRoot,
111
- root: worktreeRoot,
112
- snapshotTree: tree,
113
- target: "remote",
114
- }),
115
- );
116
- if (!bootstrap.config) {
117
- return finishTarget({ inspected: bootstrap, root: repositoryRoot, target: "remote" });
118
- }
119
- const primary = fetchPrimary(repositoryRoot, bootstrap.config);
120
- const inspected = await withTemporaryWorktree(repositoryRoot, primary, (worktree, tree) =>
121
- inspectSnapshot({
122
- gitRoot: repositoryRoot,
123
- historyCommit: primary,
124
- root: worktree,
125
- snapshotTree: tree,
126
- target: "remote",
127
- }),
128
- );
129
- return finishTarget({
130
- commit: primary,
131
- inspected,
132
- root: repositoryRoot,
133
- target: "remote",
134
- });
135
- } catch (caught) {
136
- return targetFailure(
137
- repositoryRoot,
138
- "git.fetch.failed",
139
- caught.message,
140
- "Check remote access and retry.",
141
- );
142
- }
143
- }
144
-
145
- if (commit !== undefined || (!staged && !worktree)) {
146
- const revision = commit ?? "HEAD";
147
- const target = commit === undefined ? "head" : "commit";
148
- let resolvedCommit;
149
- try {
150
- resolvedCommit = resolveCommit(repositoryRoot, revision);
151
- } catch (caught) {
152
- return targetFailure(
153
- repositoryRoot,
154
- "git.commit.invalid",
155
- caught.message,
156
- commit === undefined
157
- ? "Create or restore a valid HEAD commit."
158
- : "Choose a commit available in the local repository.",
159
- );
160
- }
161
- try {
162
- const parent = runGit(repositoryRoot, ["rev-parse", `${resolvedCommit}^1`]);
163
- const inspected = await withTemporaryWorktree(repositoryRoot, resolvedCommit, (worktree, tree) =>
164
- inspectSnapshot({
165
- baseRevision: parent.ok ? parent.stdout : null,
166
- gitRoot: repositoryRoot,
167
- root: worktree,
168
- snapshotTree: tree,
169
- target,
170
- validateCandidate: true,
171
- }),
172
- );
173
- return finishTarget({
174
- commit: resolvedCommit,
175
- inspected,
176
- root: repositoryRoot,
177
- target,
178
- });
179
- } catch (caught) {
180
- return targetFailure(
181
- repositoryRoot,
182
- "git.snapshot.failed",
183
- caught.message,
184
- "Resolve the repository state and retry the commit check.",
185
- );
186
- }
187
- }
188
-
189
- if (staged) {
190
- try {
191
- const snapshot = indexSnapshot(repositoryRoot);
192
- const inspected = await withTemporaryTree(repositoryRoot, snapshot.tree, (worktree) =>
193
- inspectSnapshot({
194
- baseRevision: "HEAD",
195
- gitRoot: repositoryRoot,
196
- root: worktree,
197
- snapshotTree: snapshot.tree,
198
- target: "staged",
199
- validateCandidate: true,
200
- }),
201
- );
202
- return finishTarget({
203
- inspected,
204
- root: repositoryRoot,
205
- target: "staged",
206
- });
207
- } catch (caught) {
208
- return targetFailure(
209
- repositoryRoot,
210
- "git.snapshot.failed",
211
- caught.message,
212
- "Resolve the index state and retry the staged check.",
213
- );
214
- }
215
- }
216
-
217
- if (worktree) {
218
- try {
219
- const snapshot = worktreeSnapshot(repositoryRoot);
220
- const inspected = await withTemporaryTree(repositoryRoot, snapshot.tree, (worktree) =>
221
- inspectSnapshot({
222
- baseRevision: "HEAD",
223
- gitRoot: repositoryRoot,
224
- root: worktree,
225
- snapshotTree: snapshot.tree,
226
- target: "worktree",
227
- validateCandidate: true,
228
- }),
229
- );
230
- return finishTarget({
231
- inspected,
232
- root: repositoryRoot,
233
- target: "worktree",
234
- });
235
- } catch (caught) {
236
- return targetFailure(
237
- repositoryRoot,
238
- "git.snapshot.failed",
239
- caught.message,
240
- "Resolve the worktree state and retry the worktree check.",
241
- );
242
- }
243
- }
244
-
245
- throw new Error("Unreachable check target");
246
- }
247
-
248
- export {
249
- deriveIdeaState,
250
- isValidUlid,
251
- parseIdeaStatus,
252
- serializeIdeaStatus,
253
- validateIdeaStatus,
254
- } from "./ideas.js";
255
- export { createIdea, generateUlid } from "./create-idea.js";
256
- export {
257
- inspectAdoption,
258
- SILVERMOON_VERSION,
259
- } from "./adoption.js";
260
- export { whatsNext } from "./whatsnext.js";
1
+ import { resolve } from "node:path";
2
+
3
+ import { loadConfig } from "./config.js";
4
+ import {
5
+ fetchPrimary,
6
+ indexSnapshot,
7
+ resolveCommit,
8
+ runGit,
9
+ worktreeSnapshot,
10
+ withTemporaryTree,
11
+ withTemporaryWorktree,
12
+ } from "./git.js";
13
+ import { inspectIdeaLayout } from "./idea-layout.js";
14
+
15
+ function ideaSummary(idea) {
16
+ const value = {
17
+ id: idea.id,
18
+ idealRevision: idea.idealRevision,
19
+ implementationRevision: idea.implementationRevision,
20
+ deploymentRevision: idea.deploymentRevision,
21
+ state: idea.state,
22
+ };
23
+ if (idea.alias !== undefined) value.alias = idea.alias;
24
+ return value;
25
+ }
26
+
27
+ async function inspectSnapshot({
28
+ baseRevision,
29
+ gitRoot = root,
30
+ historyCommit,
31
+ root,
32
+ snapshotTree,
33
+ target = "local",
34
+ validateCandidate = false,
35
+ }) {
36
+ const repositoryRoot = resolve(root);
37
+ const loaded = await loadConfig({ root: repositoryRoot });
38
+ const layout = loaded.config
39
+ ? await inspectIdeaLayout({
40
+ baseRevision,
41
+ config: loaded.config,
42
+ gitRoot,
43
+ historyCommit,
44
+ root: repositoryRoot,
45
+ snapshotTree,
46
+ validateCandidate,
47
+ })
48
+ : { diagnostics: [], ideas: [] };
49
+ const diagnostics = [
50
+ ...loaded.diagnostics,
51
+ ...layout.diagnostics,
52
+ ];
53
+
54
+ const report = {
55
+ command: "check",
56
+ ok: diagnostics.every(({ level }) => level !== "error"),
57
+ root: repositoryRoot,
58
+ diagnostics,
59
+ result: {
60
+ target,
61
+ checked: layout.ideas.length,
62
+ ideas: layout.ideas.map(ideaSummary),
63
+ },
64
+ };
65
+ return { config: loaded.config, layout, report };
66
+ }
67
+
68
+ function targetFailure(root, code, message, remediation) {
69
+ return {
70
+ command: "check",
71
+ ok: false,
72
+ root,
73
+ diagnostics: [{ code, level: "error", message, remediation }],
74
+ result: null,
75
+ };
76
+ }
77
+
78
+ function finishTarget({ commit, inspected, root, target }) {
79
+ const report = inspected.report;
80
+ report.root = root;
81
+ report.result = { ...report.result, target };
82
+ if (commit) report.result.commit = commit;
83
+ report.ok = report.diagnostics.every(({ level }) => level !== "error");
84
+ return report;
85
+ }
86
+
87
+ export async function checkRepository({
88
+ commit,
89
+ remote = false,
90
+ root = process.cwd(),
91
+ staged = false,
92
+ worktree = false,
93
+ } = {}) {
94
+ const repositoryRoot = resolve(root);
95
+ const targetCount = [remote, commit !== undefined, staged, worktree].filter(Boolean).length;
96
+ if (targetCount > 1) {
97
+ return targetFailure(
98
+ repositoryRoot,
99
+ "check.target.conflict",
100
+ "Check targets are mutually exclusive.",
101
+ "Choose exactly one of remote, commit, staged, or worktree.",
102
+ );
103
+ }
104
+
105
+ if (remote) {
106
+ try {
107
+ const head = resolveCommit(repositoryRoot, "HEAD");
108
+ const bootstrap = await withTemporaryWorktree(repositoryRoot, head, (worktreeRoot, tree) =>
109
+ inspectSnapshot({
110
+ gitRoot: repositoryRoot,
111
+ root: worktreeRoot,
112
+ snapshotTree: tree,
113
+ target: "remote",
114
+ }),
115
+ );
116
+ if (!bootstrap.config) {
117
+ return finishTarget({ inspected: bootstrap, root: repositoryRoot, target: "remote" });
118
+ }
119
+ const primary = fetchPrimary(repositoryRoot, bootstrap.config);
120
+ const inspected = await withTemporaryWorktree(repositoryRoot, primary, (worktree, tree) =>
121
+ inspectSnapshot({
122
+ gitRoot: repositoryRoot,
123
+ historyCommit: primary,
124
+ root: worktree,
125
+ snapshotTree: tree,
126
+ target: "remote",
127
+ }),
128
+ );
129
+ return finishTarget({
130
+ commit: primary,
131
+ inspected,
132
+ root: repositoryRoot,
133
+ target: "remote",
134
+ });
135
+ } catch (caught) {
136
+ return targetFailure(
137
+ repositoryRoot,
138
+ "git.fetch.failed",
139
+ caught.message,
140
+ "Check remote access and retry.",
141
+ );
142
+ }
143
+ }
144
+
145
+ if (commit !== undefined || (!staged && !worktree)) {
146
+ const revision = commit ?? "HEAD";
147
+ const target = commit === undefined ? "head" : "commit";
148
+ let resolvedCommit;
149
+ try {
150
+ resolvedCommit = resolveCommit(repositoryRoot, revision);
151
+ } catch (caught) {
152
+ return targetFailure(
153
+ repositoryRoot,
154
+ "git.commit.invalid",
155
+ caught.message,
156
+ commit === undefined
157
+ ? "Create or restore a valid HEAD commit."
158
+ : "Choose a commit available in the local repository.",
159
+ );
160
+ }
161
+ try {
162
+ const parent = runGit(repositoryRoot, ["rev-parse", `${resolvedCommit}^1`]);
163
+ const inspected = await withTemporaryWorktree(repositoryRoot, resolvedCommit, (worktree, tree) =>
164
+ inspectSnapshot({
165
+ baseRevision: parent.ok ? parent.stdout : null,
166
+ gitRoot: repositoryRoot,
167
+ root: worktree,
168
+ snapshotTree: tree,
169
+ target,
170
+ validateCandidate: true,
171
+ }),
172
+ );
173
+ return finishTarget({
174
+ commit: resolvedCommit,
175
+ inspected,
176
+ root: repositoryRoot,
177
+ target,
178
+ });
179
+ } catch (caught) {
180
+ return targetFailure(
181
+ repositoryRoot,
182
+ "git.snapshot.failed",
183
+ caught.message,
184
+ "Resolve the repository state and retry the commit check.",
185
+ );
186
+ }
187
+ }
188
+
189
+ if (staged) {
190
+ try {
191
+ const snapshot = indexSnapshot(repositoryRoot);
192
+ const inspected = await withTemporaryTree(repositoryRoot, snapshot.tree, (worktree) =>
193
+ inspectSnapshot({
194
+ baseRevision: "HEAD",
195
+ gitRoot: repositoryRoot,
196
+ root: worktree,
197
+ snapshotTree: snapshot.tree,
198
+ target: "staged",
199
+ validateCandidate: true,
200
+ }),
201
+ );
202
+ return finishTarget({
203
+ inspected,
204
+ root: repositoryRoot,
205
+ target: "staged",
206
+ });
207
+ } catch (caught) {
208
+ return targetFailure(
209
+ repositoryRoot,
210
+ "git.snapshot.failed",
211
+ caught.message,
212
+ "Resolve the index state and retry the staged check.",
213
+ );
214
+ }
215
+ }
216
+
217
+ if (worktree) {
218
+ try {
219
+ const snapshot = worktreeSnapshot(repositoryRoot);
220
+ const inspected = await withTemporaryTree(repositoryRoot, snapshot.tree, (worktree) =>
221
+ inspectSnapshot({
222
+ baseRevision: "HEAD",
223
+ gitRoot: repositoryRoot,
224
+ root: worktree,
225
+ snapshotTree: snapshot.tree,
226
+ target: "worktree",
227
+ validateCandidate: true,
228
+ }),
229
+ );
230
+ return finishTarget({
231
+ inspected,
232
+ root: repositoryRoot,
233
+ target: "worktree",
234
+ });
235
+ } catch (caught) {
236
+ return targetFailure(
237
+ repositoryRoot,
238
+ "git.snapshot.failed",
239
+ caught.message,
240
+ "Resolve the worktree state and retry the worktree check.",
241
+ );
242
+ }
243
+ }
244
+
245
+ throw new Error("Unreachable check target");
246
+ }
247
+
248
+ export {
249
+ deriveIdeaState,
250
+ isValidUlid,
251
+ parseIdeaStatus,
252
+ serializeIdeaStatus,
253
+ validateIdeaStatus,
254
+ } from "./ideas.js";
255
+ export { createIdea, generateUlid } from "./create-idea.js";
256
+ export {
257
+ inspectAdoption,
258
+ SILVERMOON_VERSION,
259
+ } from "./adoption.js";
260
+ export { whatsNext } from "./whatsnext.js";
@@ -0,0 +1,29 @@
1
+ export const DEFAULT_LANGUAGE = "en-US";
2
+
3
+ export function canonicalizeLanguageTag(value) {
4
+ if (typeof value !== "string" || value.length === 0 || value.trim() !== value) {
5
+ throw new Error("language tag must be a non-empty trimmed string");
6
+ }
7
+ try {
8
+ const [canonical] = Intl.getCanonicalLocales(value);
9
+ if (!canonical) throw new Error("language tag is empty");
10
+ return canonical;
11
+ } catch {
12
+ throw new Error(`invalid BCP 47 language tag: ${value}`);
13
+ }
14
+ }
15
+
16
+ export function isCanonicalLanguageTag(value) {
17
+ try {
18
+ return canonicalizeLanguageTag(value) === value;
19
+ } catch {
20
+ return false;
21
+ }
22
+ }
23
+
24
+ export function resolveLanguage({ idea, project, global } = {}) {
25
+ if (idea !== undefined) return { tag: idea, source: "idea" };
26
+ if (project !== undefined) return { tag: project, source: "project" };
27
+ if (global !== undefined) return { tag: global, source: "global" };
28
+ return { tag: DEFAULT_LANGUAGE, source: "default" };
29
+ }
package/src/layout.js CHANGED
@@ -1,21 +1,21 @@
1
- export const METADATA_ROOT = ".silvermoon";
2
- export const CONFIG_PATH = `${METADATA_ROOT}/config.yaml`;
3
- export const IDEAS_ROOT = `${METADATA_ROOT}/ideas`;
4
-
5
- export function ideaPaths(id) {
6
- const ideaPath = `${IDEAS_ROOT}/${id}`;
7
- const outerPath = `${ideaPath}/outer`;
8
- const innerPath = `${outerPath}/inner`;
9
- const idealPath = `${innerPath}/ideal`;
10
- return {
11
- ideaPath,
12
- statusPath: `${ideaPath}/status.yaml`,
13
- ledgerPath: `${ideaPath}/ledger.md`,
14
- outerPath,
15
- deploymentDocumentPath: `${outerPath}/Deployment.md`,
16
- innerPath,
17
- implementationDocumentPath: `${innerPath}/Implementation.md`,
18
- idealPath,
19
- ideaDocumentPath: `${idealPath}/Idea.md`,
20
- };
21
- }
1
+ export const METADATA_ROOT = ".silvermoon";
2
+ export const CONFIG_PATH = `${METADATA_ROOT}/config.yaml`;
3
+ export const IDEAS_ROOT = `${METADATA_ROOT}/ideas`;
4
+
5
+ export function ideaPaths(id) {
6
+ const ideaPath = `${IDEAS_ROOT}/${id}`;
7
+ const outerPath = `${ideaPath}/outer`;
8
+ const innerPath = `${outerPath}/inner`;
9
+ const idealPath = `${innerPath}/ideal`;
10
+ return {
11
+ ideaPath,
12
+ statusPath: `${ideaPath}/status.yaml`,
13
+ ledgerPath: `${ideaPath}/ledger.md`,
14
+ outerPath,
15
+ deploymentDocumentPath: `${outerPath}/Deployment.md`,
16
+ innerPath,
17
+ implementationDocumentPath: `${innerPath}/Implementation.md`,
18
+ idealPath,
19
+ ideaDocumentPath: `${idealPath}/Idea.md`,
20
+ };
21
+ }