stego-cli 0.4.1 → 0.4.2

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.
Files changed (126) hide show
  1. package/README.md +6 -0
  2. package/dist/shared/src/contracts/cli/envelopes.js +19 -0
  3. package/dist/shared/src/contracts/cli/errors.js +14 -0
  4. package/dist/shared/src/contracts/cli/exit-codes.js +15 -0
  5. package/dist/shared/src/contracts/cli/index.js +6 -0
  6. package/dist/shared/src/contracts/cli/metadata.js +1 -0
  7. package/dist/shared/src/contracts/cli/operations.js +1 -0
  8. package/dist/shared/src/domain/comments/anchors.js +1 -0
  9. package/dist/shared/src/domain/comments/index.js +4 -0
  10. package/dist/shared/src/domain/comments/serializer.js +1 -0
  11. package/dist/shared/src/domain/comments/thread-key.js +21 -0
  12. package/dist/shared/src/domain/frontmatter/index.js +3 -0
  13. package/dist/shared/src/domain/frontmatter/parser.js +34 -0
  14. package/dist/shared/src/domain/frontmatter/serializer.js +32 -0
  15. package/dist/shared/src/domain/frontmatter/validators.js +47 -0
  16. package/dist/shared/src/domain/project/index.js +4 -0
  17. package/dist/shared/src/domain/stages/index.js +20 -0
  18. package/dist/shared/src/index.js +6 -0
  19. package/dist/shared/src/utils/guards.js +6 -0
  20. package/dist/shared/src/utils/index.js +3 -0
  21. package/dist/shared/src/utils/invariant.js +5 -0
  22. package/dist/shared/src/utils/result.js +6 -0
  23. package/dist/stego-cli/src/app/cli-version.js +32 -0
  24. package/dist/stego-cli/src/app/command-context.js +1 -0
  25. package/dist/stego-cli/src/app/command-registry.js +121 -0
  26. package/dist/stego-cli/src/app/create-cli-app.js +42 -0
  27. package/dist/stego-cli/src/app/error-boundary.js +42 -0
  28. package/dist/stego-cli/src/app/index.js +6 -0
  29. package/dist/stego-cli/src/app/output-renderer.js +6 -0
  30. package/dist/stego-cli/src/main.js +14 -0
  31. package/dist/stego-cli/src/modules/comments/application/comment-operations.js +457 -0
  32. package/dist/stego-cli/src/modules/comments/commands/comments-add.js +40 -0
  33. package/dist/stego-cli/src/modules/comments/commands/comments-clear-resolved.js +32 -0
  34. package/dist/stego-cli/src/modules/comments/commands/comments-delete.js +33 -0
  35. package/dist/stego-cli/src/modules/comments/commands/comments-read.js +32 -0
  36. package/dist/stego-cli/src/modules/comments/commands/comments-reply.js +36 -0
  37. package/dist/stego-cli/src/modules/comments/commands/comments-set-status.js +35 -0
  38. package/dist/stego-cli/src/modules/comments/commands/comments-sync-anchors.js +33 -0
  39. package/dist/stego-cli/src/modules/comments/domain/comment-policy.js +14 -0
  40. package/dist/stego-cli/src/modules/comments/index.js +20 -0
  41. package/dist/stego-cli/src/modules/comments/infra/comments-repo.js +68 -0
  42. package/dist/stego-cli/src/modules/comments/types.js +1 -0
  43. package/dist/stego-cli/src/modules/compile/application/compile-manuscript.js +16 -0
  44. package/dist/stego-cli/src/modules/compile/commands/build.js +51 -0
  45. package/dist/stego-cli/src/modules/compile/domain/compile-structure.js +105 -0
  46. package/dist/stego-cli/src/modules/compile/index.js +8 -0
  47. package/dist/stego-cli/src/modules/compile/infra/dist-writer.js +8 -0
  48. package/dist/stego-cli/src/modules/compile/types.js +1 -0
  49. package/dist/stego-cli/src/modules/export/application/run-export.js +29 -0
  50. package/dist/stego-cli/src/modules/export/commands/export.js +61 -0
  51. package/dist/stego-cli/src/modules/export/domain/exporter.js +6 -0
  52. package/dist/stego-cli/src/modules/export/index.js +8 -0
  53. package/dist/stego-cli/src/modules/export/types.js +1 -0
  54. package/dist/stego-cli/src/modules/index.js +22 -0
  55. package/dist/stego-cli/src/modules/manuscript/application/create-manuscript.js +107 -0
  56. package/dist/stego-cli/src/modules/manuscript/application/order-inference.js +56 -0
  57. package/dist/stego-cli/src/modules/manuscript/commands/new-manuscript.js +54 -0
  58. package/dist/stego-cli/src/modules/manuscript/domain/manuscript.js +1 -0
  59. package/dist/stego-cli/src/modules/manuscript/index.js +9 -0
  60. package/dist/stego-cli/src/modules/manuscript/infra/manuscript-repo.js +39 -0
  61. package/dist/stego-cli/src/modules/manuscript/types.js +1 -0
  62. package/dist/stego-cli/src/modules/metadata/application/apply-metadata.js +45 -0
  63. package/dist/stego-cli/src/modules/metadata/application/read-metadata.js +18 -0
  64. package/dist/stego-cli/src/modules/metadata/commands/metadata-apply.js +38 -0
  65. package/dist/stego-cli/src/modules/metadata/commands/metadata-read.js +33 -0
  66. package/dist/stego-cli/src/modules/metadata/domain/metadata.js +1 -0
  67. package/dist/stego-cli/src/modules/metadata/index.js +11 -0
  68. package/dist/stego-cli/src/modules/metadata/infra/metadata-repo.js +68 -0
  69. package/dist/stego-cli/src/modules/metadata/types.js +1 -0
  70. package/dist/stego-cli/src/modules/project/application/create-project.js +203 -0
  71. package/dist/stego-cli/src/modules/project/application/infer-project.js +72 -0
  72. package/dist/stego-cli/src/modules/project/commands/new-project.js +86 -0
  73. package/dist/stego-cli/src/modules/project/domain/project.js +1 -0
  74. package/dist/stego-cli/src/modules/project/index.js +9 -0
  75. package/dist/stego-cli/src/modules/project/infra/project-repo.js +27 -0
  76. package/dist/stego-cli/src/modules/project/types.js +1 -0
  77. package/dist/stego-cli/src/modules/quality/application/inspect-project.js +603 -0
  78. package/dist/stego-cli/src/modules/quality/application/lint-runner.js +313 -0
  79. package/dist/stego-cli/src/modules/quality/application/stage-check.js +87 -0
  80. package/dist/stego-cli/src/modules/quality/commands/check-stage.js +54 -0
  81. package/dist/stego-cli/src/modules/quality/commands/lint.js +51 -0
  82. package/dist/stego-cli/src/modules/quality/commands/validate.js +50 -0
  83. package/dist/stego-cli/src/modules/quality/domain/issues.js +1 -0
  84. package/dist/stego-cli/src/modules/quality/domain/policies.js +1 -0
  85. package/dist/stego-cli/src/modules/quality/index.js +14 -0
  86. package/dist/stego-cli/src/modules/quality/infra/cspell-adapter.js +1 -0
  87. package/dist/stego-cli/src/modules/quality/infra/markdownlint-adapter.js +1 -0
  88. package/dist/stego-cli/src/modules/quality/types.js +1 -0
  89. package/dist/stego-cli/src/modules/scaffold/application/scaffold-workspace.js +307 -0
  90. package/dist/stego-cli/src/modules/scaffold/commands/init.js +34 -0
  91. package/dist/stego-cli/src/modules/scaffold/domain/templates.js +182 -0
  92. package/dist/stego-cli/src/modules/scaffold/index.js +8 -0
  93. package/dist/stego-cli/src/modules/scaffold/infra/template-repo.js +33 -0
  94. package/dist/stego-cli/src/modules/scaffold/types.js +1 -0
  95. package/dist/stego-cli/src/modules/spine/application/create-category.js +14 -0
  96. package/dist/stego-cli/src/modules/spine/application/create-entry.js +9 -0
  97. package/dist/stego-cli/src/modules/spine/application/read-catalog.js +13 -0
  98. package/dist/stego-cli/src/modules/spine/commands/spine-deprecated-aliases.js +33 -0
  99. package/dist/stego-cli/src/modules/spine/commands/spine-new-category.js +64 -0
  100. package/dist/stego-cli/src/modules/spine/commands/spine-new-entry.js +65 -0
  101. package/dist/stego-cli/src/modules/spine/commands/spine-read.js +49 -0
  102. package/dist/{spine/spine-domain.js → stego-cli/src/modules/spine/domain/spine.js} +13 -7
  103. package/dist/stego-cli/src/modules/spine/index.js +16 -0
  104. package/dist/stego-cli/src/modules/spine/infra/spine-repo.js +46 -0
  105. package/dist/stego-cli/src/modules/spine/types.js +1 -0
  106. package/dist/stego-cli/src/modules/workspace/application/discover-projects.js +18 -0
  107. package/dist/stego-cli/src/modules/workspace/application/resolve-workspace.js +73 -0
  108. package/dist/stego-cli/src/modules/workspace/commands/list-projects.js +40 -0
  109. package/dist/stego-cli/src/modules/workspace/index.js +9 -0
  110. package/dist/stego-cli/src/modules/workspace/infra/workspace-repo.js +37 -0
  111. package/dist/stego-cli/src/modules/workspace/types.js +1 -0
  112. package/dist/stego-cli/src/platform/clock.js +3 -0
  113. package/dist/stego-cli/src/platform/fs.js +13 -0
  114. package/dist/stego-cli/src/platform/index.js +3 -0
  115. package/dist/stego-cli/src/platform/temp-files.js +6 -0
  116. package/package.json +20 -11
  117. package/dist/comments/comments-command.js +0 -499
  118. package/dist/comments/errors.js +0 -20
  119. package/dist/metadata/metadata-command.js +0 -127
  120. package/dist/metadata/metadata-domain.js +0 -209
  121. package/dist/spine/spine-command.js +0 -129
  122. package/dist/stego-cli.js +0 -2264
  123. /package/dist/{exporters/exporter-types.js → shared/src/contracts/cli/comments.js} +0 -0
  124. /package/dist/{comments/comment-domain.js → shared/src/domain/comments/parser.js} +0 -0
  125. /package/dist/{exporters → stego-cli/src/modules/export/infra}/markdown-exporter.js +0 -0
  126. /package/dist/{exporters → stego-cli/src/modules/export/infra}/pandoc-exporter.js +0 -0
