stego-cli 0.1.5 → 0.1.7

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 (47) hide show
  1. package/.gitignore +1 -0
  2. package/.markdownlint.manuscript.json +8 -0
  3. package/README.md +39 -189
  4. package/dist/stego-cli.js +163 -24
  5. package/package.json +7 -5
  6. package/projects/fiction-example/.markdownlint.json +5 -0
  7. package/projects/fiction-example/.markdownlint.manuscript.json +8 -0
  8. package/projects/{plague-demo → fiction-example}/README.md +5 -5
  9. package/projects/{plague-demo → fiction-example}/manuscript/300-the-hearing.md +1 -13
  10. package/projects/{plague-demo → fiction-example}/package.json +1 -1
  11. package/projects/{plague-demo → fiction-example}/spine/characters.md +4 -0
  12. package/projects/{plague-demo → fiction-example}/spine/locations.md +4 -0
  13. package/projects/{plague-demo → fiction-example}/spine/sources.md +3 -0
  14. package/projects/{plague-demo → fiction-example}/stego-project.json +1 -1
  15. package/projects/stego-docs/README.md +27 -0
  16. package/projects/stego-docs/manuscript/100-what-stego-is.md +56 -0
  17. package/projects/stego-docs/manuscript/200-install-and-initialize.md +68 -0
  18. package/projects/stego-docs/manuscript/300-workspace-layout-and-vscode.md +56 -0
  19. package/projects/stego-docs/manuscript/400-everyday-workflow-and-commands.md +70 -0
  20. package/projects/stego-docs/manuscript/500-project-configuration.md +58 -0
  21. package/projects/stego-docs/manuscript/600-spine-and-browser-workflows.md +55 -0
  22. package/projects/stego-docs/manuscript/700-validation-and-stage-gates.md +53 -0
  23. package/projects/stego-docs/manuscript/800-build-export-and-release-outputs.md +53 -0
  24. package/projects/{docs-demo → stego-docs}/package.json +1 -1
  25. package/projects/stego-docs/spine/commands.md +62 -0
  26. package/projects/stego-docs/spine/concepts.md +72 -0
  27. package/projects/stego-docs/spine/configuration.md +57 -0
  28. package/projects/stego-docs/spine/integrations.md +43 -0
  29. package/projects/stego-docs/spine/workflows.md +48 -0
  30. package/projects/stego-docs/stego-project.json +50 -0
  31. package/docs/conventions.md +0 -182
  32. package/docs/workflow.md +0 -78
  33. package/projects/docs-demo/README.md +0 -20
  34. package/projects/docs-demo/manuscript/100-what-stego-is.md +0 -37
  35. package/projects/docs-demo/manuscript/200-writing-workflow.md +0 -69
  36. package/projects/docs-demo/manuscript/300-quality-gates.md +0 -36
  37. package/projects/docs-demo/manuscript/400-build-and-export.md +0 -42
  38. package/projects/docs-demo/stego-project.json +0 -9
  39. package/projects/plague-demo/.markdownlint.json +0 -4
  40. /package/projects/{docs-demo → fiction-example}/dist/.gitkeep +0 -0
  41. /package/projects/{plague-demo → fiction-example}/manuscript/100-the-commission.md +0 -0
  42. /package/projects/{plague-demo → fiction-example}/manuscript/200-at-the-wards.md +0 -0
  43. /package/projects/{plague-demo → fiction-example}/manuscript/400-the-final-account.md +0 -0
  44. /package/projects/{plague-demo → fiction-example}/notes/style-guide.md +0 -0
  45. /package/projects/{plague-demo → stego-docs}/dist/.gitkeep +0 -0
  46. /package/projects/{docs-demo → stego-docs}/notes/implementation-notes.md +0 -0
  47. /package/projects/{docs-demo → stego-docs}/notes/style-guide.md +0 -0
package/.gitignore CHANGED
@@ -6,3 +6,4 @@ projects/*/dist/*
6
6
  !projects/*/dist/.gitkeep
