scriveno 2.7.1 → 2.8.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.
@@ -1,6 +1,6 @@
1
1
  # Command Reference
2
2
 
3
- Scriveno has **113 commands** organized into **14 categories**. Commands adapt automatically to your work type -- for example, `/scr:draft` talks about drafting a surah for Quranic commentary, an act for screenplays, and a section for research papers.
3
+ Scriveno has **115 commands** organized into **14 categories**. Commands adapt automatically to your work type -- for example, `/scr:draft` talks about drafting a surah for Quranic commentary, an act for screenplays, and a section for research papers.
4
4
 
5
5
  Commands marked with **adaptive terminology** change how Scriveno talks about your work type's `command_unit` in `.manuscript/config.json`, while keeping the runnable command id stable. Commands marked with **group adaptation** have different labels for specific work type groups (academic, sacred, etc.).
6
6
 
@@ -146,6 +146,26 @@ Finalize Chapter 3 after editor review. Marks it as complete in the workflow.
146
146
 
147
147
  ---
148
148
 
149
+ ### `/scr:proof-unit`
150
+
151
+ **Description:** Run one manuscript unit through a proof path from voice profile to export readiness.
152
+
153
+ **Usage:** `/scr:proof-unit [unit] [--demo] [--export-check]`
154
+
155
+ **Prerequisites:** STYLE-GUIDE.md should exist. If it is missing, run `/scr:profile-writer` or `/scr:voice-test` first.
156
+
157
+ **Flags:**
158
+ - `--demo` -- Use the demo proof path when available
159
+ - `--export-check` -- Run `/scr:export --check` after review to verify external tools
160
+
161
+ **Example:**
162
+ ```
163
+ /scr:proof-unit 3 --export-check
164
+ ```
165
+ Prove one chapter through discuss, plan, draft, review, context health, and export-tool readiness without silently running a full publishing pipeline.
166
+
167
+ ---
168
+
149
169
  ### `/scr:complete-draft`
150
170
 
151
171
  **Description:** Mark the entire manuscript draft as complete.
@@ -1336,7 +1356,7 @@ Generate 10-15 book club questions that spark real conversation about your theme
1336
1356
 
1337
1357
  **Description:** Publishing wizard or preset-driven pipeline. Chains export commands based on destination.
1338
1358
 
1339
- **Usage:** `/scr:publish [--preset <preset>] [--all] [--skip-validate]`
1359
+ **Usage:** `/scr:publish [--preset <preset>] [--all] [--skip-validate] [--preflight]`
1340
1360
 
1341
1361
  **Prerequisites:** None (wraps export commands)
1342
1362
 
@@ -1348,6 +1368,7 @@ Generate 10-15 book club questions that spark real conversation about your theme
1348
1368
  - **Academic / archival:** `academic-submission`, `thesis-defense`, `all-formats`
1349
1369
  - `--all` -- Run all applicable presets
1350
1370
  - `--skip-validate` -- Skip the scaffold-marker validation gate (not recommended)
1371
+ - `--preflight` -- Check publishing readiness and external tools, then stop before writing deliverables
1351
1372
  - No flags -- Run the interactive wizard, which asks the writer-facing question "What are you doing?" (Share / Publish / Submit / Academic / Screenplay / Everything / Custom) and drills into the matching branch.
1352
1373
 
1353
1374
  **Available for:** All work types
@@ -1364,7 +1385,7 @@ Run the full KDP paperback publishing pipeline: prepare the interior package, ge
1364
1385
 
1365
1386
  **Description:** Compile and export manuscript to publication-ready formats.
1366
1387
 
1367
- **Usage:** `/scr:export [--format <format>] [--formatted] [--print-ready] [--skip-validate]`
1388
+ **Usage:** `/scr:export [--format <format>] [--formatted] [--print-ready] [--skip-validate] [--check]`
1368
1389
 
1369
1390
  **Prerequisites:** Complete draft must exist
1370
1391
 
@@ -1373,6 +1394,7 @@ Run the full KDP paperback publishing pipeline: prepare the interior package, ge
1373
1394
  - `--formatted` -- Use designed/formatted template (vs. manuscript format)