@@ -0,0 +1,307 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+ import process from "node:process";
4
+ import { spawnSync } from "node:child_process";
5
+ import { createInterface } from "node:readline/promises";
6
+ import { fileURLToPath } from "node:url";
7
+ import { CliError } from "../../../../../shared/src/contracts/cli/index.js";
8
+ import { ROOT_CONFIG_FILENAME } from "../../workspace/index.js";
9
+ import { COMMENT_AUTHOR_PROMPT, PROJECT_EXTENSION_RECOMMENDATIONS, PROSE_FONT_PROMPT, PROSE_MARKDOWN_EDITOR_SETTINGS, SCAFFOLD_AGENTS_CONTENT, SCAFFOLD_GITIGNORE_CONTENT, SCAFFOLD_README_CONTENT } from "../domain/templates.js";
10
+ import { copyDirectory, copyFile, ensureDirectory, listDirectoryEntries, pathExists, readTextFile, statPath, writeTextFile } from "../infra/template-repo.js";
11
+ export async function scaffoldWorkspace(input) {
12
+ const targetRoot = path.resolve(input.cwd);
13
+ const entries = listDirectoryEntries(targetRoot)
14
+ .filter((entry) => entry.name !== "." && entry.name !== "..");
15
+ if (entries.length > 0 && !input.force) {
16
+ throw new Error(`Target directory is not empty: ${targetRoot}. Re-run with --force to continue.`);
17
+ }
18
+ const packageRoot = resolvePackageRoot();
19
+ const copiedPaths = [];
20
+ writeScaffoldGitignore(targetRoot, copiedPaths);
21
+ writeScaffoldReadme(targetRoot, copiedPaths);
22
+ writeScaffoldAgents(targetRoot, copiedPaths);
23
+ copyTemplateAsset(packageRoot, ".markdownlint.json", targetRoot, copiedPaths);
24
+ copyTemplateAsset(packageRoot, ".markdownlint.manuscript.json", targetRoot, copiedPaths);
25
+ copyTemplateAsset(packageRoot, ".cspell.json", targetRoot, copiedPaths);
26
+ copyTemplateAsset(packageRoot, ROOT_CONFIG_FILENAME, targetRoot, copiedPaths);
27
+ copyTemplateAsset(packageRoot, "projects", targetRoot, copiedPaths);
28
+ copyTemplateAsset(packageRoot, path.join(".vscode", "tasks.json"), targetRoot, copiedPaths);
29
+ copyTemplateAsset(packageRoot, path.join(".vscode", "extensions.json"), targetRoot, copiedPaths, { optional: true });
30
+ rewriteTemplateProjectPackageScripts(targetRoot);
31
+ const enableProseFont = await promptYesNo(PROSE_FONT_PROMPT, true);
32
+ const suggestedCommentAuthor = resolveSuggestedCommentAuthor(targetRoot);
33
+ const commentAuthor = (await promptText(COMMENT_AUTHOR_PROMPT, suggestedCommentAuthor)).trim();
34
+ if (enableProseFont || commentAuthor) {
35
+ writeProjectProseEditorSettings(targetRoot, copiedPaths, {
36
+ enableProseFont,
37
+ commentAuthor
38
+ });
39
+ }
40
+ writeInitRootPackageJson(targetRoot, packageRoot);
41
+ return {
42
+ targetRoot,
43
+ copiedPaths
44
+ };
45
+ }
46
+ function resolvePackageRoot() {
47
+ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
48
+ const candidates = [
49
+ path.resolve(scriptDir, "../../../../"),
50
+ path.resolve(scriptDir, "../../../../../../")
51
+ ];
52
+ for (const candidate of candidates) {
53
+ const configPath = path.join(candidate, ROOT_CONFIG_FILENAME);
54
+ const projectsPath = path.join(candidate, "projects");
55
+ if (pathExists(configPath) && pathExists(projectsPath)) {
56
+ return candidate;
57
+ }
58
+ }
59
+ throw new CliError("INTERNAL_ERROR", "Unable to resolve stego-cli package root for scaffolding.");
60
+ }
61
+ async function promptYesNo(question, defaultYes) {
62
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
63
+ return defaultYes;
64
+ }
65
+ const rl = createInterface({
66
+ input: process.stdin,
67
+ output: process.stdout
68
+ });
69
+ const suffix = defaultYes ? " [Y/n] " : " [y/N] ";
70
+ try {
71
+ while (true) {
72
+ const answer = (await rl.question(`${question}${suffix}`)).trim().toLowerCase();
73
+ if (!answer) {
74
+ return defaultYes;
75
+ }
76
+ if (answer === "y" || answer === "yes") {
77
+ return true;
78
+ }
79
+ if (answer === "n" || answer === "no") {
80
+ return false;
81
+ }
82
+ process.stdout.write("Please answer y or n.\n");
83
+ }
84
+ }
85
+ finally {
86
+ rl.close();
87
+ }
88
+ }
89
+ async function promptText(question, defaultValue = "") {
90
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
91
+ return defaultValue;
92
+ }
93
+ const rl = createInterface({
94
+ input: process.stdin,
95
+ output: process.stdout
96
+ });
97
+ const suffix = defaultValue ? ` [${defaultValue}] ` : " ";
98
+ try {
99
+ const answer = (await rl.question(`${question}${suffix}`)).trim();
100
+ return answer || defaultValue;
101
+ }
102
+ finally {
103
+ rl.close();
104
+ }
105
+ }
106
+ function resolveSuggestedCommentAuthor(cwd) {
107
+ const gitAuthor = spawnSync("git", ["config", "--get", "user.name"], {
108
+ cwd,
109
+ encoding: "utf8"
110
+ });
111
+ const fromGit = (gitAuthor.stdout || "").trim();
112
+ if (gitAuthor.status === 0 && fromGit) {
113
+ return fromGit;
114
+ }
115
+ try {
116
+ const username = os.userInfo().username.trim();
117
+ if (username) {
118
+ return username;
119
+ }
120
+ }
121
+ catch {
122
+ // ignore and fall back to empty string
123
+ }
124
+ return "";
125
+ }
126
+ function copyTemplateAsset(packageRoot, sourceRelativePath, targetRoot, copiedPaths, options) {
127
+ const sourcePath = path.join(packageRoot, sourceRelativePath);
128
+ if (!pathExists(sourcePath)) {
129
+ if (options?.optional) {
130
+ return;
131
+ }
132
+ throw new Error(`Template asset is missing from stego-cli package: ${sourceRelativePath}`);
133
+ }
134
+ const destinationPath = path.join(targetRoot, sourceRelativePath);
135
+ const stats = statPath(sourcePath);
136
+ if (stats.isDirectory()) {
137
+ copyDirectory(sourcePath, destinationPath, (currentSourcePath) => shouldCopyTemplatePath(packageRoot, currentSourcePath));
138
+ }
139
+ else {
140
+ copyFile(sourcePath, destinationPath);
141
+ }
142
+ copiedPaths.push(sourceRelativePath);
143
+ }
144
+ function writeScaffoldGitignore(targetRoot, copiedPaths) {
145
+ writeTextFile(path.join(targetRoot, ".gitignore"), SCAFFOLD_GITIGNORE_CONTENT);
146
+ copiedPaths.push(".gitignore");
147
+ }
148
+ function writeScaffoldReadme(targetRoot, copiedPaths) {
149
+ writeTextFile(path.join(targetRoot, "README.md"), SCAFFOLD_README_CONTENT);
150
+ copiedPaths.push("README.md");
151
+ }
152
+ function writeScaffoldAgents(targetRoot, copiedPaths) {
153
+ writeTextFile(path.join(targetRoot, "AGENTS.md"), SCAFFOLD_AGENTS_CONTENT);
154
+ copiedPaths.push("AGENTS.md");
155
+ }
156
+ function shouldCopyTemplatePath(packageRoot, currentSourcePath) {
157
+ const relativePath = path.relative(packageRoot, currentSourcePath);
158
+ if (!relativePath || relativePath.startsWith("..")) {
159
+ return true;
160
+ }
161
+ const parts = relativePath.split(path.sep);
162
+ const name = parts[parts.length - 1] || "";
163
+ if (name === ".DS_Store") {
164
+ return false;
165
+ }
166
+ if (parts[0] === "projects") {
167
+ if (parts[parts.length - 2] === ".vscode" && name === "settings.json") {
168
+ return false;
169
+ }
170
+ const distIndex = parts.indexOf("dist");
171
+ if (distIndex >= 0) {
172
+ const isDistRoot = distIndex === parts.length - 1;
173
+ const isGitkeep = name === ".gitkeep";
174
+ return isDistRoot || isGitkeep;
175
+ }
176
+ }
177
+ return true;
178
+ }
179
+ function rewriteTemplateProjectPackageScripts(targetRoot) {
180
+ const projectsRoot = path.join(targetRoot, "projects");
181
+ if (!pathExists(projectsRoot)) {
182
+ return;
183
+ }
184
+ for (const entry of listDirectoryEntries(projectsRoot)) {
185
+ if (!entry.isDirectory()) {
186
+ continue;
187
+ }
188
+ const projectRoot = path.join(projectsRoot, entry.name);
189
+ const packageJsonPath = path.join(projectRoot, "package.json");
190
+ if (!pathExists(packageJsonPath)) {
191
+ continue;
192
+ }
193
+ const parsed = tryReadJsonObject(packageJsonPath) ?? {};
194
+ const scripts = isPlainObject(parsed.scripts) ? { ...parsed.scripts } : {};
195
+ scripts.validate = "npx --no-install stego validate";
196
+ scripts.build = "npx --no-install stego build";
197
+ scripts["check-stage"] = "npx --no-install stego check-stage";
198
+ scripts.export = "npx --no-install stego export";
199
+ scripts.new = "npx --no-install stego new";
200
+ writeTextFile(packageJsonPath, `${JSON.stringify({ ...parsed, scripts }, null, 2)}\n`);
201
+ ensureProjectExtensionsRecommendations(projectRoot);
202
+ }
203
+ }
204
+ function ensureProjectExtensionsRecommendations(projectRoot) {
205
+ const vscodeDir = path.join(projectRoot, ".vscode");
206
+ const extensionsPath = path.join(vscodeDir, "extensions.json");
207
+ ensureDirectory(vscodeDir);
208
+ let existingRecommendations = [];
209
+ const parsed = tryReadJsonObject(extensionsPath);
210
+ if (parsed && Array.isArray(parsed.recommendations)) {
211
+ existingRecommendations = parsed.recommendations.filter((value) => typeof value === "string");
212
+ }
213
+ const mergedRecommendations = [
214
+ ...new Set([...PROJECT_EXTENSION_RECOMMENDATIONS, ...existingRecommendations])
215
+ ];
216
+ writeTextFile(extensionsPath, `${JSON.stringify({ recommendations: mergedRecommendations }, null, 2)}\n`);
217
+ }
218
+ function writeProjectProseEditorSettings(targetRoot, copiedPaths, options) {
219
+ const projectsRoot = path.join(targetRoot, "projects");
220
+ if (!pathExists(projectsRoot)) {
221
+ return;
222
+ }
223
+ for (const entry of listDirectoryEntries(projectsRoot)) {
224
+ if (!entry.isDirectory()) {
225
+ continue;
226
+ }
227
+ const projectRoot = path.join(projectsRoot, entry.name);
228
+ const settingsPath = writeProseEditorSettingsForProject(projectRoot, options);
229
+ copiedPaths.push(path.relative(targetRoot, settingsPath));
230
+ }
231
+ }
232
+ function writeProseEditorSettingsForProject(projectRoot, options) {
233
+ const vscodeDir = path.join(projectRoot, ".vscode");
234
+ const settingsPath = path.join(vscodeDir, "settings.json");
235
+ ensureDirectory(vscodeDir);
236
+ const enableProseFont = options?.enableProseFont ?? true;
237
+ const commentAuthor = (options?.commentAuthor ?? "").trim();
238
+ const existingSettings = tryReadJsonObject(settingsPath) ?? {};
239
+ const proseMarkdownSettings = isPlainObject(PROSE_MARKDOWN_EDITOR_SETTINGS["[markdown]"])
240
+ ? PROSE_MARKDOWN_EDITOR_SETTINGS["[markdown]"]
241
+ : {};
242
+ const nextSettings = {
243
+ ...existingSettings
244
+ };
245
+ if (enableProseFont) {
246
+ const existingMarkdownSettings = isPlainObject(existingSettings["[markdown]"])
247
+ ? existingSettings["[markdown]"]
248
+ : {};
249
+ nextSettings["[markdown]"] = {
250
+ ...existingMarkdownSettings,
251
+ ...proseMarkdownSettings
252
+ };
253
+ nextSettings["markdown.preview.fontFamily"] = PROSE_MARKDOWN_EDITOR_SETTINGS["markdown.preview.fontFamily"];
254
+ }
255
+ if (commentAuthor) {
256
+ nextSettings["stego.comments.author"] = commentAuthor;
257
+ }
258
+ writeTextFile(settingsPath, `${JSON.stringify(nextSettings, null, 2)}\n`);
259
+ return settingsPath;
260
+ }
261
+ function writeInitRootPackageJson(targetRoot, packageRoot) {
262
+ const cliPackagePath = path.join(packageRoot, "package.json");
263
+ const cliPackage = tryReadJsonObject(cliPackagePath) ?? {};
264
+ const cliVersion = typeof cliPackage.version === "string" ? cliPackage.version : "0.1.0";
265
+ const manifest = {
266
+ name: path.basename(targetRoot) || "stego-workspace",
267
+ private: true,
268
+ type: "module",
269
+ description: "Stego writing workspace",
270
+ engines: {
271
+ node: ">=20"
272
+ },
273
+ scripts: {
274
+ "list-projects": "stego list-projects",
275
+ "new-project": "stego new-project",
276
+ new: "stego new",
277
+ spine: "stego spine",
278
+ metadata: "stego metadata",
279
+ lint: "stego lint",
280
+ validate: "stego validate",
281
+ build: "stego build",
282
+ "check-stage": "stego check-stage",
283
+ export: "stego export"
284
+ },
285
+ devDependencies: {
286
+ "stego-cli": `^${cliVersion}`,
287
+ cspell: "^9.6.4",
288
+ "markdownlint-cli": "^0.47.0"
289
+ }
290
+ };
291
+ writeTextFile(path.join(targetRoot, "package.json"), `${JSON.stringify(manifest, null, 2)}\n`);
292
+ }
293
+ function tryReadJsonObject(filePath) {
294
+ if (!pathExists(filePath)) {
295
+ return null;
296
+ }
297
+ try {
298
+ const parsed = JSON.parse(readTextFile(filePath));
299
+ return isPlainObject(parsed) ? parsed : null;
300
+ }
301
+ catch {
302
+ return null;
303
+ }
304
+ }
305
+ function isPlainObject(value) {
306
+ return typeof value === "object" && value !== null && !Array.isArray(value);
307
+ }
@@ -0,0 +1,34 @@
1
+ import { writeText } from "../../../app/output-renderer.js";
2
+ import { scaffoldWorkspace } from "../application/scaffold-workspace.js";
3
+ export function registerInitCommand(registry) {
4
+ registry.register({
5
+ name: "init",
6
+ description: "Initialize a new Stego workspace",
7
+ options: [
8
+ {
9
+ flags: "--force",
10
+ description: "Allow init in non-empty directory"
11
+ }
12
+ ],
13
+ action: async (context) => {
14
+ const result = await scaffoldWorkspace({
15
+ cwd: context.cwd,
16
+ force: readBooleanOption(context.options, "force")
17
+ });
18
+ writeText(`Initialized Stego workspace in ${result.targetRoot}`);
19
+ for (const relativePath of result.copiedPaths) {
20
+ writeText(`- ${relativePath}`);
21
+ }
22
+ writeText("- package.json");
23
+ writeText("");
24
+ writeText("Next steps:");
25
+ writeText(" npm install");
26
+ writeText(" stego list-projects");
27
+ writeText(" stego validate --project fiction-example");
28
+ writeText(" stego build --project fiction-example");
29
+ }
30
+ });
31
+ }
32
+ function readBooleanOption(options, key) {
33
+ return options[key] === true;
34
+ }
@@ -0,0 +1,182 @@
1
+ export const PROSE_FONT_PROMPT = "Switch workspace to proportional (prose-style) font? (recommended)";
2
+ export const COMMENT_AUTHOR_PROMPT = "Default comment author for stego.comments.author?";
3
+ export const SCAFFOLD_GITIGNORE_CONTENT = `node_modules/
4
+ /dist/
5
+ .DS_Store
6
+ *.log
7
+ projects/*/dist/*
8
+ !projects/*/dist/.gitkeep
9
+ projects/*/.vscode/settings.json
10
+ .vscode/settings.json
11
+ `;
12
+ export const SCAFFOLD_README_CONTENT = `# Stego Workspace
13
+
14
+ This directory is a Stego writing workspace (a monorepo for one or more writing projects).
15
+
16
+ ## What was scaffolded
17
+
18
+ - \`stego.config.json\` workspace configuration
19
+ - \`projects/\` demo projects (\`stego-docs\` and \`fiction-example\`)
20
+ - root \`package.json\` scripts for Stego commands
21
+ - root \`.vscode/tasks.json\` tasks for common workflows
22
+
23
+ Full documentation lives in \`projects/stego-docs\`.
24
+
25
+ ## First run
26
+
27
+ \`\`\`bash
28
+ npm install
29
+ stego list-projects
30
+ \`\`\`
31
+
32
+ ## Run commands for a specific project (from workspace root)
33
+
34
+ \`\`\`bash
35
+ stego validate --project fiction-example
36
+ stego build --project fiction-example
37
+ stego check-stage --project fiction-example --stage revise
38
+ stego export --project fiction-example --format md
39
+ stego new --project fiction-example
40
+ \`\`\`
41
+
42
+ ## Work inside one project
43
+
44
+ Each project also has local scripts, so you can run commands from inside a project directory:
45
+
46
+ \`\`\`bash
47
+ cd projects/fiction-example
48
+ npm run validate
49
+ npm run build
50
+ \`\`\`
51
+
52
+ ## VS Code recommendation
53
+
54
+ When you are actively working on one project, open that project directory directly in VS Code (for example \`projects/fiction-example\`).
55
+
56
+ This keeps your editor context focused and applies the project's recommended extensions (including Stego + Saurus) for that project.
57
+
58
+ ## Create a new project
59
+
60
+ \`\`\`bash
61
+ stego new-project --project my-book --title "My Book"
62
+ \`\`\`
63
+
64
+ ## Add a new manuscript file
65
+
66
+ \`\`\`bash
67
+ stego new --project fiction-example
68
+ \`\`\`
69
+ `;
70
+ export const SCAFFOLD_AGENTS_CONTENT = `# AGENTS.md
71
+
72
+ ## Purpose
73
+
74
+ This workspace is designed to be AI-friendly for writing workflows.
75
+
76
+ ## Canonical CLI Interface
77
+
78
+ - Run \`stego --help\` for the full command reference.
79
+ - Run \`stego --version\` to confirm which CLI is active.
80
+ - Run project docs commands in \`projects/stego-docs\` when available.
81
+
82
+ ## CLI Resolution Rules
83
+
84
+ - Prefer local CLI over global CLI:
85
+ - \`npm exec -- stego ...\`
86
+ - \`npx --no-install stego ...\`
87
+ - At the start of mutation tasks, run \`stego --version\` and report the version used.
88
+
89
+ ## Workspace Discovery Checklist
90
+
91
+ 1. Confirm workspace root contains \`stego.config.json\`.
92
+ 2. Run \`stego list-projects\`.
93
+ 3. Use explicit \`--project <id>\` for project-scoped commands.
94
+
95
+ ## CLI-First Policy (Required)
96
+
97
+ When asked to edit Stego project content, use documented Stego CLI commands first.
98
+
99
+ Typical targets:
100
+
101
+ - manuscript files
102
+ - spine categories and entries
103
+ - frontmatter metadata
104
+ - comments
105
+ - stage/build/export workflows
106
+
107
+ Preferred commands include:
108
+
109
+ - \`stego new\`
110
+ - \`stego spine read\`
111
+ - \`stego spine new-category\`
112
+ - \`stego spine new\`
113
+ - \`stego metadata read\`
114
+ - \`stego metadata apply\`
115
+ - \`stego comments ...\`
116
+
117
+ ## Machine-Mode Output
118
+
119
+ - For automation and integrations, prefer \`--format json\` and parse structured output.
120
+ - Use text output only for human-facing summaries.
121
+
122
+ ## Mutation Protocol
123
+
124
+ 1. Read current state first (\`metadata read\`, \`spine read\`, \`comments read\`).
125
+ 2. Mutate via CLI commands.
126
+ 3. Verify after writes (\`stego validate --project <id>\` and relevant read commands).
127
+
128
+ ## Manual Edit Fallback
129
+
130
+ Manual file edits are a last resort.
131
+
132
+ If manual edits are required, the agent must:
133
+
134
+ 1. warn that CLI was bypassed,
135
+ 2. explain why CLI could not be used, and
136
+ 3. list which files were manually edited.
137
+
138
+ ## Failure Contract
139
+
140
+ When CLI fails:
141
+
142
+ 1. show the attempted command,
143
+ 2. summarize the error briefly,
144
+ 3. report the recovery attempt, and
145
+ 4. if fallback is required, apply the Manual Edit Fallback policy.
146
+
147
+ ## Validation Expectations
148
+
149
+ After mutations, run relevant checks when feasible (for example \`stego validate --project <id>\`) and report results.
150
+
151
+ ## Scope Guardrails
152
+
153
+ - Do not manually edit \`dist/\` outputs or compiled export artifacts.
154
+ - Do not modify files outside the requested project scope unless the user explicitly asks.
155
+
156
+ ## Task To Command Quick Map
157
+
158
+ - New manuscript: \`stego new --project <id> [--filename <name>]\`
159
+ - Read spine: \`stego spine read --project <id> --format json\`
160
+ - New spine category: \`stego spine new-category --project <id> --key <category>\`
161
+ - New spine entry: \`stego spine new --project <id> --category <category> [--filename <path>]\`
162
+ - Read metadata: \`stego metadata read <markdown-path> --format json\`
163
+ - Apply metadata: \`stego metadata apply <markdown-path> --input <path|-> --format json\`
164
+ - Read comments: \`stego comments read <manuscript> --format json\`
165
+ - Mutate comments: \`stego comments add|reply|set-status|delete|clear-resolved|sync-anchors ... --format json\`
166
+ `;
167
+ export const PROSE_MARKDOWN_EDITOR_SETTINGS = {
168
+ "[markdown]": {
169
+ "editor.fontFamily": "Inter, Helvetica Neue, Helvetica, Arial, sans-serif",
170
+ "editor.fontSize": 17,
171
+ "editor.lineHeight": 28,
172
+ "editor.wordWrap": "wordWrapColumn",
173
+ "editor.wordWrapColumn": 72,
174
+ "editor.lineNumbers": "off"
175
+ },
176
+ "markdown.preview.fontFamily": "Inter, Helvetica Neue, Helvetica, Arial, sans-serif"
177
+ };
178
+ export const PROJECT_EXTENSION_RECOMMENDATIONS = [
179
+ "matt-gold.stego-extension",
180
+ "matt-gold.saurus-extension",
181
+ "streetsidesoftware.code-spell-checker"
182
+ ];
@@ -0,0 +1,8 @@
1
+ import { registerInitCommand } from "./commands/init.js";
2
+ export const scaffoldModule = {
3
+ registerCommands(registry) {
4
+ registerInitCommand(registry);
5
+ }
6
+ };
7
+ export * from "./types.js";
8
+ export * from "./application/scaffold-workspace.js";
@@ -0,0 +1,33 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ export function listDirectoryEntries(dirPath) {
4
+ return fs.readdirSync(dirPath, { withFileTypes: true });
5
+ }
6
+ export function pathExists(filePath) {
7
+ return fs.existsSync(filePath);
8
+ }
9
+ export function statPath(filePath) {
10
+ return fs.statSync(filePath);
11
+ }
12
+ export function ensureDirectory(dirPath) {
13
+ fs.mkdirSync(dirPath, { recursive: true });
14
+ }
15
+ export function copyFile(sourcePath, destinationPath) {
16
+ ensureDirectory(path.dirname(destinationPath));
17
+ fs.copyFileSync(sourcePath, destinationPath);
18
+ }
19
+ export function copyDirectory(sourcePath, destinationPath, filter) {
20
+ ensureDirectory(destinationPath);
21
+ fs.cpSync(sourcePath, destinationPath, {
22
+ recursive: true,
23
+ force: true,
24
+ filter
25
+ });
26
+ }
27
+ export function writeTextFile(filePath, content) {
28
+ ensureDirectory(path.dirname(filePath));
29
+ fs.writeFileSync(filePath, content, "utf8");
30
+ }
31
+ export function readTextFile(filePath) {
32
+ return fs.readFileSync(filePath, "utf8");
33
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ import { createSpineCategory } from "../domain/spine.js";
2
+ import { readRequiredMetadata, writeRequiredMetadata } from "../infra/spine-repo.js";
3
+ export function createSpineCategoryForProject(input) {
4
+ const requiredMetadata = readRequiredMetadata(input.project.meta);
5
+ const result = createSpineCategory(input.project.root, input.project.spineDir, input.key, input.label, requiredMetadata, input.requireMetadata);
6
+ if (input.requireMetadata && result.requiredMetadataUpdated) {
7
+ writeRequiredMetadata(input.project.root, input.project.meta, requiredMetadata);
8
+ }
9
+ return {
10
+ ok: true,
11
+ operation: "new-category",
12
+ result
13
+ };
14
+ }
@@ -0,0 +1,9 @@
1
+ import { createSpineEntry } from "../domain/spine.js";
2
+ export function createSpineEntryForProject(input) {
3
+ const result = createSpineEntry(input.project.root, input.project.spineDir, input.category, input.filename);
4
+ return {
5
+ ok: true,
6
+ operation: "new",
7
+ result
8
+ };
9
+ }
@@ -0,0 +1,13 @@
1
+ import { readSpineCatalog } from "../domain/spine.js";
2
+ export function readSpineCatalogForProject(project) {
3
+ const catalog = readSpineCatalog(project.root, project.spineDir);
4
+ return {
5
+ ok: true,
6
+ operation: "read",
7
+ state: {
8
+ projectId: project.id,
9
+ categories: catalog.categories,
10
+ issues: catalog.issues
11
+ }
12
+ };
13
+ }
@@ -0,0 +1,33 @@
1
+ export function registerSpineDeprecatedAliases(registry) {
2
+ registry.register({
3
+ name: "spine add-category",
4
+ description: "Deprecated alias for spine new-category",
5
+ allowUnknownOptions: true,
6
+ options: [
7
+ { flags: "--project <project-id>", description: "Project id" },
8
+ { flags: "--key <category>", description: "Category key" },
9
+ { flags: "--label <label>", description: "Category display label" },
10
+ { flags: "--require-metadata", description: "Append key to required metadata" },
11
+ { flags: "--format <format>", description: "text|json" },
12
+ { flags: "--root <path>", description: "Workspace root path" }
13
+ ],
14
+ action: () => {
15
+ throw new Error("`stego spine add-category` was renamed. Use `stego spine new-category`.");
16
+ }
17
+ });
18
+ registry.register({
19
+ name: "spine new-entry",
20
+ description: "Deprecated alias for spine new",
21
+ allowUnknownOptions: true,
22
+ options: [
23
+ { flags: "--project <project-id>", description: "Project id" },
24
+ { flags: "--category <category>", description: "Category key" },
25
+ { flags: "--filename <path>", description: "Relative entry path" },
26
+ { flags: "--format <format>", description: "text|json" },
27
+ { flags: "--root <path>", description: "Workspace root path" }
28
+ ],
29
+ action: () => {
30
+ throw new Error("`stego spine new-entry` was renamed. Use `stego spine new`.");
31
+ }
32
+ });
33
+ }