stego-cli 0.4.2 → 0.5.0

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 (45) hide show
  1. package/README.md +68 -22
  2. package/dist/shared/src/domain/frontmatter/validators.js +20 -2
  3. package/dist/shared/src/domain/images/index.js +1 -0
  4. package/dist/shared/src/domain/images/style.js +185 -0
  5. package/dist/shared/src/index.js +1 -0
  6. package/dist/stego-cli/src/app/command-registry.js +93 -1
  7. package/dist/stego-cli/src/app/create-cli-app.js +3 -0
  8. package/dist/stego-cli/src/app/error-boundary.js +11 -1
  9. package/dist/stego-cli/src/modules/comments/commands/comments-add.js +0 -1
  10. package/dist/stego-cli/src/modules/comments/commands/comments-clear-resolved.js +0 -1
  11. package/dist/stego-cli/src/modules/comments/commands/comments-delete.js +0 -1
  12. package/dist/stego-cli/src/modules/comments/commands/comments-read.js +0 -1
  13. package/dist/stego-cli/src/modules/comments/commands/comments-reply.js +0 -1
  14. package/dist/stego-cli/src/modules/comments/commands/comments-set-status.js +0 -1
  15. package/dist/stego-cli/src/modules/comments/commands/comments-sync-anchors.js +0 -1
  16. package/dist/stego-cli/src/modules/compile/application/compile-manuscript.js +12 -1
  17. package/dist/stego-cli/src/modules/compile/commands/build.js +1 -2
  18. package/dist/stego-cli/src/modules/compile/domain/compile-structure.js +0 -3
  19. package/dist/stego-cli/src/modules/compile/domain/image-settings.js +394 -0
  20. package/dist/stego-cli/src/modules/export/application/run-export.js +22 -1
  21. package/dist/stego-cli/src/modules/export/commands/export.js +1 -2
  22. package/dist/stego-cli/src/modules/export/infra/pandoc-exporter.js +29 -2
  23. package/dist/stego-cli/src/modules/manuscript/commands/new-manuscript.js +1 -1
  24. package/dist/stego-cli/src/modules/project/application/create-project.js +41 -1
  25. package/dist/stego-cli/src/modules/project/application/infer-project.js +1 -1
  26. package/dist/stego-cli/src/modules/project/commands/new-project.js +1 -1
  27. package/dist/stego-cli/src/modules/quality/application/inspect-project.js +147 -112
  28. package/dist/stego-cli/src/modules/quality/commands/check-stage.js +1 -2
  29. package/dist/stego-cli/src/modules/quality/commands/lint.js +1 -2
  30. package/dist/stego-cli/src/modules/quality/commands/validate.js +1 -2
  31. package/dist/stego-cli/src/modules/scaffold/commands/init.js +2 -2
  32. package/dist/stego-cli/src/modules/scaffold/domain/templates.js +15 -14
  33. package/dist/stego-cli/src/modules/spine/commands/spine-deprecated-aliases.js +2 -2
  34. package/dist/stego-cli/src/modules/spine/commands/spine-new-category.js +1 -2
  35. package/dist/stego-cli/src/modules/spine/commands/spine-new-entry.js +1 -2
  36. package/dist/stego-cli/src/modules/spine/commands/spine-read.js +1 -2
  37. package/filters/image-layout.css +23 -0
  38. package/filters/image-layout.lua +170 -0
  39. package/package.json +4 -2
  40. package/projects/fiction-example/assets/README.md +31 -0
  41. package/projects/stego-docs/assets/README.md +31 -0
  42. package/projects/stego-docs/manuscript/500-project-configuration.md +37 -0
  43. package/projects/stego-docs/manuscript/800-build-export-and-release-outputs.md +4 -0
  44. package/dist/stego-cli/src/modules/manuscript/domain/manuscript.js +0 -1
  45. package/dist/stego-cli/src/modules/project/domain/project.js +0 -1