1374
1395
  - `--print-ready` -- Generate the interior print PDF surface used by print-package flows
1375
1396
  - `--skip-validate` -- Skip the scaffold-marker validation gate (not recommended)
1397
+ - `--check` -- Verify format availability and required external tools without assembling or writing export files
1376
1398
 
1377
1399
  **Available for:** All work types (format availability varies by work type)
1378
1400
 
@@ -1932,12 +1954,13 @@ See all your writing projects with status, word count, and last activity.
1932
1954
 
1933
1955
  **Description:** Diagnose and repair common project state issues.
1934
1956
 
1935
- **Usage:** `/scr:health [--repair]`
1957
+ **Usage:** `/scr:health [--repair] [--context]`
1936
1958
 
1937
1959
  **Prerequisites:** None
1938
1960
 
1939
1961
  **Flags:**
1940
1962
  - `--repair` -- Attempt to fix detected issues
1963
+ - `--context` -- Estimate loaded-context size and flag watch, tight, or critical sessions
1941
1964
 
1942
1965
  **Available for:** All work types
1943
1966
 
@@ -1949,6 +1972,30 @@ Check for missing files, broken references, and state inconsistencies, then fix
1949
1972
 
1950
1973
  ---
1951
1974
 
1975
+ ### `/scr:surface`
1976
+
1977
+ **Description:** Inspect or change the installed Scriveno command profile.
1978
+
1979
+ **Usage:** `/scr:surface [list|status|profile <name>] [--runtime <runtime>] [--dry-run]`
1980
+
1981
+ **Prerequisites:** Node.js >=20.0.0 and a Scriveno package or repo checkout with `bin/install.js`
1982
+
1983
+ **Flags:**
1984
+ - `list` -- Show available profiles and command counts
1985
+ - `status` -- Show the currently installed profile and runtime surface
1986
+ - `profile <name>` -- Reinstall a named profile: core, writing, publishing, translation, specialist, or full
1987
+ - `--dry-run` -- Show the planned install changes without writing files
1988
+
1989
+ **Available for:** All work types
1990
+
1991
+ **Example:**
1992
+ ```
1993
+ /scr:surface profile writing --dry-run
1994
+ ```
1995
+ Preview a smaller drafting-focused command surface before changing installed runtime files.
1996
+
1997
+ ---
1998
+
1952
1999
  ### `/scr:scan`
1953
2000
 
1954
2001
  **Description:** Detect drift between recorded state (STATE.md, OUTLINE.md, config.json) and what is actually on disk.
@@ -57,7 +57,7 @@ When a writer runs `/scr:new-work`, Scriveno creates `.manuscript/config.json`.
57
57
 