7
7
  projects/*/.vscode/settings.json
8
8
  .vscode/settings.json
9
+ local-only/
@@ -0,0 +1,8 @@
1
+ {
2
+ "default": true,
3
+ "MD013": false,
4
+ "MD033": false,
5
+ "MD026": false,
6
+ "MD036": false,
7
+ "MD041": false
8
+ }
package/README.md CHANGED
@@ -1,22 +1,12 @@
1
- # Stego
1
+ # Stego CLI
2
2
 
3
- Stego is a Markdown-first creative writing workflow packaged as an installable CLI: `stego-cli`.
3
+ `stego-cli` is an installable CLI for the Stego writing workflow.
4
4
 
5
- This repository is the source for the CLI and the example/template content that `stego init` scaffolds into a new workspace.
5
+ It scaffolds a Stego workspace, validates manuscript structure and metadata, runs stage-aware quality gates, builds compiled markdown outputs, and exports release formats.
6
6
 
7
- ## What this includes
7
+ This repository is the source for the CLI and the template/example projects that `stego init` scaffolds.
8
8
 
9
- - Installable CLI (`stego`) for creating and operating Stego workspaces
10
- - Project-per-folder structure inside one monorepo.
11
- - Flexible manuscript files with per-project metadata requirements.
12
- - Configurable manuscript grouping via `compileStructure.levels` (for example `part` + `chapter`).
13
- - Project-defined spine categories (configured in each `stego-project.json`).
14
- - Deterministic build into one manuscript Markdown file.
15
- - Stage-based quality gates (`draft` -> `final`).
16
- - Export abstraction (`md` always, `docx`/`pdf` via optional `pandoc`).
17
- - Example/demo projects (`docs-demo`, `plague-demo`) included in `stego init`.
18
-
19
- ## Getting started (from npm)
9
+ ## Quick start (install + init)
20
10
 
21
11
  ```bash
22
12
  npm install -g stego-cli
@@ -24,73 +14,69 @@ npm install -g stego-cli
24
14
  mkdir my-stego-workspace
25
15
  cd my-stego-workspace
26
16
  stego init
27
-
28
17
  npm install
29
- npm run list-projects
30
- npm run validate -- --project plague-demo
31
- npm run build -- --project plague-demo
32
- npm run check-stage -- --project plague-demo --stage revise
33
- npm run export -- --project plague-demo --format md
18
+
19
+ stego list-projects
20
+ stego validate --project fiction-example
21
+ stego build --project fiction-example
34
22
  ```
35
23
 
36
- `stego init` scaffolds a new workspace in the current directory (it must be empty unless you pass `--force`).
24
+ `stego init` scaffolds two example projects:
37
25
 
38
- Create another project in the workspace:
26
+ - `stego-docs` (the full documentation project)
27
+ - `fiction-example` (a fiction-oriented demo with rich Spine usage)
39
28
 
40
- ```bash
41
- stego new-project --project my-new-project --title "My New Project"
42
- ```
29
+ For day-to-day editing, open a project folder in VS Code (for example `projects/stego-docs`) and use the Stego VS Code extension, which is the official UI for Stego projects.
30
+
31
+ ## Full documentation
43
32
 
44
- `stego new-project` scaffolds `manuscript/`, `spine/`, `notes/`, and `dist/`, seeds `stego-project.json`, creates a project-local `package.json`, and writes `.vscode/extensions.json` recommendations (Stego + Saurus) for that project.
33
+ The full user documentation lives in the `stego-docs` project.
45
34
 
46
- ## Running commands for a specific project
35
+ - In a scaffolded workspace: `projects/stego-docs`
36
+ - In this source repo: `projects/stego-docs`
47
37
 
48
- From the workspace root, target a project with `--project`:
38
+ Start by reading the manuscript files in order, or build the docs project:
49
39
 
50
40
  ```bash
51
- npm run validate -- --project plague-demo
52
- npm run build -- --project plague-demo
53
- npm run check-stage -- --project plague-demo --stage proof
54
- npm run export -- --project plague-demo --format md
41
+ stego build --project stego-docs
55
42
  ```
56
43
 
57
- Each project also has local scripts, so you can work from inside the project directory:
44
+ ## Core commands
45
+
46
+ Run commands from the workspace root and target a project with `--project`.
58
47
 
59
48
  ```bash
60
- cd projects/plague-demo
61
- npm run validate
62
- npm run build
63
- npm run check-stage -- --stage proof
64
- npm run export -- --format md
49
+ stego list-projects
50
+ stego new-project --project my-book --title "My Book"
51
+ stego validate --project fiction-example
52
+ stego build --project fiction-example
53
+ stego check-stage --project fiction-example --stage revise
54
+ stego export --project fiction-example --format md
65
55
  ```
66
56
 
67
- ## VS Code workflow
57
+ Projects also include local npm scripts so you can work from inside a project directory.
68
58
 
69
- When you are actively working on one project, open that project directory directly in VS Code (for example `projects/plague-demo`) rather than the whole workspace.
59
+ ## VS Code workflow
70
60
 
71
- This keeps editor context focused and applies the project's recommended extensions via `projects/<project-id>/.vscode/extensions.json`.
61
+ When actively working on one project, open that project directory directly in VS Code (for example `projects/fiction-example`).
72
62
 
73
- ## Developing `stego-cli` (this repo)
63
+ The Stego VS Code extension is the official UI for Stego projects, and opening a single project keeps its UI context and Spine Browser focused. Project folders also include extension recommendations.
74
64
 
75
- If you are working on the CLI itself (this repository), use the local development scripts:
65
+ ## Develop `stego-cli` (this repo)
76
66
 
77
67
  ```bash
78
68
  npm install
79
69
  npm run list-projects
80
- npm run validate -- --project docs-demo
81
- npm run build -- --project plague-demo
82
- npm run test:compile-structure
70
+ npm run validate -- --project stego-docs
71
+ npm run build -- --project fiction-example
72
+ npm run test
83
73
  npm run build:cli
84
74
  npm run pack:dry-run
85
75
  ```
86
76
 
87
- These source-repo scripts run the TypeScript CLI directly with Node (`--experimental-strip-types`) for local development.
88
-
89
- ## Export requirements (DOCX/PDF)
77
+ ## Export requirements (`docx`, `pdf`, `epub`)
90
78
 
91
- `docx` and `pdf` export require `pandoc` on your system `PATH`.
92
-
93
- Install:
79
+ These formats require `pandoc` on your `PATH`.
94
80
 
95
81
  ```bash
96
82
  # macOS (Homebrew)
@@ -106,139 +92,3 @@ sudo apt-get update && sudo apt-get install -y pandoc
106
92
  # Windows (winget)
107
93
  winget install --id JohnMacFarlane.Pandoc -e
108
94
  ```
109
-
110
- Verify:
111
-
112
- ```bash
113
- pandoc --version
114
- ```
115
-
116
- Then run:
117
-
118
- ```bash
119
- npm run export -- --project plague-demo --format docx
120
- npm run export -- --project plague-demo --format pdf
121
- ```
122
-
123
- ## Scaffolded workspace layout
124
-
125
- - `projects/<project-id>/manuscript/` source manuscript files
126
- - `projects/<project-id>/spine/` canonical spine category files (`spineCategories[*].notesFile`)
127
- - `projects/<project-id>/notes/` regular notes and planning docs
128
- - `projects/<project-id>/dist/` generated outputs only
129
- - `stego.config.json` workspace configuration
130
- - `docs/` workflow and conventions
131
- - `.vscode/tasks.json` root VS Code tasks for common Stego commands
132
-
133
- ## This repo layout (`stego-cli` source)
134
-
135
- - `tools/` CLI source code and exporters
136
- - `projects/` template/demo projects bundled by `stego init`
137
- - `docs/` user-facing docs copied into scaffolded workspaces
138
- - `.github/workflows/` CI + Changesets release automation
139
-
140
- ## Project spine categories
141
-
142
- Spine categories are not fixed. Each project can declare them in `stego-project.json` under `spineCategories`.
143
-
144
- Example:
145
-
146
- ```json
147
- {
148
- "spineCategories": [
149
- { "key": "cast", "prefix": "CHAR", "notesFile": "characters.md" },
150
- { "key": "places", "prefix": "LOC", "notesFile": "locations.md" },
151
- { "key": "incidents", "prefix": "EVENT", "notesFile": "timeline.md" },
152
- { "key": "ordinances", "prefix": "STATUTE", "notesFile": "ordinances.md" }
153
- ]
154
- }
155
- ```
156
-
157
- Use those keys as metadata arrays in manuscript files (for example `cast`, `places`, `incidents`, `ordinances`).
158
- Each `notesFile` is a filename resolved in `spine/` (for example `spine/characters.md`).
159
-
160
- If `spineCategories` is omitted or empty, category-based continuity validation is disabled.
161
-
162
- ## Project metadata requirements
163
-
164
- Base config defaults to `status`.
165
-
166
- Each project can override required keys in `stego-project.json`:
167
-
168
- ```json
169
- {
170
- "requiredMetadata": ["status", "chapter", "pov", "timeline"]
171
- }
172
- ```
173
-
174
- These keys are advisory and reported as warnings when missing; they do not block validate/build/export.
175
- Files may omit metadata entirely.
176
-
177
- ## Compile structure (grouped manuscript output)
178
-
179
- Build grouping is configured per project with `compileStructure.levels`.
180
-
181
- Example:
182
-
183
- ```json
184
- {
185
- "compileStructure": {
186
- "levels": [
187
- {
188
- "key": "part",
189
- "label": "Part",
190
- "titleKey": "part_title",
191
- "injectHeading": true,
192
- "headingTemplate": "{label} {value}: {title}",
193
- "pageBreak": "between-groups"
194
- },
195
- {
196
- "key": "chapter",
197
- "label": "Chapter",
198
- "titleKey": "chapter_title",
199
- "injectHeading": true,
200
- "headingTemplate": "{label} {value}: {title}",
201
- "pageBreak": "between-groups"
202
- }
203
- ]
204
- }
205
- }
206
- ```
207
-
208
- Notes:
209
-
210
- - `pageBreak` currently supports `none` or `between-groups`.
211
- - TOC entries are nested by level depth.
212
- - Missing group key/title values inherit from the previous manuscript file, so you only need to set metadata at structural boundaries.
213
- - `validate` reports configuration errors for invalid `compileStructure` entries.
214
-
215
- ## Included examples
216
-
217
- - `plague-demo`: full configuration — rich metadata (`pov`, `timeline`), three spine categories (`characters`, `locations`, `sources`), cross-linked spine with Wikipedia reference links
218
- - `docs-demo`: nonfiction documentation configuration — no spine categories, freeform notes only, primarily `status` metadata
219
-
220
- ## Placeholder edit workflow (`{{...}}` + Cmd+I)
221
-
222
- This repo includes Copilot instruction files to keep placeholder edits scoped:
223
-
224
- - `.github/copilot-instructions.md`
225
- - `.github/instructions/placeholder-fill.instructions.md`
226
-
227
- Placeholder convention:
228
-
229
- - Write draft placeholders as `{{...}}` in manuscript prose.
230
- - Select the placeholder text and run Cmd+I.
231
- - Use short prompts like `fill placeholder` or `replace only inside {{}}`.
232
-
233
- Expected behavior:
234
-
235
- - Replace only the content inside braces.
236
- - Preserve surrounding sentence/paragraph text.
237
-
238
- ## Next Steps
239
-
240
- - Add Mermaid graphs of metadata (entity relationships, co-occurrence, chapter sequence).
241
-
242
- ## VS Code tasks
243
-
244
- Tasks are defined in `.vscode/tasks.json` for validate/build/stage-check/export.
package/dist/stego-cli.js CHANGED
@@ -34,25 +34,26 @@ This directory is a Stego writing workspace (a monorepo for one or more writing
34
34
  ## What was scaffolded
35
35
 
36
36
  - \`stego.config.json\` workspace configuration
37
- - \`projects/\` demo projects (\`docs-demo\` and \`plague-demo\`)
38
- - \`docs/\` workflow and conventions docs
37
+ - \`projects/\` demo projects (\`stego-docs\` and \`fiction-example\`)
39
38
  - root \`package.json\` scripts for Stego commands
40
39
  - root \`.vscode/tasks.json\` tasks for common workflows
41
40
 
41
+ Full documentation lives in \`projects/stego-docs\`.
42
+
42
43
  ## First run
43
44
 
44
45
  \`\`\`bash
45
46
  npm install
46
- npm run list-projects
47
+ stego list-projects
47
48
  \`\`\`
48
49
 
49
50
  ## Run commands for a specific project (from workspace root)
50
51
 
51
52
  \`\`\`bash
52
- npm run validate -- --project plague-demo
53
- npm run build -- --project plague-demo
54
- npm run check-stage -- --project plague-demo --stage revise
55
- npm run export -- --project plague-demo --format md
53
+ stego validate --project fiction-example
54
+ stego build --project fiction-example
55
+ stego check-stage --project fiction-example --stage revise
56
+ stego export --project fiction-example --format md
56
57
  \`\`\`
57
58
 
58
59
  ## Work inside one project
@@ -60,14 +61,14 @@ npm run export -- --project plague-demo --format md
60
61
  Each project also has local scripts, so you can run commands from inside a project directory:
61
62
 
62
63
  \`\`\`bash
63
- cd projects/plague-demo
64
+ cd projects/fiction-example
64
65
  npm run validate
65
66
  npm run build
66
67
  \`\`\`
67
68
 
68
69
  ## VS Code recommendation
69
70
 
70
- When you are actively working on one project, open that project directory directly in VS Code (for example \`projects/plague-demo\`).
71
+ When you are actively working on one project, open that project directory directly in VS Code (for example \`projects/fiction-example\`).
71
72
 
72
73
  This keeps your editor context focused and applies the project's recommended extensions (including Stego + Saurus) for that project.
73
74
 
@@ -156,6 +157,18 @@ async function main() {
156
157
  }
157
158
  return;
158
159
  }
160
+ case "lint": {
161
+ activateWorkspace(options);
162
+ const project = resolveProject(readStringOption(options, "project"));
163
+ const selection = resolveLintSelection(options);
164
+ const result = runProjectLint(project, selection);
165
+ printReport(result.issues);
166
+ exitIfErrors(result.issues);
167
+ const scopeLabel = formatLintSelection(selection);
168
+ const fileLabel = result.fileCount === 1 ? "file" : "files";
169
+ logLine(`Lint passed for '${project.id}' (${scopeLabel}, ${result.fileCount} ${fileLabel}).`);
170
+ return;
171
+ }
159
172
  case "export": {
160
173
  activateWorkspace(options);
161
174
  const project = resolveProject(readStringOption(options, "project"));
@@ -463,9 +476,9 @@ async function initWorkspace(options) {
463
476
  writeScaffoldGitignore(targetRoot, copiedPaths);
464
477
  writeScaffoldReadme(targetRoot, copiedPaths);
465
478
  copyTemplateAsset(".markdownlint.json", targetRoot, copiedPaths);
479
+ copyTemplateAsset(".markdownlint.manuscript.json", targetRoot, copiedPaths);
466
480
  copyTemplateAsset(".cspell.json", targetRoot, copiedPaths);
467
481
  copyTemplateAsset(ROOT_CONFIG_FILENAME, targetRoot, copiedPaths);
468
- copyTemplateAsset("docs", targetRoot, copiedPaths);
469
482
  copyTemplateAsset("projects", targetRoot, copiedPaths);
470
483
  copyTemplateAsset(path.join(".vscode", "tasks.json"), targetRoot, copiedPaths);
471
484
  copyTemplateAsset(path.join(".vscode", "extensions.json"), targetRoot, copiedPaths, { optional: true });
@@ -483,8 +496,9 @@ async function initWorkspace(options) {
483
496
  logLine("");
484
497
  logLine("Next steps:");
485
498
  logLine(" npm install");
486
- logLine(" npm run list-projects");
487
- logLine(" npm run validate -- --project plague-demo");
499
+ logLine(" stego list-projects");
500
+ logLine(" stego validate --project fiction-example");
501
+ logLine(" stego build --project fiction-example");
488
502
  }
489
503
  async function promptYesNo(question, defaultYes) {
490
504
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
@@ -589,9 +603,6 @@ function rewriteTemplateProjectPackageScripts(targetRoot) {
589
603
  const scripts = isPlainObject(projectPackage.scripts)
590
604
  ? { ...projectPackage.scripts }
591
605
  : {};
592
- if (typeof projectPackage.name === "string" && projectPackage.name.startsWith("writing-project-")) {
593
- projectPackage.name = projectPackage.name.replace(/^writing-project-/, "stego-project-");
594
- }
595
606
  scripts.validate = "npx --no-install stego validate";
596
607
  scripts.build = "npx --no-install stego build";
597
608
  scripts["check-stage"] = "npx --no-install stego check-stage";
@@ -686,6 +697,7 @@ function writeInitRootPackageJson(targetRoot) {
686
697
  scripts: {
687
698
  "list-projects": "stego list-projects",
688
699
  "new-project": "stego new-project",
700
+ lint: "stego lint",
689
701
  validate: "stego validate",
690
702
  build: "stego build",
691
703
  "check-stage": "stego check-stage",
@@ -700,7 +712,7 @@ function writeInitRootPackageJson(targetRoot) {
700
712
  fs.writeFileSync(path.join(targetRoot, "package.json"), `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
701
713
  }
702
714
  function printUsage() {
703
- console.log(`Stego CLI\n\nCommands:\n init [--force]\n list-projects [--root <path>]\n new-project --project <project-id> [--title <title>] [--root <path>]\n validate --project <project-id> [--file <project-relative-manuscript-path>] [--root <path>]\n build --project <project-id> [--root <path>]\n check-stage --project <project-id> --stage <draft|revise|line-edit|proof|final> [--file <project-relative-manuscript-path>] [--root <path>]\n export --project <project-id> --format <md|docx|pdf|epub> [--output <path>] [--root <path>]\n`);
715
+ console.log(`Stego CLI\n\nCommands:\n init [--force]\n list-projects [--root <path>]\n new-project --project <project-id> [--title <title>] [--root <path>]\n validate --project <project-id> [--file <project-relative-manuscript-path>] [--root <path>]\n build --project <project-id> [--root <path>]\n check-stage --project <project-id> --stage <draft|revise|line-edit|proof|final> [--file <project-relative-manuscript-path>] [--root <path>]\n lint --project <project-id> [--manuscript|--spine] [--root <path>]\n export --project <project-id> --format <md|docx|pdf|epub> [--output <path>] [--root <path>]\n`);
704
716
  }
705
717
  function listProjects() {
706
718
  const ids = getProjectIds();
@@ -731,6 +743,7 @@ async function createProject(projectIdOption, titleOption) {
731
743
  const notesDir = path.join(projectRoot, config.notesDir);
732
744
  fs.mkdirSync(notesDir, { recursive: true });
733
745
  fs.mkdirSync(path.join(projectRoot, config.distDir), { recursive: true });
746
+ const manuscriptDir = path.join(projectRoot, config.chapterDir);
734
747
  const projectJson = {
735
748
  id: projectId,
736
749
  title: titleOption?.trim() || toDisplayTitle(projectId),
@@ -761,6 +774,7 @@ async function createProject(projectIdOption, titleOption) {
761
774
  name: `stego-project-${projectId}`,
762
775
  private: true,
763
776
  scripts: {
777
+ lint: "npx --no-install stego lint",
764
778
  validate: "npx --no-install stego validate",
765
779
  build: "npx --no-install stego build",
766
780
  "check-stage": "npx --no-install stego check-stage",
@@ -769,6 +783,17 @@ async function createProject(projectIdOption, titleOption) {
769
783
  };
770
784
  const projectPackagePath = path.join(projectRoot, "package.json");
771
785
  fs.writeFileSync(projectPackagePath, `${JSON.stringify(projectPackage, null, 2)}\n`, "utf8");
786
+ const starterManuscriptPath = path.join(manuscriptDir, "100-hello-world.md");
787
+ fs.writeFileSync(starterManuscriptPath, `---
788
+ status: draft
789
+ chapter: 1
790
+ chapter_title: Hello World
791
+ ---
792
+
793
+ # Hello World
794
+
795
+ Start writing here.
796
+ `, "utf8");
772
797
  const charactersNotesPath = path.join(spineDir, "characters.md");
773
798
  fs.writeFileSync(charactersNotesPath, "# Characters\n\n", "utf8");
774
799
  const projectExtensionsPath = path.join(projectRoot, ".vscode", "extensions.json");
@@ -781,6 +806,7 @@ async function createProject(projectIdOption, titleOption) {
781
806
  logLine(`Created project: ${path.relative(repoRoot, projectRoot)}`);
782
807
  logLine(`- ${path.relative(repoRoot, projectJsonPath)}`);
783
808
  logLine(`- ${path.relative(repoRoot, projectPackagePath)}`);
809
+ logLine(`- ${path.relative(repoRoot, starterManuscriptPath)}`);
784
810
  logLine(`- ${path.relative(repoRoot, charactersNotesPath)}`);
785
811
  logLine(`- ${path.relative(repoRoot, projectExtensionsPath)}`);
786
812
  if (projectSettingsPath) {
@@ -1604,10 +1630,10 @@ function runStageCheck(project, runtimeConfig, stage, onlyFile) {
1604
1630
  const chapterPaths = report.chapters.map((chapter) => chapter.path);
1605
1631
  const spineWords = collectSpineWordsForSpellcheck(report.spineState.ids);
1606
1632
  if (policy.enforceMarkdownlint) {
1607
- issues.push(...runMarkdownlint(project, chapterPaths, true));
1633
+ issues.push(...runMarkdownlint(project, chapterPaths, true, "manuscript"));
1608
1634
  }
1609
1635
  else {
1610
- issues.push(...runMarkdownlint(project, chapterPaths, false));
1636
+ issues.push(...runMarkdownlint(project, chapterPaths, false, "manuscript"));
1611
1637
  }
1612
1638
  if (policy.enforceCSpell) {
1613
1639
  issues.push(...runCSpell(chapterPaths, true, spineWords));
@@ -1617,20 +1643,133 @@ function runStageCheck(project, runtimeConfig, stage, onlyFile) {
1617
1643
  }
1618
1644
  return { chapters: report.chapters, issues };
1619
1645
  }
1620
- function runMarkdownlint(project, files, required) {
1646
+ function resolveLintSelection(options) {
1647
+ const manuscript = readBooleanOption(options, "manuscript");
1648
+ const spine = readBooleanOption(options, "spine");
1649
+ if (!manuscript && !spine) {
1650
+ return { manuscript: true, spine: true };
1651
+ }
1652
+ return { manuscript, spine };
1653
+ }
1654
+ function formatLintSelection(selection) {
1655
+ if (selection.manuscript && selection.spine) {
1656
+ return "manuscript + spine";
1657
+ }
1658
+ if (selection.manuscript) {
1659
+ return "manuscript";
1660
+ }
1661
+ if (selection.spine) {
1662
+ return "spine";
1663
+ }
1664
+ return "none";
1665
+ }
1666
+ function runProjectLint(project, selection) {
1667
+ const issues = [];
1668
+ let fileCount = 0;
1669
+ if (selection.manuscript) {
1670
+ const manuscriptFiles = collectTopLevelMarkdownFiles(project.manuscriptDir);
1671
+ if (manuscriptFiles.length === 0) {
1672
+ issues.push(makeIssue("error", "lint", `No manuscript markdown files found in ${project.manuscriptDir}`));
1673
+ }
1674
+ else {
1675
+ fileCount += manuscriptFiles.length;
1676
+ issues.push(...runMarkdownlint(project, manuscriptFiles, true, "manuscript"));
1677
+ }
1678
+ }
1679
+ if (selection.spine) {
1680
+ const spineLintState = collectSpineLintMarkdownFiles(project);
1681
+ issues.push(...spineLintState.issues);
1682
+ if (spineLintState.files.length > 0) {
1683
+ fileCount += spineLintState.files.length;
1684
+ issues.push(...runMarkdownlint(project, spineLintState.files, true, "default"));
1685
+ }
1686
+ }
1687
+ if (fileCount === 0 && issues.length === 0) {
1688
+ issues.push(makeIssue("error", "lint", `No markdown files found for lint scope '${formatLintSelection(selection)}' in project '${project.id}'.`));
1689
+ }
1690
+ return { issues, fileCount };
1691
+ }
1692
+ function collectSpineLintMarkdownFiles(project) {
1693
+ const issues = [];
1694
+ const files = new Set();
1695
+ addMarkdownFilesFromDirectory(files, project.spineDir, true);
1696
+ if (!fs.existsSync(project.spineDir)) {
1697
+ issues.push(makeIssue("warning", "lint", `Missing spine directory: ${project.spineDir}`));
1698
+ }
1699
+ addMarkdownFilesFromDirectory(files, project.notesDir, true);
1700
+ if (!fs.existsSync(project.notesDir)) {
1701
+ issues.push(makeIssue("warning", "lint", `Missing notes directory: ${project.notesDir}`));
1702
+ }
1703
+ for (const file of collectTopLevelMarkdownFiles(project.root)) {
1704
+ files.add(file);
1705
+ }
1706
+ const sortedFiles = Array.from(files).sort();
1707
+ if (sortedFiles.length === 0) {
1708
+ issues.push(makeIssue("error", "lint", `No spine/notes markdown files found in ${project.spineDir}, ${project.notesDir}, or project root.`));
1709
+ }
1710
+ return { files: sortedFiles, issues };
1711
+ }
1712
+ function collectTopLevelMarkdownFiles(directory) {
1713
+ if (!fs.existsSync(directory) || !fs.statSync(directory).isDirectory()) {
1714
+ return [];
1715
+ }
1716
+ return fs
1717
+ .readdirSync(directory, { withFileTypes: true })
1718
+ .filter((entry) => entry.isFile() && entry.name.endsWith(".md"))
1719
+ .map((entry) => path.join(directory, entry.name))
1720
+ .sort();
1721
+ }
1722
+ function addMarkdownFilesFromDirectory(target, directory, recursive) {
1723
+ if (!fs.existsSync(directory) || !fs.statSync(directory).isDirectory()) {
1724
+ return;
1725
+ }
1726
+ const stack = [directory];
1727
+ while (stack.length > 0) {
1728
+ const current = stack.pop();
1729
+ if (!current) {
1730
+ continue;
1731
+ }
1732
+ const entries = fs.readdirSync(current, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
1733
+ for (const entry of entries) {
1734
+ const fullPath = path.join(current, entry.name);
1735
+ if (entry.isFile() && entry.name.endsWith(".md")) {
1736
+ target.add(fullPath);
1737
+ continue;
1738
+ }
1739
+ if (recursive && entry.isDirectory()) {
1740
+ stack.push(fullPath);
1741
+ }
1742
+ }
1743
+ }
1744
+ }
1745
+ function runMarkdownlint(project, files, required, profile = "default") {
1746
+ if (files.length === 0) {
1747
+ return [];
1748
+ }
1621
1749
  const markdownlintCommand = resolveCommand("markdownlint");
1622
1750
  if (!markdownlintCommand) {
1623
1751
  if (required) {
1624
1752
  return [
1625
- makeIssue("error", "tooling", "markdownlint is required for this stage but not installed. Run 'npm i' in the repo root.")
1753
+ makeIssue("error", "tooling", "markdownlint is required for this command but not installed. Run 'npm i' in the repo root.")
1626
1754
  ];
1627
1755
  }
1628
1756
  return [];
1629
1757
  }
1630
- const projectConfigPath = path.join(project.root, ".markdownlint.json");
1631
- const markdownlintConfigPath = fs.existsSync(projectConfigPath)
1632
- ? projectConfigPath
1633
- : path.join(repoRoot, ".markdownlint.json");
1758
+ const manuscriptProjectConfigPath = path.join(project.root, ".markdownlint.manuscript.json");
1759
+ const manuscriptRepoConfigPath = path.join(repoRoot, ".markdownlint.manuscript.json");
1760
+ const defaultProjectConfigPath = path.join(project.root, ".markdownlint.json");
1761
+ const defaultRepoConfigPath = path.join(repoRoot, ".markdownlint.json");
1762
+ const markdownlintConfigPath = profile === "manuscript"
1763
+ ? (fs.existsSync(manuscriptProjectConfigPath)
1764
+ ? manuscriptProjectConfigPath
1765
+ : fs.existsSync(manuscriptRepoConfigPath)
1766
+ ? manuscriptRepoConfigPath
1767
+ : fs.existsSync(defaultProjectConfigPath)
1768
+ ? defaultProjectConfigPath
1769
+ : defaultRepoConfigPath)
1770
+ : (fs.existsSync(defaultProjectConfigPath)
1771
+ ? defaultProjectConfigPath
1772
+ : defaultRepoConfigPath);
1634
1773
  const prepared = prepareFilesWithoutComments(files);
1635
1774
  try {
1636
1775
  const result = spawnSync(markdownlintCommand, ["--config", markdownlintConfigPath, ...prepared.files], {
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "stego-cli",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "type": "module",
5
5
  "description": "Installable CLI for the Stego writing monorepo workflow.",
6
6
  "license": "Apache-2.0",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "https://github.com/matt-gold/stego.git"
9
+ "url": "https://github.com/matt-gold/stego-cli.git"
10
10
  },
11
11
  "bugs": {
12
- "url": "https://github.com/matt-gold/stego/issues"
12
+ "url": "https://github.com/matt-gold/stego-cli/issues"
13
13
  },
14
- "homepage": "https://github.com/matt-gold/stego#readme",
14
+ "homepage": "https://github.com/matt-gold/stego-cli#readme",
15
15
  "bin": {
16
16
  "stego": "dist/stego-cli.js"
17
17
  },
@@ -20,14 +20,15 @@
20
20
  },
21
21
  "files": [
22
22
  "dist/**",
23
- "docs/**",
24
23
  "projects/**",
24
+ "!projects/local-only/**",
25
25
  "!projects/*/dist/*.md",
26
26
  "!projects/*/dist/exports/**",
27
27
  ".vscode/tasks.json",
28
28
  ".vscode/extensions.json",
29
29
  ".gitignore",
30
30
  ".markdownlint.json",
31
+ ".markdownlint.manuscript.json",
31
32
  ".cspell.json",
32
33
  "stego.config.json",
33
34
  "README.md"
@@ -41,6 +42,7 @@
41
42
  "release:version": "changeset version",
42
43
  "list-projects": "node --experimental-strip-types tools/stego-cli.ts list-projects",
43
44
  "new-project": "node --experimental-strip-types tools/stego-cli.ts new-project",
45
+ "lint": "node --experimental-strip-types tools/stego-cli.ts lint",
44
46
  "validate": "node --experimental-strip-types tools/stego-cli.ts validate",
45
47
  "build": "node --experimental-strip-types tools/stego-cli.ts build",
46
48
  "check-stage": "node --experimental-strip-types tools/stego-cli.ts check-stage",
@@ -0,0 +1,5 @@
1
+ {
2
+ "default": true,
3
+ "MD013": false,
4
+ "MD033": false
5
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "default": true,
3
+ "MD013": false,
4
+ "MD033": false,
5
+ "MD026": false,
6
+ "MD036": false,
7
+ "MD041": false
8
+ }