@@ -1,6 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { parseCommentAppendix } from "../../../../../shared/src/domain/comments/index.js";
4
+ import { parseMarkdownDocument } from "../../../../../shared/src/domain/frontmatter/index.js";
4
5
  import { isStageName } from "../../../../../shared/src/domain/stages/index.js";
5
6
  import { readSpineCatalogForProject } from "../../spine/index.js";
6
7
  export function inspectProject(project, options = {}) {
@@ -12,6 +13,7 @@ export function inspectProject(project, options = {}) {
12
13
  const compileStructureState = resolveCompileStructure(project);
13
14
  issues.push(...requiredMetadataState.issues);
14
15
  issues.push(...compileStructureState.issues);
16
+ issues.push(...validateProjectImagesConfiguration(project));
15
17
  if (project.meta.spineCategories !== undefined) {
16
18
  issues.push(makeIssue("error", "metadata", "Legacy 'spineCategories' in stego-project.json is no longer supported. Use spine/ category directories and files.", path.relative(repoRoot, path.join(project.root, "stego-project.json"))));
17
19
  }
@@ -232,7 +234,8 @@ function parseChapter(chapterPath, project, requiredMetadata, spineCategories, c
232
234
  }
233
235
  const referenceValidation = extractReferenceKeysByCategory(metadata, relativePath, spineCategories);
234
236
  chapterIssues.push(...referenceValidation.issues);
235
- chapterIssues.push(...validateMarkdownBody(body, chapterPath, repoRoot));
237
+ chapterIssues.push(...validateImagesMetadata(metadata, relativePath));
238
+ chapterIssues.push(...validateMarkdownBody(body, chapterPath, repoRoot, project.root));
236
239
  return {
237
240
  path: chapterPath,
238
241
  relativePath,
@@ -251,7 +254,7 @@ function normalizeGroupingValue(rawValue, relativePath, issues, key) {
251
254
  if (rawValue == null || rawValue === "") {
252
255
  return undefined;
253
256
  }
254
- if (Array.isArray(rawValue)) {
257
+ if (Array.isArray(rawValue) || isPlainObject(rawValue)) {
255
258
  issues.push(makeIssue("error", "metadata", `Metadata '${key}' must be a scalar value.`, relativePath));
256
259
  return undefined;
257
260
  }
@@ -316,101 +319,135 @@ function extractReferenceKeysByCategory(metadata, relativePath, spineCategories)
316
319
  }
317
320
  return { referencesByCategory, issues };
318
321
  }
322
+ function validateImagesMetadata(metadata, relativePath) {
323
+ const issues = [];
324
+ const rawImages = metadata.images;
325
+ if (rawImages == null) {
326
+ return issues;
327
+ }
328
+ if (!isPlainObject(rawImages)) {
329
+ issues.push(makeIssue("warning", "metadata", "Metadata 'images' must be an object.", relativePath));
330
+ return issues;
331
+ }
332
+ const reservedGlobalKeys = new Set(["width", "height", "classes", "id", "attrs", "layout", "align"]);
333
+ for (const [key, value] of Object.entries(rawImages)) {
334
+ if (reservedGlobalKeys.has(key)) {
335
+ issues.push(makeIssue("warning", "metadata", `Manuscript frontmatter 'images.${key}' is reserved for project defaults. Put defaults in stego-project.json 'images.${key}'.`, relativePath));
336
+ continue;
337
+ }
338
+ if (!isPlainObject(value)) {
339
+ issues.push(makeIssue("warning", "metadata", `Metadata 'images.${key}' must be an object of style keys (width, height, classes, id, attrs, layout, align).`, relativePath));
340
+ continue;
341
+ }
342
+ for (const [styleKey, styleValue] of Object.entries(value)) {
343
+ issues.push(...validateImageStyleField(styleValue, styleKey, `images.${key}.${styleKey}`, relativePath));
344
+ }
345
+ }
346
+ return issues;
347
+ }
348
+ function validateProjectImagesConfiguration(project) {
349
+ const issues = [];
350
+ const rawImages = project.meta.images;
351
+ const projectConfigPath = path.relative(project.workspace.repoRoot, path.join(project.root, "stego-project.json"));
352
+ if (rawImages == null) {
353
+ return issues;
354
+ }
355
+ if (!isPlainObject(rawImages)) {
356
+ issues.push(makeIssue("warning", "metadata", "Project 'images' must be an object.", projectConfigPath));
357
+ return issues;
358
+ }
359
+ const reservedGlobalKeys = new Set(["width", "height", "classes", "id", "attrs", "layout", "align"]);
360
+ for (const [key, value] of Object.entries(rawImages)) {
361
+ if (reservedGlobalKeys.has(key)) {
362
+ issues.push(...validateImageStyleField(value, key, `images.${key}`, projectConfigPath));
363
+ continue;
364
+ }
365
+ issues.push(makeIssue("warning", "metadata", `Project image defaults do not support key 'images.${key}'. Use only width, height, classes, id, attrs, layout, align in stego-project.json.`, projectConfigPath));
366
+ }
367
+ return issues;
368
+ }
369
+ function validateImageStyleField(value, key, metadataPath, relativePath) {
370
+ const issues = [];
371
+ if (key === "width" || key === "height" || key === "id") {
372
+ if (!isStyleScalar(value)) {
373
+ issues.push(makeIssue("warning", "metadata", `Metadata '${metadataPath}' must be a scalar value.`, relativePath));
374
+ }
375
+ return issues;
376
+ }
377
+ if (key === "classes") {
378
+ if (typeof value === "string") {
379
+ return issues;
380
+ }
381
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string" || entry.trim().length === 0)) {
382
+ issues.push(makeIssue("warning", "metadata", `Metadata '${metadataPath}' must be a string or array of strings.`, relativePath));
383
+ }
384
+ return issues;
385
+ }
386
+ if (key === "attrs") {
387
+ if (!isPlainObject(value)) {
388
+ issues.push(makeIssue("warning", "metadata", `Metadata '${metadataPath}' must be an object of scalar values.`, relativePath));
389
+ return issues;
390
+ }
391
+ for (const [attrKey, attrValue] of Object.entries(value)) {
392
+ if (!isStyleScalar(attrValue)) {
393
+ issues.push(makeIssue("warning", "metadata", `Metadata '${metadataPath}.${attrKey}' must be a scalar value.`, relativePath));
394
+ }
395
+ }
396
+ return issues;
397
+ }
398
+ if (key === "layout") {
399
+ if (value !== "block" && value !== "inline") {
400
+ issues.push(makeIssue("warning", "metadata", `Metadata '${metadataPath}' must be either 'block' or 'inline'.`, relativePath));
401
+ }
402
+ return issues;
403
+ }
404
+ if (key === "align") {
405
+ if (value !== "left" && value !== "center" && value !== "right") {
406
+ issues.push(makeIssue("warning", "metadata", `Metadata '${metadataPath}' must be one of: left, center, right.`, relativePath));
407
+ }
408
+ return issues;
409
+ }
410
+ issues.push(makeIssue("warning", "metadata", `Unsupported image style key '${key}' in '${metadataPath}'. Allowed keys: width, height, classes, id, attrs, layout, align.`, relativePath));
411
+ return issues;
412
+ }
413
+ function isStyleScalar(value) {
414
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
415
+ }
319
416
  function parseMetadata(raw, chapterPath, repoRoot, required) {
320
417
  const relativePath = path.relative(repoRoot, chapterPath);
321
418
  const issues = [];
322
- if (!raw.startsWith("---\n") && !raw.startsWith("---\r\n")) {
323
- const commentsResult = parseStegoCommentsAppendix(raw, relativePath, 1);
324
- if (!required) {
325
- return {
326
- metadata: {},
327
- body: commentsResult.bodyWithoutComments,
328
- comments: commentsResult.comments,
329
- issues: commentsResult.issues
330
- };
331
- }
419
+ const frontmatterMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
420
+ if ((raw.startsWith("---\n") || raw.startsWith("---\r\n")) && !frontmatterMatch) {
332
421
  return {
333
422
  metadata: {},
334
- body: commentsResult.bodyWithoutComments,
335
- comments: commentsResult.comments,
336
- issues: [makeIssue("error", "metadata", "Missing metadata block at top of file.", relativePath), ...commentsResult.issues]
423
+ body: raw,
424
+ comments: [],
425
+ issues: [makeIssue("error", "metadata", "Metadata opening delimiter found, but closing delimiter is missing.", relativePath)]
337
426
  };
338
427
  }
339
- const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
340
- if (!match) {
428
+ let parsed;
429
+ try {
430
+ parsed = parseMarkdownDocument(raw);
431
+ }
432
+ catch (error) {
433
+ const message = error instanceof Error ? error.message : String(error);
341
434
  return {
342
435
  metadata: {},
343
436
  body: raw,
344
437
  comments: [],
345
- issues: [makeIssue("error", "metadata", "Metadata opening delimiter found, but closing delimiter is missing.", relativePath)]
438
+ issues: [makeIssue("error", "metadata", `Could not parse frontmatter: ${message}`, relativePath)]
346
439
  };
347
440
  }
348
- const metadataText = match[1];
349
- const body = raw.slice(match[0].length);
350
- const metadata = {};
351
- const lines = metadataText.split(/\r?\n/);
352
- for (let i = 0; i < lines.length; i += 1) {
353
- const line = lines[i].trim();
354
- if (!line || line.startsWith("#")) {
355
- continue;
356
- }
357
- const separatorIndex = line.indexOf(":");
358
- if (separatorIndex === -1) {
359
- issues.push(makeIssue("error", "metadata", `Invalid metadata line '${line}'. Expected 'key: value' format.`, relativePath, i + 1));
360
- continue;
361
- }
362
- const key = line.slice(0, separatorIndex).trim();
363
- const value = line.slice(separatorIndex + 1).trim();
364
- if (!value) {
365
- let lookahead = i + 1;
366
- while (lookahead < lines.length) {
367
- const nextTrimmed = lines[lookahead].trim();
368
- if (!nextTrimmed || nextTrimmed.startsWith("#")) {
369
- lookahead += 1;
370
- continue;
371
- }
372
- break;
373
- }
374
- if (lookahead < lines.length) {
375
- const firstValueLine = lines[lookahead];
376
- const firstValueTrimmed = firstValueLine.trim();
377
- const firstValueIndent = firstValueLine.length - firstValueLine.trimStart().length;
378
- if (firstValueIndent > 0 && firstValueTrimmed.startsWith("- ")) {
379
- const items = [];
380
- let j = lookahead;
381
- while (j < lines.length) {
382
- const candidateRaw = lines[j];
383
- const candidateTrimmed = candidateRaw.trim();
384
- if (!candidateTrimmed || candidateTrimmed.startsWith("#")) {
385
- j += 1;
386
- continue;
387
- }
388
- const indent = candidateRaw.length - candidateRaw.trimStart().length;
389
- if (indent === 0) {
390
- break;
391
- }
392
- if (!candidateTrimmed.startsWith("- ")) {
393
- issues.push(makeIssue("error", "metadata", `Unsupported metadata list line '${candidateTrimmed}'. Expected '- value'.`, relativePath, j + 1));
394
- j += 1;
395
- continue;
396
- }
397
- const itemValue = candidateTrimmed.slice(2).trim().replace(/^['"]|['"]$/g, "");
398
- items.push(itemValue);
399
- j += 1;
400
- }
401
- metadata[key] = items;
402
- i = j - 1;
403
- continue;
404
- }
405
- }
406
- }
407
- metadata[key] = coerceMetadataValue(value);
441
+ if (!parsed.hasFrontmatter && required) {
442
+ issues.push(makeIssue("error", "metadata", "Missing metadata block at top of file.", relativePath));
408
443
  }
409
- const bodyStartLine = match[0].split(/\r?\n/).length;
410
- const commentsResult = parseStegoCommentsAppendix(body, relativePath, bodyStartLine);
444
+ const bodyStartLine = frontmatterMatch
445
+ ? frontmatterMatch[0].split(/\r?\n/).length
446
+ : 1;
447
+ const commentsResult = parseStegoCommentsAppendix(parsed.body, relativePath, bodyStartLine);
411
448
  issues.push(...commentsResult.issues);
412
449
  return {
413
- metadata,
450
+ metadata: parsed.frontmatter,
414
451
  body: commentsResult.bodyWithoutComments,
415
452
  comments: commentsResult.comments,
416
453
  issues
@@ -439,32 +476,7 @@ function parseCommentIssueFromParserError(error, relativePath, bodyStartLine) {
439
476
  const absoluteLine = Number.isFinite(relativeLine) ? bodyStartLine + relativeLine - 1 : undefined;
440
477
  return makeIssue("error", "comments", lineMatch[2], relativePath, absoluteLine);
441
478
  }
442
- function coerceMetadataValue(value) {
443
- if (!value) {
444
- return "";
445
- }
446
- if ((value.startsWith("\"") && value.endsWith("\"")) || (value.startsWith("'") && value.endsWith("'"))) {
447
- return value.slice(1, -1);
448
- }
449
- if (value.startsWith("[") && value.endsWith("]")) {
450
- const inner = value.slice(1, -1).trim();
451
- if (!inner) {
452
- return [];
453
- }
454
- return inner.split(",").map((entry) => entry.trim().replace(/^['\"]|['\"]$/g, ""));
455
- }
456
- if (/^-?\d+$/.test(value)) {
457
- return Number(value);
458
- }
459
- if (value === "true") {
460
- return true;
461
- }
462
- if (value === "false") {
463
- return false;
464
- }
465
- return value;
466
- }
467
- function validateMarkdownBody(body, chapterPath, repoRoot) {
479
+ function validateMarkdownBody(body, chapterPath, repoRoot, projectRoot) {
468
480
  const relativePath = path.relative(repoRoot, chapterPath);
469
481
  const issues = [];
470
482
  const lines = body.split(/\r?\n/);
@@ -499,17 +511,20 @@ function validateMarkdownBody(body, chapterPath, repoRoot) {
499
511
  if (openFence) {
500
512
  issues.push(makeIssue("error", "structure", `Unclosed code fence opened at line ${openFence.line}.`, relativePath, openFence.line));
501
513
  }
502
- issues.push(...checkLocalMarkdownLinks(body, chapterPath, repoRoot));
514
+ issues.push(...checkLocalMarkdownLinks(body, chapterPath, repoRoot, projectRoot));
503
515
  issues.push(...runStyleHeuristics(body, relativePath));
504
516
  return issues;
505
517
  }
506
- function checkLocalMarkdownLinks(body, chapterPath, repoRoot) {
518
+ function checkLocalMarkdownLinks(body, chapterPath, repoRoot, projectRoot) {
507
519
  const relativePath = path.relative(repoRoot, chapterPath);
508
520
  const issues = [];
509
- const linkRegex = /!?\[[^\]]*\]\(([^)]+)\)/g;
521
+ const assetsDir = path.resolve(projectRoot, "assets");
522
+ const warnedOutsideAssets = new Set();
523
+ const linkRegex = /(!?)\[[^\]]*\]\(([^)]+)\)/g;
510
524
  let match = null;
511
525
  while ((match = linkRegex.exec(body)) !== null) {
512
- let target = match[1].trim();
526
+ const isImage = match[1] === "!";
527
+ let target = match[2].trim();
513
528
  if (!target) {
514
529
  continue;
515
530
  }
@@ -520,7 +535,7 @@ function checkLocalMarkdownLinks(body, chapterPath, repoRoot) {
520
535
  if (isExternalTarget(target) || target.startsWith("#")) {
521
536
  continue;
522
537
  }
523
- const cleanTarget = target.split("#")[0];
538
+ const cleanTarget = target.split("#")[0].split("?")[0];
524
539
  if (!cleanTarget) {
525
540
  continue;
526
541
  }
@@ -528,15 +543,35 @@ function checkLocalMarkdownLinks(body, chapterPath, repoRoot) {
528
543
  if (!fs.existsSync(resolved)) {
529
544
  issues.push(makeIssue("warning", "links", `Broken local link/image target '${cleanTarget}'.`, relativePath));
530
545
  }
546
+ if (!isImage) {
547
+ continue;
548
+ }
549
+ if (isPathInside(resolved, assetsDir)) {
550
+ continue;
551
+ }
552
+ const warningKey = `${relativePath}|${cleanTarget}`;
553
+ if (warnedOutsideAssets.has(warningKey)) {
554
+ continue;
555
+ }
556
+ warnedOutsideAssets.add(warningKey);
557
+ issues.push(makeIssue("warning", "assets", `Local image target '${cleanTarget}' is outside project assets/. Store manuscript images under 'assets/'.`, relativePath));
531
558
  }
532
559
  return issues;
533
560
  }
534
561
  function isExternalTarget(target) {
535
562
  return (target.startsWith("http://")
536
563
  || target.startsWith("https://")
564
+ || target.startsWith("data:")
537
565
  || target.startsWith("mailto:")
538
566
  || target.startsWith("tel:"));
539
567
  }
568
+ function isPathInside(candidatePath, parentPath) {
569
+ const relative = path.relative(path.resolve(parentPath), path.resolve(candidatePath));
570
+ if (!relative) {
571
+ return true;
572
+ }
573
+ return !relative.startsWith("..") && !path.isAbsolute(relative);
574
+ }
540
575
  function runStyleHeuristics(body, relativePath) {
541
576
  const issues = [];
542
577
  const prose = body
@@ -7,9 +7,8 @@ export function registerCheckStageCommand(registry) {
7
7
  registry.register({
8
8
  name: "check-stage",
9
9
  description: "Run stage-specific quality gates",
10
- allowUnknownOptions: true,
11
10
  options: [
12
- { flags: "--project <project-id>", description: "Project id" },
11
+ { flags: "-p, --project <project-id>", description: "Project id" },
13
12
  { flags: "--stage <stage>", description: "draft|revise|line-edit|proof|final" },
14
13
  { flags: "--file <path>", description: "Project-relative manuscript path" },
15
14
  { flags: "--root <path>", description: "Workspace root path" }
@@ -7,9 +7,8 @@ export function registerLintCommand(registry) {
7
7
  registry.register({
8
8
  name: "lint",
9
9
  description: "Run markdown and spelling checks",
10
- allowUnknownOptions: true,
11
10
  options: [
12
- { flags: "--project <project-id>", description: "Project id" },
11
+ { flags: "-p, --project <project-id>", description: "Project id" },
13
12
  { flags: "--manuscript", description: "Lint manuscript files only" },
14
13
  { flags: "--spine", description: "Lint spine/notes files only" },
15
14
  { flags: "--root <path>", description: "Workspace root path" }
@@ -6,9 +6,8 @@ export function registerValidateCommand(registry) {
6
6
  registry.register({
7
7
  name: "validate",
8
8
  description: "Validate manuscript and project state",
9
- allowUnknownOptions: true,
10
9
  options: [
11
- { flags: "--project <project-id>", description: "Project id" },
10
+ { flags: "-p, --project <project-id>", description: "Project id" },
12
11
  { flags: "--file <path>", description: "Project-relative manuscript path" },
13
12
  { flags: "--root <path>", description: "Workspace root path" }
14
13
  ],
@@ -24,8 +24,8 @@ export function registerInitCommand(registry) {
24
24
  writeText("Next steps:");
25
25
  writeText(" npm install");
26
26
  writeText(" stego list-projects");
27
- writeText(" stego validate --project fiction-example");
28
- writeText(" stego build --project fiction-example");
27
+ writeText(" stego validate -p fiction-example");
28
+ writeText(" stego build -p fiction-example");
29
29
  }
30
30
  });
31
31
  }
@@ -17,6 +17,7 @@ This directory is a Stego writing workspace (a monorepo for one or more writing
17
17
 
18
18
  - \`stego.config.json\` workspace configuration
19
19
  - \`projects/\` demo projects (\`stego-docs\` and \`fiction-example\`)
20
+ - per-project \`assets/\` directories for manuscript images
20
21
  - root \`package.json\` scripts for Stego commands
21
22
  - root \`.vscode/tasks.json\` tasks for common workflows
22
23
 
@@ -32,11 +33,11 @@ stego list-projects
32
33
  ## Run commands for a specific project (from workspace root)
33
34
 
34
35
  \`\`\`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
36
+ stego validate -p fiction-example
37
+ stego build -p fiction-example
38
+ stego check-stage -p fiction-example --stage revise
39
+ stego export -p fiction-example --format md
40
+ stego new -p fiction-example
40
41
  \`\`\`
41
42
 
42
43
  ## Work inside one project
@@ -58,13 +59,13 @@ This keeps your editor context focused and applies the project's recommended ext
58
59
  ## Create a new project
59
60
 
60
61
  \`\`\`bash
61
- stego new-project --project my-book --title "My Book"
62
+ stego new-project -p my-book --title "My Book"
62
63
  \`\`\`
63
64
 
64
65
  ## Add a new manuscript file
65
66
 
66
67
  \`\`\`bash
67
- stego new --project fiction-example
68
+ stego new -p fiction-example
68
69
  \`\`\`
69
70
  `;
70
71
  export const SCAFFOLD_AGENTS_CONTENT = `# AGENTS.md
@@ -90,7 +91,7 @@ This workspace is designed to be AI-friendly for writing workflows.
90
91
 
91
92
  1. Confirm workspace root contains \`stego.config.json\`.
92
93
  2. Run \`stego list-projects\`.
93
- 3. Use explicit \`--project <id>\` for project-scoped commands.
94
+ 3. Use explicit \`--project/-p <id>\` for project-scoped commands.
94
95
 
95
96
  ## CLI-First Policy (Required)
96
97
 
@@ -123,7 +124,7 @@ Preferred commands include:
123
124
 
124
125
  1. Read current state first (\`metadata read\`, \`spine read\`, \`comments read\`).
125
126
  2. Mutate via CLI commands.
126
- 3. Verify after writes (\`stego validate --project <id>\` and relevant read commands).
127
+ 3. Verify after writes (\`stego validate --project/-p <id>\` and relevant read commands).
127
128
 
128
129
  ## Manual Edit Fallback
129
130
 
@@ -146,7 +147,7 @@ When CLI fails:
146
147
 
147
148
  ## Validation Expectations
148
149
 
149
- After mutations, run relevant checks when feasible (for example \`stego validate --project <id>\`) and report results.
150
+ After mutations, run relevant checks when feasible (for example \`stego validate --project/-p <id>\`) and report results.
150
151
 
151
152
  ## Scope Guardrails
152
153
 
@@ -155,10 +156,10 @@ After mutations, run relevant checks when feasible (for example \`stego validate
155
156
 
156
157
  ## Task To Command Quick Map
157
158
 
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>]\`
159
+ - New manuscript: \`stego new --project/-p <id> [--filename <name>]\`
160
+ - Read spine: \`stego spine read --project/-p <id> --format json\`
161
+ - New spine category: \`stego spine new-category --project/-p <id> --key <category>\`
162
+ - New spine entry: \`stego spine new --project/-p <id> --category <category> [--filename <path>]\`
162
163
  - Read metadata: \`stego metadata read <markdown-path> --format json\`
163
164
  - Apply metadata: \`stego metadata apply <markdown-path> --input <path|-> --format json\`
164
165
  - Read comments: \`stego comments read <manuscript> --format json\`
@@ -4,7 +4,7 @@ export function registerSpineDeprecatedAliases(registry) {
4
4
  description: "Deprecated alias for spine new-category",
5
5
  allowUnknownOptions: true,
6
6
  options: [
7
- { flags: "--project <project-id>", description: "Project id" },
7
+ { flags: "-p, --project <project-id>", description: "Project id" },
8
8
  { flags: "--key <category>", description: "Category key" },
9
9
  { flags: "--label <label>", description: "Category display label" },
10
10
  { flags: "--require-metadata", description: "Append key to required metadata" },
@@ -20,7 +20,7 @@ export function registerSpineDeprecatedAliases(registry) {
20
20
  description: "Deprecated alias for spine new",
21
21
  allowUnknownOptions: true,
22
22
  options: [
23
- { flags: "--project <project-id>", description: "Project id" },
23
+ { flags: "-p, --project <project-id>", description: "Project id" },
24
24
  { flags: "--category <category>", description: "Category key" },
25
25
  { flags: "--filename <path>", description: "Relative entry path" },
26
26
  { flags: "--format <format>", description: "text|json" },
@@ -8,9 +8,8 @@ export function registerSpineNewCategoryCommand(registry) {
8
8
  registry.register({
9
9
  name: "spine new-category",
10
10
  description: "Create a new spine category",
11
- allowUnknownOptions: true,
12
11
  options: [
13
- { flags: "--project <project-id>", description: "Project id" },
12
+ { flags: "-p, --project <project-id>", description: "Project id" },
14
13
  { flags: "--key <category>", description: "Category key" },
15
14
  { flags: "--label <label>", description: "Category display label" },
16
15
  { flags: "--require-metadata", description: "Append key to required metadata" },
@@ -8,9 +8,8 @@ export function registerSpineNewEntryCommand(registry) {
8
8
  registry.register({
9
9
  name: "spine new",
10
10
  description: "Create a new spine entry",
11
- allowUnknownOptions: true,
12
11
  options: [
13
- { flags: "--project <project-id>", description: "Project id" },
12
+ { flags: "-p, --project <project-id>", description: "Project id" },
14
13
  { flags: "--category <category>", description: "Category key" },
15
14
  { flags: "--filename <path>", description: "Relative entry path" },
16
15
  { flags: "--format <format>", description: "text|json" },
@@ -7,9 +7,8 @@ export function registerSpineReadCommand(registry) {
7
7
  registry.register({
8
8
  name: "spine read",
9
9
  description: "Read spine catalog",
10
- allowUnknownOptions: true,
11
10
  options: [
12
- { flags: "--project <project-id>", description: "Project id" },
11
+ { flags: "-p, --project <project-id>", description: "Project id" },
13
12
  { flags: "--format <format>", description: "text|json" },
14
13
  { flags: "--root <path>", description: "Workspace root path" }
15
14
  ],
@@ -0,0 +1,23 @@
1
+ img[data-layout="inline"] {
2
+ display: inline;
3
+ vertical-align: baseline;
4
+ }
5
+
6
+ img[data-layout="block"] {
7
+ display: block;
8
+ }
9
+
10
+ img[data-layout="block"][data-align="left"] {
11
+ margin-left: 0;
12
+ margin-right: auto;
13
+ }
14
+
15
+ img[data-layout="block"][data-align="center"] {
16
+ margin-left: auto;
17
+ margin-right: auto;
18
+ }
19
+
20
+ img[data-layout="block"][data-align="right"] {
21
+ margin-left: auto;
22
+ margin-right: 0;
23
+ }