58
58
  ```json
59
59
  {
60
- "scriveno_version": "2.7.1",
60
+ "scriveno_version": "2.8.0",
61
61
  "work_type": "<chosen>",
62
62
  "group": "<group>",
63
63
  "command_unit": "<unit>",
@@ -159,7 +159,7 @@ For release-oriented documentation surfaces, the main files are:
159
159
  - `docs/runtime-support.md`
160
160
  - `docs/route-graph.md`
161
161
  - `templates/*/README.md` when shipped profiles or templates change
162
- - `.planning/` milestone summaries when you are still using the GSD planning layer
162
+ - `.planning/` milestone summaries when you are still using an external planning layer
163
163
 
164
164
  ## Before shipping
165
165
 
@@ -2,11 +2,37 @@
2
2
 
3
3
  This document is the public-facing summary of what changed between package releases. For package history, see the root [CHANGELOG](../CHANGELOG.md).
4
4
 
5
+ ## 2.8.0 - 2026-05-30
6
+
7
+ ### What changed
8
+
9
+ - Scriveno can now install focused command profiles: `core`, `writing`, `publishing`, `translation`, `specialist`, or `full`. The new `/scr:surface` command and `scriveno surface` CLI helpers let writers inspect, dry-run, and switch profiles.
10
+ - Added `/scr:proof-unit`, a one-unit proof path that checks voice context, plans, drafts, reviews, context health, and optional export-tool readiness.
11
+ - Added context-health estimation to `scriveno status`, `/scr:next`, and `/scr:health --context`, with watch, tight, and critical thresholds.
12
+ - Added `/scr:export --check` and `/scr:publish --preflight` so tool readiness and publishing prerequisites can be verified before writing deliverables.
13
+ - Updated the public command inventory to 115 commands and added regression tests for profiles, dry-run installs, context health, proof-unit, and preflight surfaces.
14
+
15
+ ### Why it matters
16
+
17
+ Writers can keep Scriveno small when they only need the active workflow, prove the product on one real unit before trusting a larger run, and check export or publishing readiness before generating packages.
18
+
19
+ ## 2.7.2 - 2026-05-30
20
+
21
+ ### What changed
22
+
23
+ - Progress ledger correctness: reviewed units with open editor notes now stay in progress instead of being counted as done. Submitted units still count as done, and clean reviews count as done when the workflow stops at review.
24
+ - `/scr:scan` now points its plan and review counts at the canonical `.manuscript/plans/` and `.manuscript/reviews/` directories, with legacy root-level fallbacks.
25
+ - The 2.7.1 release text now matches the actual `scriveno status` output.
26
+
27
+ ### Why it matters
28
+
29
+ The progress bar now reflects the writer's real state of work: review notes still waiting on revision no longer look finished.
30
+
5
31
  ## 2.7.1 - 2026-05-30
6
32
 
7
33
  ### What changed
8
34
 
9
- - `scriveno status` now prints the progress ledger directly (a `Progress:` line with bar, done / in progress / untouched, and pipeline position), so the deliverable view is visible from the bundled CLI, not only to runtimes that load the engine module.
35
+ - `scriveno status` now prints the progress ledger directly (a `Progress:` line with the bar and done / in progress / untouched counts), so the deliverable view is visible from the bundled CLI, not only to runtimes that load the engine module.
10
36
  - Documentation-integrity pass: corrected the context-integrity layer description (now five files), added `PROGRESS.md` to the scan trust-file lists and the architecture template tree, and refreshed stale version and count references across the docs.
11
37
 
12
38
  ### Why it matters
@@ -64,7 +90,7 @@ This release closes the gap between what Scriveno documented and what it shipped
64
90
  - The public CLI now supports `scriveno first-run --project .`, giving terminal users the same proof path without relying on host-specific slash-command behavior.
65
91
  - First-run guidance is connected to `/scr:help`, `/scr:demo`, Quick Proof, Starter Sets, Runtime Support, Shipped Assets, Command Reference, README launch copy, and architecture docs.
66
92
  - Scriveno now includes committed first-run and runtime-parity proof bundles under `data/proof/`.
67
- - Runtime smoke now validates the 113-command installed surface across Claude Code, Codex, Cursor, Gemini CLI, OpenCode, GitHub Copilot, Windsurf, Antigravity, Manus, Perplexity Desktop, and the generic fallback.
93
+ - Runtime smoke now validates the 115-command installed surface across Claude Code, Codex, Cursor, Gemini CLI, OpenCode, GitHub Copilot, Windsurf, Antigravity, Manus, Perplexity Desktop, and the generic fallback.
68
94
  - README badges, package metadata, constraints metadata, generated config metadata, changelog, release notes, configuration docs, route graph docs, architecture docs, proof docs, and release tests are aligned on `2.5.0`.
69
95
 
70
96
  ### Why it matters
@@ -13,9 +13,9 @@ The text report summarizes command count, graph edges, agent-capable routes, loc
13
13
 
14
14
  ## Current Shape
15
15
 
16
- As of `2.7.1`, the route graph contains:
16
+ As of `2.8.0`, the route graph contains:
17
17
 
18
- - 113 commands
18
+ - 115 commands
19
19
  - intent-order edges from `command_intents`
20
20
  - dependency-chain edges from `dependencies.core_chain`
21
21
  - automation lanes from `getCommandAutomationPolicy()`
@@ -292,5 +292,5 @@ Here is a quick walkthrough for starting a sacred writing project:
292
292
  ## See Also
293
293
 
294
294
  - [Getting Started](getting-started.md) -- Install Scriveno and create your first project
295
- - [Command Reference](command-reference.md) -- Full reference for all 113 commands, including the [Sacred Exclusive](command-reference.md#sacred-exclusive) section
295
+ - [Command Reference](command-reference.md) -- Full reference for all 115 commands, including the [Sacred Exclusive](command-reference.md#sacred-exclusive) section
296
296
  - [README](../README.md) -- Project overview and feature list
package/docs/voice-dna.md CHANGED
@@ -293,5 +293,5 @@ See [docs/drafter-quality.md](drafter-quality.md) for the full system, including
293
293
  - [Proof Artifacts](proof-artifacts.md) -- inspect the Voice DNA before/after bundle first if you want the fastest concrete evidence
294
294
  - [Getting Started](getting-started.md) -- Install Scriveno and write your first draft
295
295
  - [Drafter Quality](drafter-quality.md) -- the three rule layers, the `draft` config block, and model-tier recommendations
296
- - [Command Reference](command-reference.md) -- Full list of all 113 commands with usage and examples
296
+ - [Command Reference](command-reference.md) -- Full list of all 115 commands with usage and examples
297
297
  - [Work Types Guide](work-types.md) -- How work types adapt Scriveno's vocabulary and commands
@@ -335,5 +335,5 @@ Your work type is stored in `.manuscript/config.json` and can be changed later b
335
335
  ## See Also
336
336
 
337
337
  - [Getting Started](getting-started.md) -- Install Scriveno and write your first draft
338
- - [Command Reference](command-reference.md) -- Full list of all 113 commands with usage and examples
338
+ - [Command Reference](command-reference.md) -- Full list of all 115 commands with usage and examples
339
339
  - [Voice DNA Guide](voice-dna.md) -- How Scriveno profiles and preserves your writing voice
@@ -91,6 +91,7 @@ const REVIEW_KEYWORDS = [
91
91
  'VOICE DRIFT',
92
92
  'CONTINUITY',
93
93
  ];
94
+ const OPEN_REVIEW_KEYWORDS = REVIEW_KEYWORDS.filter((keyword) => keyword !== 'CONTINUITY');
94
95
 
95
96
  const CORE_PROJECT_FILES = [
96
97
  'WORK.md',
@@ -580,6 +581,72 @@ function detectContextSignal(manuscriptDir, draftFiles) {
580
581
  return { state: 'fresh', suggest: null };
581
582
  }
582
583
 
584
+ const CONTEXT_HEALTH_LIMITS = {
585
+ watch: 45000,
586
+ tight: 80000,
587
+ critical: 120000,
588
+ };
589
+
590
+ function estimateTextTokens(byteCount) {
591
+ return Math.ceil(byteCount / 4);
592
+ }
593
+
594
+ function fileByteSize(filePath) {
595
+ const stat = safeStat(filePath);
596
+ return stat && stat.isFile() ? stat.size : 0;
597
+ }
598
+
599
+ function newestFiles(files, limit) {
600
+ return files
601
+ .map((file) => ({ file, mtime: safeStat(file)?.mtimeMs || 0 }))
602
+ .sort((a, b) => b.mtime - a.mtime)
603
+ .slice(0, limit)
604
+ .map((entry) => entry.file);
605
+ }
606
+
607
+ function detectContextHealth(manuscriptDir, draftFiles) {
608
+ const coreFiles = [
609
+ 'STYLE-GUIDE.md',
610
+ 'CONTEXT.md',
611
+ 'STATE.md',
612
+ 'OUTLINE.md',
613
+ 'RECORD.md',
614
+ 'WORK.md',
615
+ 'config.json',
616
+ ].map((name) => path.join(manuscriptDir, name));
617
+ const reviewFiles = newestFiles(listFiles(path.join(manuscriptDir, 'reviews'), { extensions: ['.md', '.txt'], recursive: true }), 3);
618
+ const planFiles = newestFiles(listFiles(path.join(manuscriptDir, 'plans'), { extensions: ['.md'], recursive: true }), 3);
619
+ const recentDrafts = newestFiles(draftFiles, 5);
620
+ const uniqueFiles = [...new Set([...coreFiles, ...planFiles, ...recentDrafts, ...reviewFiles])].filter(pathExists);
621
+ const files = uniqueFiles.map((file) => ({
622
+ file: path.relative(manuscriptDir, file),
623
+ bytes: fileByteSize(file),
624
+ }));
625
+ const estimatedBytes = files.reduce((sum, file) => sum + file.bytes, 0);
626
+ const estimatedTokens = estimateTextTokens(estimatedBytes);
627
+ let state = 'ok';
628
+ let suggest = null;
629
+ if (estimatedTokens >= CONTEXT_HEALTH_LIMITS.critical) {
630
+ state = 'critical';
631
+ suggest = '/scr:thread';
632
+ } else if (estimatedTokens >= CONTEXT_HEALTH_LIMITS.tight) {
633
+ state = 'tight';
634
+ suggest = '/scr:save';
635
+ } else if (estimatedTokens >= CONTEXT_HEALTH_LIMITS.watch) {
636
+ state = 'watch';
637
+ suggest = '/scr:health --context';
638
+ }
639
+ return {
640
+ state,
641
+ estimatedTokens,
642
+ estimatedBytes,
643
+ fileCount: files.length,
644
+ limits: { ...CONTEXT_HEALTH_LIMITS },
645
+ largestFiles: files.sort((a, b) => b.bytes - a.bytes).slice(0, 5),
646
+ suggest,
647
+ };
648
+ }
649
+
583
650
  function detectExportSignal(manuscriptDir, sourceFiles) {
584
651
  const newestSource = newestMtime(sourceFiles);
585
652
  const newestOutput = findNewestOutput(manuscriptDir);
@@ -756,6 +823,12 @@ function buildAutomationPlan(signals, recommendation) {
756
823
  if (signals.context?.state === 'stale') {
757
824
  localCandidates.push({ command: signals.context.suggest || '/scr:scan', reason: 'refresh stale context before chaining work' });
758
825
  }
826
+ if (signals.contextHealth?.state === 'tight' || signals.contextHealth?.state === 'critical') {
827
+ localCandidates.push({
828
+ command: signals.contextHealth.suggest || '/scr:health --context',
829
+ reason: `loaded context estimate is ${signals.contextHealth.state}`,
830
+ });
831
+ }
759
832
  if (signals.notes?.count > 0) {
760
833
  localCandidates.push({ command: '/scr:check-notes', reason: 'surface unresolved notes before the next writing route' });
761
834
  }
@@ -800,6 +873,7 @@ function analyzeProject(projectRoot = process.cwd(), options = {}) {
800
873
  hasProject: false,
801
874
  hasState: false,
802
875
  context: { state: 'none', suggest: null },
876
+ contextHealth: { state: 'none', estimatedTokens: 0, estimatedBytes: 0, fileCount: 0, largestFiles: [], suggest: null },
803
877
  history: { state: 'none', lastFailed: false },
804
878
  reviews: { state: 'none', count: 0, files: [] },
805
879
  reviewCoverage: { state: 'none', suggest: null },
@@ -843,6 +917,7 @@ function analyzeProject(projectRoot = process.cwd(), options = {}) {
843
917
  hasProject: true,
844
918
  hasState: pathExists(statePath),
845
919
  context: detectContextSignal(manuscriptDir, draftFiles),
920
+ contextHealth: detectContextHealth(manuscriptDir, draftFiles),
846
921
  history: historySignal,
847
922
  readiness: detectProjectReadiness(manuscriptDir),
848
923
  plan: detectPlanSignal(manuscriptDir, draftFiles),
@@ -894,6 +969,7 @@ function formatProactiveChecks(analysis) {
894
969
  progressLine,
895
970
  ` Readiness: ${signals.readiness?.state || 'none'}${signals.readiness?.missing?.length ? `, missing ${signals.readiness.missing.join(', ')}` : ''}`,
896
971
  ` Session: ${signals.context.state}${signals.context.suggest ? `, suggest ${signals.context.suggest}` : ''}`,
972
+ ` Context health: ${signals.contextHealth?.state || 'none'}${signals.contextHealth?.estimatedTokens ? `, about ${signals.contextHealth.estimatedTokens} tokens` : ''}${signals.contextHealth?.suggest ? `, suggest ${signals.contextHealth.suggest}` : ''}`,
897
973
  ` Plans: ${signals.plan?.state || 'none'}${signals.plan?.suggest ? `, suggest ${signals.plan.suggest}` : ''}`,
898
974
  ` Reviews: ${signals.reviews.count ? `${signals.reviews.count} pending, suggest /scr:editor-review` : 'none'}`,
899
975
  ` Review coverage: ${signals.reviewCoverage?.state || 'none'}${signals.reviewCoverage?.suggest ? `, suggest ${signals.reviewCoverage.suggest}` : ''}`,
@@ -1404,6 +1480,30 @@ function ledgerUnitNumbers(dir, suffixRegex) {
1404
1480
  return found;
1405
1481
  }
1406
1482
 
1483
+ function ledgerUnitFileMap(dir, suffixRegex) {
1484
+ const found = new Map();
1485
+ let entries;
1486
+ try {
1487
+ entries = fs.readdirSync(dir);
1488
+ } catch (err) {
1489
+ return found;
1490
+ }
1491
+ for (const name of entries) {
1492
+ const match = name.match(suffixRegex);
1493
+ if (match) {
1494
+ found.set(parseInt(match[1], 10), path.join(dir, name));
1495
+ }
1496
+ }
1497
+ return found;
1498
+ }
1499
+
1500
+ function mergeUnitFileMaps(target, source) {
1501
+ for (const [unit, file] of source.entries()) {
1502
+ if (!target.has(unit)) target.set(unit, file);
1503
+ }
1504
+ return target;
1505
+ }
1506
+
1407
1507
  function ledgerOutlineUnitCount(manuscriptDir) {
1408
1508
  let text;
1409
1509
  try {
@@ -1426,6 +1526,39 @@ function ledgerBar(done, total, width) {
1426
1526
  return '█'.repeat(clamped) + '░'.repeat(cells - clamped);
1427
1527
  }
1428
1528
 
1529
+ function ledgerReferencedUnits(text) {
1530
+ const units = new Set();
1531
+ const pattern = /\b(?:unit|chapter|scene|section|act|part|surah|procedure|poem)\s*#?\s*(\d+)\b/gi;
1532
+ let match;
1533
+ while ((match = pattern.exec(text)) !== null) {
1534
+ const unit = parseInt(match[1], 10);
1535
+ if (unit > 0) units.add(unit);
1536
+ }
1537
+ return units;
1538
+ }
1539
+
1540
+ function stateOpenRevisionUnits(manuscriptDir) {
1541
+ const text = readText(path.join(manuscriptDir, 'STATE.md'));
1542
+ const open = new Set();
1543
+ for (const line of text.split(/\r?\n/)) {
1544
+ if (!/(open revisions|unresolved review|editor notes.*awaiting revision|awaiting revision|revisions pending)/i.test(line)) {
1545
+ continue;
1546
+ }
1547
+ for (const unit of ledgerReferencedUnits(line)) open.add(unit);
1548
+ }
1549
+ return open;
1550
+ }
1551
+
1552
+ function stateSubmittedUnits(manuscriptDir) {
1553
+ const text = readText(path.join(manuscriptDir, 'STATE.md'));
1554
+ const submitted = new Set();
1555
+ for (const line of text.split(/\r?\n/)) {
1556
+ if (!/\b(submit|submitted)\b/i.test(line)) continue;
1557
+ for (const unit of ledgerReferencedUnits(line)) submitted.add(unit);
1558
+ }
1559
+ return submitted;
1560
+ }
1561
+
1429
1562
  // Returns deliverable progress for a project's .manuscript directory: total
1430
1563
  // units, per-stage counts, the done / in-progress / untouched buckets, a
1431
1564
  // percent, a rendered bar, and the unit-number sets. Derived purely from disk
@@ -1439,17 +1572,30 @@ function computeProgressLedger(manuscriptDir) {
1439
1572
  for (const unit of ledgerUnitNumbers(manuscriptDir, /^(\d+)\D.*PLAN\.md$/i)) {
1440
1573
  planned.add(unit);
1441
1574
  }
1442
- const reviewed = ledgerUnitNumbers(path.join(manuscriptDir, 'reviews'), /^(\d+)\D.*REVIEW\.md$/i);
1443
- for (const unit of ledgerUnitNumbers(manuscriptDir, /^(\d+)\D.*EDITOR-NOTES\.md$/i)) {
1444
- reviewed.add(unit);
1575
+ const reviewFiles = ledgerUnitFileMap(path.join(manuscriptDir, 'reviews'), /^(\d+)\D.*REVIEW\.md$/i);
1576
+ mergeUnitFileMaps(reviewFiles, ledgerUnitFileMap(manuscriptDir, /^(\d+)\D.*EDITOR-NOTES\.md$/i));
1577
+ const reviewed = new Set(reviewFiles.keys());
1578
+ const submitted = stateSubmittedUnits(manuscriptDir);
1579
+ const openReviews = stateOpenRevisionUnits(manuscriptDir);
1580
+ for (const [unit, file] of reviewFiles.entries()) {
1581
+ if (containsAny(readText(file), OPEN_REVIEW_KEYWORDS)) {
1582
+ openReviews.add(unit);
1583
+ }
1584
+ }
1585
+ for (const unit of submitted) {
1586
+ openReviews.delete(unit);
1587
+ }
1588
+ const doneUnits = new Set(submitted);
1589
+ for (const unit of reviewed) {
1590
+ if (!openReviews.has(unit)) doneUnits.add(unit);
1445
1591
  }
1446
1592
 
1447
- const worked = new Set([...planned, ...drafted, ...reviewed]);
1593
+ const worked = new Set([...planned, ...drafted, ...reviewed, ...submitted]);
1448
1594
  const maxWorked = worked.size ? Math.max(...worked) : 0;
1449
1595
  const total = Math.max(ledgerOutlineUnitCount(manuscriptDir), worked.size, maxWorked);
1450
1596
 
1451
- const done = reviewed.size;
1452
- const inProgress = [...worked].filter((unit) => !reviewed.has(unit)).length;
1597
+ const done = doneUnits.size;
1598
+ const inProgress = [...worked].filter((unit) => !doneUnits.has(unit)).length;
1453
1599
  const untouched = Math.max(0, total - worked.size);
1454
1600
  const percent = total > 0 ? Math.round((done / total) * 100) : 0;
1455
1601
 
@@ -1458,6 +1604,7 @@ function computeProgressLedger(manuscriptDir) {
1458
1604
  drafted: drafted.size,
1459
1605
  planned: planned.size,
1460
1606
  reviewed: reviewed.size,
1607
+ submitted: submitted.size,
1461
1608
  done,
1462
1609
  inProgress,
1463
1610
  untouched,
@@ -1467,6 +1614,9 @@ function computeProgressLedger(manuscriptDir) {
1467
1614
  drafted: [...drafted].sort((a, b) => a - b),
1468
1615
  planned: [...planned].sort((a, b) => a - b),
1469
1616
  reviewed: [...reviewed].sort((a, b) => a - b),
1617
+ submitted: [...submitted].sort((a, b) => a - b),
1618
+ openReviews: [...openReviews].sort((a, b) => a - b),
1619
+ done: [...doneUnits].sort((a, b) => a - b),
1470
1620
  },
1471
1621
  };
1472
1622
  }
@@ -1499,4 +1649,6 @@ module.exports = {
1499
1649
  listRuntimeAgentSupport,
1500
1650
  parseCliArgs,
1501
1651
  computeProgressLedger,
1652
+ detectContextHealth,
1653
+ estimateTextTokens,
1502
1654
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scriveno",
3
- "version": "2.7.1",
3
+ "version": "2.8.0",
4
4
  "description": "Spec-driven creative writing, publishing, and translation pipeline for AI coding agents. From blank page to published book.",
5
5
  "bin": {
6
6
  "scriveno": "bin/install.js"
@@ -1,5 +1,5 @@
1
1
  {
2
- "scriveno_version": "2.7.1",
2
+ "scriveno_version": "2.8.0",
3
3
  "work_type": "",
4
4
  "group": "",
5
5
  "command_unit": "",