scriveno 2.0.7 → 2.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,9 +2,10 @@
2
2
 
3
3
  [![CI](https://github.com/aihxp/scriveno/actions/workflows/ci.yml/badge.svg)](https://github.com/aihxp/scriveno/actions/workflows/ci.yml)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
5
- [![Version](https://img.shields.io/badge/version-2.0.7-blue)](CHANGELOG.md)
5
+ [![Version](https://img.shields.io/badge/version-2.0.8-blue)](CHANGELOG.md)
6
6
  [![npm](https://img.shields.io/npm/v/scriveno.svg)](https://www.npmjs.com/package/scriveno)
7
7
  [![Downloads](https://img.shields.io/npm/dm/scriveno.svg)](https://www.npmjs.com/package/scriveno)
8
+ [![Status CLI](https://img.shields.io/badge/status%20CLI-scriveno%20status-blue)](docs/runtime-support.md#shared-auto-invoke-engine)
8
9
 
9
10
  **[scriveno on npm](https://www.npmjs.com/package/scriveno)**
10
11
 
@@ -18,6 +19,9 @@ Scriveno is best understood as **AI-native longform writing software built aroun
18
19
 
19
20
  ```bash
20
21
  npx scriveno@latest
22
+
23
+ # Optional project status check
24
+ scriveno status --project .
21
25
  ```
22
26
 
23
27
  ---
@@ -68,6 +72,19 @@ If you want the shortest proof-first route, read [Proof Artifacts](docs/proof-ar
68
72
 
69
73
  ---
70
74
 
75
+ ## Proactive status
76
+
77
+ Scriveno ships a shared read-only status engine for every installer target. The public CLI is:
78
+
79
+ ```bash
80
+ scriveno status --project .
81
+ scriveno status . --json
82
+ ```
83
+
84
+ It inspects disk evidence such as `.manuscript/`, `STATE.md`, `CONTEXT.md`, review files, translation work, exports, and history, then recommends the safest next command. The engine does not mutate files and does not spawn agents by itself. Command surfaces such as `/scr-next`, `/scr:next`, `/scr:progress`, `/scr:session-report`, and `/scr:sync` call it when local command execution is available, then fall back to embedded markdown logic when a host cannot run Node. See [Auto-Invoke Policy](docs/auto-invoke-policy.md) and [Runtime Support](docs/runtime-support.md#shared-auto-invoke-engine).
85
+
86
+ ---
87
+
71
88
  ## The Voice DNA system
72
89
 
73
90
  Scriveno's core insight: drafted prose should sound like *you*, not like an AI. Before drafting begins, `/scr:profile-writer` builds a detailed voice profile across 15+ dimensions:
@@ -171,6 +188,7 @@ Scriveno is built on five principles:
171
188
  - [Contributing](docs/contributing.md) -- How to add commands, agents, work types, and templates
172
189
  - [Architecture](docs/architecture.md) -- How Scriveno works under the hood
173
190
  - [Configuration](docs/configuration.md) -- Package, installer, constraints, and `.manuscript/config.json` surfaces
191
+ - [Auto-Invoke Policy](docs/auto-invoke-policy.md) -- Shared status engine, visible automation status, and agent-spawn boundaries
174
192
  - [Development](docs/development.md) -- Contributor workflow for changing commands, templates, installer logic, and docs
175
193
  - [Testing](docs/testing.md) -- What the test suite covers and which checks to run before shipping
176
194
  - [Release Notes](docs/release-notes.md) -- Public summary of what changed between package releases
@@ -195,7 +213,7 @@ Scriveno currently ships installer targets for these AI tooling environments:
195
213
  - **Perplexity Desktop** (guided local-MCP setup)
196
214
  - **Generic (SKILL.md)** fallback
197
215
 
198
- **Installer baseline:** `Node.js >=20.0.0` for `npx scriveno@latest` and `bin/install.js`. For new installs, use a currently supported LTS such as Node.js 24; Node.js 20 is now a compatibility floor, not the recommended fresh-install target.
216
+ **Installer baseline:** `Node.js >=20.0.0` for `npx scriveno@latest`, `bin/install.js`, and `scriveno status --project .`. For new installs, use a currently supported LTS such as Node.js 24; Node.js 20 is now a compatibility floor, not the recommended fresh-install target.
199
217
 
200
218
  **Support note:** Claude Code is the primary reference runtime and now installs a flat `/scr-*` command surface. The environments listed above are installer targets, not a claim that every host runtime has verified parity today. Codex currently installs a skill-native `$scr-*` surface, while Perplexity Desktop is a guided local-MCP target rather than a writable command runtime. See the [runtime compatibility matrix](docs/runtime-support.md) for install type, support level, and verification status.
201
219
 
@@ -203,11 +221,11 @@ Scriveno currently ships installer targets for these AI tooling environments:
203
221
 
204
222
  ## Status
205
223
 
206
- **Version:** 2.0.7
224
+ **Version:** 2.0.8
207
225
 
208
- Scriveno's core command surface is stable across 112 commands, 50 work types, and 11 installer targets. The current repo baseline includes shipped planning milestones through `v2.0 Publishing Cover Packaging`, plus the creative-context, record-store, branching-next, runtime-sync, adaptive concierge, human-first writing-safeguard, authenticity-diagnostic, domain-grilling, installer-marker cleanup, cross-runtime agent metadata, and proactive auto-invoke visibility work through `2.0.7`. See [Shipped Assets](docs/shipped-assets.md) for the canonical asset inventory and [Runtime Support](docs/runtime-support.md) for the runtime compatibility matrix.
226
+ Scriveno's core command surface is stable across 112 commands, 50 work types, and 11 installer targets. The current repo baseline includes shipped planning milestones through `v2.0 Publishing Cover Packaging`, plus the creative-context, record-store, branching-next, runtime-sync, adaptive concierge, human-first writing-safeguard, authenticity-diagnostic, domain-grilling, installer-marker cleanup, cross-runtime agent metadata, visible automation status, and the shared `scriveno status --project .` auto-invoke engine through `2.0.8`. See [Shipped Assets](docs/shipped-assets.md) for the canonical asset inventory and [Runtime Support](docs/runtime-support.md) for the runtime compatibility matrix.
209
227
 
210
- Version `2.0.7` publishes Scriveno under the package name `scriveno`, so the current install command is `npx scriveno@latest`. The older `scriveno-cli` package name is historical and was unpublished during the rename, so npm cannot attach a deprecation notice to it while it has no active registry record. The older `scriven-cli` package remains on npm only as a deprecated legacy name that points users to `scriveno`. Do not treat either legacy package name as active unless a deliberate compatibility shim is republished. See [CHANGELOG](CHANGELOG.md) for the full list and [docs/release-notes.md](docs/release-notes.md) for the public-facing summary.
228
+ Version `2.0.8` publishes Scriveno under the package name `scriveno`, so the current install command is `npx scriveno@latest`. The older `scriveno-cli` package name is historical and was unpublished during the rename, so npm cannot attach a deprecation notice to it while it has no active registry record. The older `scriven-cli` package remains on npm only as a deprecated legacy name that points users to `scriveno`. Do not treat either legacy package name as active unless a deliberate compatibility shim is republished. See [CHANGELOG](CHANGELOG.md) for the full list and [docs/release-notes.md](docs/release-notes.md) for the public-facing summary.
211
229
 
212
230
  Package history is tracked in [CHANGELOG.md](CHANGELOG.md), and the public-facing summary for this release is in [docs/release-notes.md](docs/release-notes.md).
213
231
 
package/bin/install.js CHANGED
@@ -6,6 +6,7 @@ const os = require('os');
6
6
  const readline = require('readline');
7
7
  const crypto = require('crypto');
8
8
  const architecturalProfiles = require('../lib/architectural-profiles.js');
9
+ const autoInvokeEngine = require('../lib/auto-invoke-engine.js');
9
10
 
10
11
  const PKG_ROOT = path.join(__dirname, '..');
11
12
  const PKG = require('../package.json');
@@ -820,6 +821,8 @@ function printHelp() {
820
821
  console.log(BANNER);
821
822
  console.log(`Usage:
822
823
  scriveno
824
+ scriveno status --project .
825
+ scriveno status . --json
823
826
  scriveno --runtimes codex,claude-code --global --writer --silent
824
827
 
825
828
  Options:
@@ -834,6 +837,12 @@ Options:
834
837
  --help Show this help text
835
838
  --version Show the Scriveno package version
836
839
 
840
+ Status options:
841
+ status Inspect a project and recommend the next command
842
+ --project <path> Project root to inspect (default: current directory)
843
+ --trigger <name> Status trigger label (default: scriveno status)
844
+ --json Print machine-readable status JSON
845
+
837
846
  Runtime keys:
838
847
  ${Object.keys(RUNTIMES).join(', ')}
839
848
  `);
@@ -841,6 +850,7 @@ Runtime keys:
841
850
 
842
851
  function parseArgs(argv) {
843
852
  const options = {
853
+ command: 'install',
844
854
  runtimeKeys: [],
845
855
  installDetected: false,
846
856
  isGlobal: null,
@@ -848,8 +858,44 @@ function parseArgs(argv) {
848
858
  silent: false,
849
859
  showHelp: false,
850
860
  showVersion: false,
861
+ statusProjectRoot: process.cwd(),
862
+ statusTrigger: 'scriveno status',
863
+ statusJson: false,
851
864
  };
852
865
 
866
+ if (argv[0] === 'status') {
867
+ options.command = 'status';
868
+ for (let i = 1; i < argv.length; i++) {
869
+ const arg = argv[i];
870
+ if (arg === '--help' || arg === '-h') {
871
+ options.showHelp = true;
872
+ } else if (arg === '--version' || arg === '-v') {
873
+ options.showVersion = true;
874
+ } else if (arg === '--json') {
875
+ options.statusJson = true;
876
+ } else if (arg === '--project') {
877
+ const value = argv[i + 1];
878
+ if (!value) throw new Error('--project requires a value for status');
879
+ options.statusProjectRoot = value;
880
+ i++;
881
+ } else if (arg.startsWith('--project=')) {
882
+ options.statusProjectRoot = arg.slice('--project='.length);
883
+ } else if (arg === '--trigger') {
884
+ const value = argv[i + 1];
885
+ if (!value) throw new Error('--trigger requires a value');
886
+ options.statusTrigger = value;
887
+ i++;
888
+ } else if (arg.startsWith('--trigger=')) {
889
+ options.statusTrigger = arg.slice('--trigger='.length);
890
+ } else if (arg.startsWith('-')) {
891
+ throw new Error(`Unknown status argument "${arg}"`);
892
+ } else {
893
+ options.statusProjectRoot = arg;
894
+ }
895
+ }
896
+ return options;
897
+ }
898
+
853
899
  function addRuntimeList(value) {
854
900
  for (const key of String(value).split(',').map((item) => item.trim()).filter(Boolean)) {
855
901
  if (!Object.prototype.hasOwnProperty.call(RUNTIMES, key)) {
@@ -901,6 +947,16 @@ function parseArgs(argv) {
901
947
  return options;
902
948
  }
903
949
 
950
+ function runStatus({ projectRoot, trigger, json }) {
951
+ const analysis = autoInvokeEngine.analyzeProject(projectRoot);
952
+ if (json) {
953
+ console.log(JSON.stringify(analysis, null, 2));
954
+ } else {
955
+ console.log(autoInvokeEngine.formatReport(analysis, { trigger }));
956
+ }
957
+ return analysis;
958
+ }
959
+
904
960
  function resolveInstallRequest(parsed, detectedRuntimeKeys, { isTTY }) {
905
961
  const hasRuntimeDirective = parsed.runtimeKeys.length > 0 || parsed.installDetected;
906
962
  const hasModifierOverrides = parsed.isGlobal !== null || parsed.developerMode !== null;
@@ -1324,6 +1380,15 @@ async function main() {
1324
1380
  return;
1325
1381
  }
1326
1382
 
1383
+ if (parsed.command === 'status') {
1384
+ runStatus({
1385
+ projectRoot: parsed.statusProjectRoot,
1386
+ trigger: parsed.statusTrigger,
1387
+ json: parsed.statusJson,
1388
+ });
1389
+ return;
1390
+ }
1391
+
1327
1392
  const detectedRuntimeKeys = Object.entries(RUNTIMES).filter(([, runtime]) => runtime.detect()).map(([key]) => key);
1328
1393
  const installRequest = resolveInstallRequest(parsed, detectedRuntimeKeys, { isTTY: Boolean(process.stdin.isTTY) });
1329
1394
 
@@ -1545,11 +1610,13 @@ function installGuidedRuntime(runtime, isGlobal, dataDir, log) {
1545
1610
  function writeSharedAssets(dataDir, runtimeKeys, isGlobal, developerMode, installMode, log) {
1546
1611
  fs.mkdirSync(path.join(dataDir, 'templates'), { recursive: true });
1547
1612
  fs.mkdirSync(path.join(dataDir, 'data'), { recursive: true });
1613
+ fs.mkdirSync(path.join(dataDir, 'lib'), { recursive: true });
1548
1614
  const templateResult = copyDirWithPreservation(path.join(PKG_ROOT, 'templates'), path.join(dataDir, 'templates'));
1549
1615
  const dataResult = copyDirWithPreservation(path.join(PKG_ROOT, 'data'), path.join(dataDir, 'data'));
1616
+ const libResult = copyDirWithPreservation(path.join(PKG_ROOT, 'lib'), path.join(dataDir, 'lib'));
1550
1617
  const sum = (r) => r.fresh + r.replaced + r.backedUp;
1551
- log(` ${c('green', 'OK')} ${sum(templateResult)} templates + ${sum(dataResult)} data files -> ${c('dim', dataDir)}`);
1552
- const totalBackedUp = templateResult.backedUp + dataResult.backedUp;
1618
+ log(` ${c('green', 'OK')} ${sum(templateResult)} templates + ${sum(dataResult)} data files + ${sum(libResult)} lib files -> ${c('dim', dataDir)}`);
1619
+ const totalBackedUp = templateResult.backedUp + dataResult.backedUp + libResult.backedUp;
1553
1620
  if (totalBackedUp > 0) {
1554
1621
  log(` ${c('yellow', 'i')} Preserved ${totalBackedUp} user-modified file(s) as .backup.<timestamp>`);
1555
1622
  }
@@ -1706,6 +1773,7 @@ module.exports = {
1706
1773
  RUNTIMES,
1707
1774
  parseArgs,
1708
1775
  resolveInstallRequest,
1776
+ runStatus,
1709
1777
  collectCommandEntries,
1710
1778
  collectAgentEntries,
1711
1779
  assertNoSkillNameCollisions,
@@ -1754,4 +1822,10 @@ module.exports = {
1754
1822
  // Per-work-type pitfall packs
1755
1823
  listPitfallPacks: architecturalProfiles.listPitfallPacks,
1756
1824
  getPitfallPackPath: architecturalProfiles.getPitfallPackPath,
1825
+ // Shared proactive status engine
1826
+ autoInvokeEngine,
1827
+ analyzeProject: autoInvokeEngine.analyzeProject,
1828
+ formatAutoInvokeReport: autoInvokeEngine.formatReport,
1829
+ getRuntimeAgentSupport: autoInvokeEngine.getRuntimeAgentSupport,
1830
+ listRuntimeAgentSupport: autoInvokeEngine.listRuntimeAgentSupport,
1757
1831
  };
@@ -69,7 +69,7 @@ Always create `RECORD.md` from `templates/RECORD.md` without renaming it. It is
69
69
  Write `.manuscript/config.json` by starting from `templates/config.json` and filling the project-specific values. The generated config must include the shared settings blocks that later commands read:
70
70
  ```json
71
71
  {
72
- "scriveno_version": "2.0.7",
72
+ "scriveno_version": "2.0.8",
73
73
  "work_type": "<chosen>",
74
74
  "group": "<group>",
75
75
  "command_unit": "<unit>",
@@ -8,6 +8,17 @@ You are routing the writer to the right next step in their workflow. This comman
8
8
 
9
9
  Follow the auto-invoke policy. In the source repository it is documented at `docs/auto-invoke-policy.md`. `/scr:next` is Level 1 only by default: it may inspect disk state and suggest the safest next command, but it does not spawn agents or mutate files unless autopilot mode explicitly routes into another command.
10
10
 
11
+ Use the shared executable engine before falling back to manual inspection. Try the first available path:
12
+
13
+ ```bash
14
+ scriveno status --project "$PWD" --trigger /scr:next
15
+ node lib/auto-invoke-engine.js --project "$PWD" --trigger /scr:next
16
+ node "$HOME/.scriveno/lib/auto-invoke-engine.js" --project "$PWD" --trigger /scr:next
17
+ node .scriveno/lib/auto-invoke-engine.js --project "$PWD" --trigger /scr:next
18
+ ```
19
+
20
+ This engine is installed into Scriveno shared assets for every runtime, including Claude Code, Codex, Cursor, Gemini CLI, OpenCode, GitHub Copilot, Windsurf, Antigravity, Manus, Perplexity Desktop, and the generic skill fallback. If the engine is not present, perform the read-only sweep below.
21
+
11
22
  ## What to do
12
23
 
13
24
  1. **Check for `.manuscript/` directory.** If none, the writer has no project. Run `/scr:new-work` to start one (or tell them to).
@@ -9,6 +9,17 @@ You are showing the writer their current project progress.
9
9
 
10
10
  Follow the auto-invoke policy. In the source repository it is documented at `docs/auto-invoke-policy.md`. `/scr:progress` is read-only: it can count, compare, and recommend, but it must not spawn agents or write files.
11
11
 
12
+ Use the shared executable engine before falling back to manual counts. Try the first available path:
13
+
14
+ ```bash
15
+ scriveno status --project "$PWD" --trigger /scr:progress
16
+ node lib/auto-invoke-engine.js --project "$PWD" --trigger /scr:progress
17
+ node "$HOME/.scriveno/lib/auto-invoke-engine.js" --project "$PWD" --trigger /scr:progress
18
+ node .scriveno/lib/auto-invoke-engine.js --project "$PWD" --trigger /scr:progress
19
+ ```
20
+
21
+ This engine is installed into Scriveno shared assets for every runtime, including Claude Code, Codex, Cursor, Gemini CLI, OpenCode, GitHub Copilot, Windsurf, Antigravity, Manus, Perplexity Desktop, and the generic skill fallback. If the engine is not present, perform the read-only progress logic below.
22
+
12
23
  ## Prerequisites
13
24
 
14
25
  - `.manuscript/STATE.md` must exist
@@ -9,6 +9,17 @@ You are summarizing the writer's current session. Your job is to compute actiona
9
9
 
10
10
  Follow the auto-invoke policy. In the source repository it is documented at `docs/auto-invoke-policy.md`. `/scr:session-report` is read-only and does not spawn agents.
11
11
 
12
+ Use the shared executable engine for the read-only status portion before computing session-specific metrics. Try the first available path:
13
+
14
+ ```bash
15
+ scriveno status --project "$PWD" --trigger /scr:session-report
16
+ node lib/auto-invoke-engine.js --project "$PWD" --trigger /scr:session-report
17
+ node "$HOME/.scriveno/lib/auto-invoke-engine.js" --project "$PWD" --trigger /scr:session-report
18
+ node .scriveno/lib/auto-invoke-engine.js --project "$PWD" --trigger /scr:session-report
19
+ ```
20
+
21
+ This engine is installed into Scriveno shared assets for every runtime, including Claude Code, Codex, Cursor, Gemini CLI, OpenCode, GitHub Copilot, Windsurf, Antigravity, Manus, Perplexity Desktop, and the generic skill fallback. If the engine is not present, continue with the read-only report logic below.
22
+
12
23
  ## What to do
13
24
 
14
25
  1. **Read STATE.md "Last actions" table** to get the full history of actions.
@@ -11,6 +11,17 @@ This command is for local runtime drift: Codex skills, Codex command mirrors, Cl
11
11
 
12
12
  This is not a package upgrade command. Do not fetch a newer Scriveno release, do not change npm dependencies, and do not modify manuscript content. If the writer wants a newer published package version, that belongs to a future `/scr:update` command.
13
13
 
14
+ The auto-invoke status engine is a shared runtime asset. It is copied for every install target and can be checked with one of these paths:
15
+
16
+ ```bash
17
+ scriveno status --project "$PWD" --trigger /scr:sync
18
+ node lib/auto-invoke-engine.js --project "$PWD" --trigger /scr:sync
19
+ node "$HOME/.scriveno/lib/auto-invoke-engine.js" --project "$PWD" --trigger /scr:sync
20
+ node .scriveno/lib/auto-invoke-engine.js --project "$PWD" --trigger /scr:sync
21
+ ```
22
+
23
+ Use it for read-only project status and next-command reasoning. Use `bin/install.js` for runtime file synchronization.
24
+
14
25
  ## Prerequisites
15
26
 
16
27
  - Node.js >=20.0.0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "./constraints.schema.json",
3
- "version": "2.0.7",
3
+ "version": "2.0.8",
4
4
  "description": "Scriveno constraint system: work types, command availability, exports, and dependencies. Every command checks this file at runtime.",
5
5
  "_notes": {
6
6
  "sacred_keys": "Sacred subcommands live at commands/scr/sacred/<name>.md and run as /scr:sacred:<name>. Their CONSTRAINTS keys use the sacred:<name> form so /scr:help can render the runnable slash-command path directly. The sacred-numbering-format entry is a separate flat command (commands/scr/sacred-numbering-format.md) that surfaces the active tradition's numbering format. It used to be named sacred-verse-numbering, which collided with sacred:verse-numbering at install time -- both flattened to scr-sacred-verse-numbering. Renamed in v1.6.x; the installer now refuses to install when two sources share a flat skill name."
@@ -349,6 +349,12 @@ Codex uses a skill-native variation of this strategy. The installer generates on
349
349
 
350
350
  **Guided local-MCP (type: `guided-mcp`).** Writes setup assets and connector recipes for runtimes that expose a documented local-MCP surface instead of a writable slash-command directory. Perplexity Desktop currently fits this model: Scriveno writes a setup guide and filesystem-server command recipe under `.scriveno/perplexity/`, and the user adds that command inside Perplexity Desktop's Connectors UI.
351
351
 
352
+ ### Shared status engine
353
+
354
+ Scriveno also ships `lib/auto-invoke-engine.js`, exposed through `scriveno status --project .` and `scriveno status . --json`. The installer copies this library into the shared Scriveno asset directory for global and project installs, so command surfaces can call a single read-only status engine before falling back to embedded markdown logic.
355
+
356
+ The engine checks disk evidence only: project presence, STATE.md, CONTEXT.md freshness, review files, translation work, exports, history, and save signals. It recommends the next command, but it does not mutate files and does not spawn agents by itself. That boundary keeps proactive behavior portable across Claude Code, Codex, Cursor, Gemini CLI, OpenCode, GitHub Copilot, Windsurf, Antigravity, Manus, Perplexity Desktop, and the generic fallback.
357
+
352
358
  ### Installation modes
353
359
 
354
360
  The installer supports two scopes:
@@ -360,7 +366,7 @@ The user chooses during installation. Guided local-MCP targets still write their
360
366
 
361
367
  ### Runtime credibility
362
368
 
363
- Scriveno's installer compatibility floor is `Node.js >=20.0.0`. For new installs, prefer a currently supported LTS such as Node.js 24. The compatibility floor applies to `npx scriveno@latest`, `bin/install.js`, and the repo's JavaScript test suite, not to the markdown command system once files are installed.
369
+ Scriveno's installer compatibility floor is `Node.js >=20.0.0`. For new installs, prefer a currently supported LTS such as Node.js 24. The compatibility floor applies to `npx scriveno@latest`, `bin/install.js`, `scriveno status --project .`, the shared status engine, and the repo's JavaScript test suite, not to the markdown command system once files are installed.
364
370
 
365
371
  This architecture doc is intentionally about mechanics: detection rules, install path shapes, `commands` versus `skills` versus `guided-mcp`, and global versus project scope. For the authoritative runtime matrix, support levels, and verification status, see [`docs/runtime-support.md`](runtime-support.md).
366
372
 
@@ -2,6 +2,8 @@
2
2
 
3
3
  Scriveno can be proactive, but it must be proactive from disk evidence. Commands should inspect `.manuscript/`, reports, timestamps, config, and installed runtime surfaces before choosing an automatic helper.
4
4
 
5
+ The executable policy lives in `lib/auto-invoke-engine.js` and is exposed through `scriveno status --project .`. The installer copies it to `.scriveno/lib/auto-invoke-engine.js` for project installs and `~/.scriveno/lib/auto-invoke-engine.js` for global installs, so every runtime can use the same read-only status logic.
6
+
5
7
  ## Cross-Platform Agent Rules
6
8
 
7
9
  Scriveno agent prompts live in `agents/*.md`. Each host runtime exposes them differently:
@@ -10,6 +12,7 @@ Scriveno agent prompts live in `agents/*.md`. Each host runtime exposes them dif
10
12
  - Codex installs `$scr-*` skills, mirrored command files, agent prompts, and `.toml` metadata so Codex can expose agent types.
11
13
  - Cursor, Gemini CLI, OpenCode, Copilot, Windsurf, and Antigravity install nested command directories and agent prompts in their runtime-specific agent directories.
12
14
  - Manus and the generic skill runtime bundle `SKILL.md`, commands, and agents inside the skill directory.
15
+ - All runtimes share the same installed auto-invoke engine under the Scriveno shared asset directory.
13
16
 
14
17
  When a host supports native fresh-context spawning, use the native agent or subagent mechanism. When it does not, load the installed agent prompt from the active runtime and run it in an isolated fresh context. When the action is only a file operation, report `Agent: none`.
15
18
 
@@ -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.0.7",
60
+ "scriveno_version": "2.0.8",
61
61
  "work_type": "<chosen>",
62
62
  "group": "<group>",
63
63
  "command_unit": "<unit>",
@@ -24,6 +24,15 @@ This installs Scriveno into the runtime you choose. Command-directory and skills
24
24
 
25
25
  Once installed, Claude Code uses flat `/scr-*` commands such as `/scr-help` and `/scr-new-work`. Other command-directory runtimes currently keep `/scr:*`. Codex uses generated `$scr-*` skills such as `$scr-help` and `$scr-new-work`. Guided targets explain their supported setup path directly in the generated setup files.
26
26
 
27
+ You can also ask Scriveno for a read-only project status from any terminal:
28
+
29
+ ```
30
+ scriveno status --project .
31
+ scriveno status . --json
32
+ ```
33
+
34
+ That status command is the same shared auto-invoke engine used by `/scr-next`, `/scr:next`, `/scr:progress`, `/scr:session-report`, and `/scr:sync` when local command execution is available. It recommends the next safest command, but does not mutate files or spawn agents by itself.
35
+
27
36
  ## Step 2: Explore the Demo (Optional)
28
37
 
29
38
  Not sure what Scriveno does? Try the demo before starting your own project:
@@ -181,6 +190,8 @@ $scr-next
181
190
 
182
191
  `/scr-next` reads your project state and runs the right next step automatically. A writer who only ever types `/scr-next` in Claude Code can complete an entire manuscript from start to finish.
183
192
 
193
+ For a terminal-readable version of the same project-state reasoning, run `scriveno status --project .`.
194
+
184
195
  Beyond the core workflow, Scriveno offers:
185
196
 
186
197
  - **Revision** -- `/scr-editor-review`, `/scr-line-edit`, `/scr-continuity-check`
@@ -194,5 +205,6 @@ For the full command list, see [Command Reference](command-reference.md).
194
205
  If you want the trust surfaces around installation and shipping details, continue with:
195
206
 
196
207
  - [Runtime Support](runtime-support.md) -- installer targets, support levels, and verification status
208
+ - [Auto-Invoke Policy](auto-invoke-policy.md) -- status engine, visible automation, and agent-spawn boundaries
197
209
  - [Shipped Assets](shipped-assets.md) -- what the npm package actually includes on the trust-critical surface
198
210
  - [Release Notes](release-notes.md) -- what changed in the latest package release
@@ -2,6 +2,41 @@
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.0.8 - 2026-05-16
6
+
7
+ ### What changed
8
+
9
+ - Scriveno now exposes proactive project status through `scriveno status --project .` and `scriveno status . --json`.
10
+ - A shared read-only engine at `lib/auto-invoke-engine.js` computes status from disk evidence and recommends the safest next command.
11
+ - The installer copies the engine into Scriveno shared assets for global and project installs, so Claude Code, Codex, Cursor, Gemini CLI, OpenCode, GitHub Copilot, Windsurf, Antigravity, Manus, Perplexity Desktop, and the generic fallback can share the same status contract.
12
+ - `/scr:next`, `/scr:progress`, `/scr:session-report`, and `/scr:sync` now try the public status CLI first, then fall back to source, global, or project engine paths.
13
+ - The README now includes a status CLI badge, quick-start status command, proactive status section, and a direct Auto-Invoke Policy link.
14
+ - Package and shipped metadata are aligned on `2.0.8`.
15
+
16
+ ### Why it matters
17
+
18
+ The previous release made automation visible. This release makes the safest read-only part executable as a real package surface. Users and host runtimes can now ask Scriveno what the project needs next without relying on a hidden background process or a Codex-only path.
19
+
20
+ The engine still does not mutate files or spawn agents by itself. It gives a consistent, inspectable status answer; command workflows decide what to run next.
21
+
22
+ ### Affected areas
23
+
24
+ - public CLI
25
+ - shared auto-invoke engine
26
+ - installer shared assets
27
+ - `/scr:next`, `/scr:progress`, `/scr:session-report`, and `/scr:sync`
28
+ - README badges and launch copy
29
+ - runtime support and auto-invoke documentation
30
+ - release metadata and package contents
31
+ - regression tests for CLI output, JSON output, install assets, and package inclusion
32
+
33
+ ### Verification
34
+
35
+ - `node --test`
36
+ - `npm run release:check`
37
+ - `npm pack --dry-run --json`
38
+ - `git diff --check`
39
+
5
40
  ## 2.0.7 - 2026-05-16
6
41
 
7
42
  ### What changed
@@ -10,9 +10,11 @@ Node is required for:
10
10
 
11
11
  - running `npx scriveno@latest`
12
12
  - executing `bin/install.js`
13
+ - running `scriveno status --project .`
14
+ - executing the shared auto-invoke status engine at `lib/auto-invoke-engine.js`
13
15
  - running the repo's JavaScript test suite
14
16
 
15
- Node is not a runtime dependency for Scriveno's markdown command system itself. Once installed, Scriveno's command files, agent prompts, templates, and constraints are read by the host AI coding agent.
17
+ Node is not a runtime dependency for Scriveno's markdown command system itself. Once installed, Scriveno's command files, agent prompts, templates, constraints, and shared auto-invoke engine are read by the host AI coding agent. Runtimes that can run local shell commands can call the engine directly; runtimes that cannot should use the same command text as a fallback contract.
16
18
 
17
19
  ## Evidence Levels
18
20
 
@@ -61,6 +63,25 @@ Node is not a runtime dependency for Scriveno's markdown command system itself.
61
63
  - Manus Desktop and the generic skills fallback install a manifest `SKILL.md`, mirrored command files, and agent prompts inside the skill bundle.
62
64
  - Perplexity Desktop receives setup assets for a local-MCP connector. It does not receive writable command or agent prompt directories from the installer.
63
65
 
66
+ ## Shared Auto-Invoke Engine
67
+
68
+ Every install target receives the same read-only status engine through Scriveno's shared asset directory:
69
+
70
+ - global installs: `~/.scriveno/lib/auto-invoke-engine.js`
71
+ - project installs: `.scriveno/lib/auto-invoke-engine.js`
72
+ - source checkouts: `lib/auto-invoke-engine.js`
73
+
74
+ The engine computes proactive state from disk evidence: missing or stale context, unresolved review files, translation work, stale exports, missing history, and the recommended next command. It does not mutate manuscript files and does not spawn agents. Host commands such as `/scr:next`, `/scr:progress`, `/scr:session-report`, and `/scr:sync` should call it first when local command execution is available, then fall back to their embedded markdown logic when the host cannot run Node.
75
+
76
+ The public CLI entrypoint is:
77
+
78
+ ```bash
79
+ scriveno status --project .
80
+ scriveno status . --json
81
+ ```
82
+
83
+ The JSON form is intended for CI, host adapters, and future runtime smoke tests.
84
+
64
85
  ## What Scriveno Proves Today
65
86
 
66
87
  Scriveno currently proves all of the following in-repo:
@@ -0,0 +1,520 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const DEFAULT_RUNTIME_SUPPORT = {
5
+ 'claude-code': {
6
+ label: 'Claude Code',
7
+ surface: 'flat commands plus agent prompts',
8
+ nativeSpawn: 'host-supported when Claude Code exposes agents',
9
+ fallback: 'load prompt from .claude/agents',
10
+ metadata: 'none',
11
+ },
12
+ codex: {
13
+ label: 'Codex',
14
+ surface: 'skills, command mirrors, agent prompts, and metadata',
15
+ nativeSpawn: 'host-supported when Codex exposes agent roles',
16
+ fallback: 'load prompt from .codex/agents',
17
+ metadata: 'toml',
18
+ },
19
+ cursor: {
20
+ label: 'Cursor',
21
+ surface: 'nested commands plus agent prompts',
22
+ nativeSpawn: 'host-supported when Cursor exposes agents',
23
+ fallback: 'load prompt from .cursor/agents',
24
+ metadata: 'none',
25
+ },
26
+ 'gemini-cli': {
27
+ label: 'Gemini CLI',
28
+ surface: 'nested commands plus agent prompts',
29
+ nativeSpawn: 'host-supported when Gemini CLI exposes agents',
30
+ fallback: 'load prompt from .gemini/agents',
31
+ metadata: 'none',
32
+ },
33
+ opencode: {
34
+ label: 'OpenCode',
35
+ surface: 'nested commands plus agent prompts',
36
+ nativeSpawn: 'host-supported when OpenCode exposes agents',
37
+ fallback: 'load prompt from .config/opencode/agents',
38
+ metadata: 'none',
39
+ },
40
+ copilot: {
41
+ label: 'GitHub Copilot',
42
+ surface: 'nested commands plus agent prompts',
43
+ nativeSpawn: 'host-supported when Copilot exposes agents',
44
+ fallback: 'load prompt from .github/agents',
45
+ metadata: 'none',
46
+ },
47
+ windsurf: {
48
+ label: 'Windsurf',
49
+ surface: 'nested commands plus agent prompts',
50
+ nativeSpawn: 'host-supported when Windsurf exposes agents',
51
+ fallback: 'load prompt from .windsurf/agents',
52
+ metadata: 'none',
53
+ },
54
+ antigravity: {
55
+ label: 'Antigravity',
56
+ surface: 'nested commands plus agent prompts',
57
+ nativeSpawn: 'host-supported when Antigravity exposes agents',
58
+ fallback: 'load prompt from .gemini/antigravity/agents',
59
+ metadata: 'none',
60
+ },
61
+ manus: {
62
+ label: 'Manus Desktop',
63
+ surface: 'bundled skill, mirrored commands, and agent prompts',
64
+ nativeSpawn: 'host-supported when Manus exposes skill agents',
65
+ fallback: 'load prompt from bundled agents directory',
66
+ metadata: 'none',
67
+ },
68
+ 'perplexity-desktop': {
69
+ label: 'Perplexity Desktop',
70
+ surface: 'guided local MCP setup',
71
+ nativeSpawn: 'not assumed',
72
+ fallback: 'read project files through the filesystem connector',
73
+ metadata: 'none',
74
+ },
75
+ generic: {
76
+ label: 'Generic (SKILL.md)',
77
+ surface: 'bundled skill, mirrored commands, and agent prompts',
78
+ nativeSpawn: 'not assumed',
79
+ fallback: 'load prompt from bundled agents directory',
80
+ metadata: 'none',
81
+ },
82
+ };
83
+
84
+ const REVIEW_KEYWORDS = [
85
+ 'TODO',
86
+ 'FIXME',
87
+ 'UNRESOLVED',
88
+ 'NEEDS REVISION',
89
+ 'QUESTION: Blocking',
90
+ 'VOICE DRIFT',
91
+ 'CONTINUITY',
92
+ ];
93
+
94
+ function pathExists(filePath) {
95
+ try {
96
+ fs.accessSync(filePath);
97
+ return true;
98
+ } catch {
99
+ return false;
100
+ }
101
+ }
102
+
103
+ function safeStat(filePath) {
104
+ try {
105
+ return fs.statSync(filePath);
106
+ } catch (err) {
107
+ if (err.code === 'ENOENT') return null;
108
+ throw err;
109
+ }
110
+ }
111
+
112
+ function readText(filePath) {
113
+ try {
114
+ return fs.readFileSync(filePath, 'utf8');
115
+ } catch (err) {
116
+ if (err.code === 'ENOENT') return '';
117
+ throw err;
118
+ }
119
+ }
120
+
121
+ function readJson(filePath) {
122
+ try {
123
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
124
+ } catch (err) {
125
+ if (err.code === 'ENOENT') return null;
126
+ if (err instanceof SyntaxError) return null;
127
+ throw err;
128
+ }
129
+ }
130
+
131
+ function listFiles(dir, options = {}) {
132
+ const { extensions = null, recursive = true } = options;
133
+ if (!pathExists(dir)) return [];
134
+ const out = [];
135
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
136
+ const fullPath = path.join(dir, entry.name);
137
+ if (entry.isDirectory()) {
138
+ if (recursive) out.push(...listFiles(fullPath, options));
139
+ } else if (!extensions || extensions.includes(path.extname(entry.name))) {
140
+ out.push(fullPath);
141
+ }
142
+ }
143
+ return out;
144
+ }
145
+
146
+ function newestMtime(files) {
147
+ let newest = 0;
148
+ for (const file of files) {
149
+ const stat = safeStat(file);
150
+ if (stat && stat.mtimeMs > newest) newest = stat.mtimeMs;
151
+ }
152
+ return newest;
153
+ }
154
+
155
+ function countMarkdownFiles(dir) {
156
+ return listFiles(dir, { extensions: ['.md'], recursive: true }).length;
157
+ }
158
+
159
+ function containsAny(text, keywords) {
160
+ const haystack = text.toUpperCase();
161
+ return keywords.some((keyword) => haystack.includes(keyword.toUpperCase()));
162
+ }
163
+
164
+ function scanReviewSignals(manuscriptDir) {
165
+ const reviewDirs = [
166
+ 'reviews',
167
+ 'reports',
168
+ 'voice',
169
+ 'continuity',
170
+ 'translation',
171
+ ].map((name) => path.join(manuscriptDir, name));
172
+ const files = reviewDirs.flatMap((dir) => listFiles(dir, { extensions: ['.md', '.txt'], recursive: true }));
173
+ const pending = [];
174
+ for (const file of files) {
175
+ const text = readText(file);
176
+ if (containsAny(text, REVIEW_KEYWORDS)) {
177
+ pending.push(path.relative(manuscriptDir, file));
178
+ }
179
+ }
180
+ return pending;
181
+ }
182
+
183
+ function findNewestOutput(manuscriptDir) {
184
+ const outputDirs = [
185
+ path.join(manuscriptDir, 'output'),
186
+ path.join(manuscriptDir, 'build'),
187
+ path.join(manuscriptDir, 'exports'),
188
+ ];
189
+ return newestMtime(outputDirs.flatMap((dir) => listFiles(dir, { recursive: true })));
190
+ }
191
+
192
+ function detectTranslationSignal(manuscriptDir, config) {
193
+ const translationDir = path.join(manuscriptDir, 'translation');
194
+ const configuredTargets = [
195
+ ...(Array.isArray(config?.target_languages) ? config.target_languages : []),
196
+ ...(Array.isArray(config?.translation?.target_languages) ? config.translation.target_languages : []),
197
+ ...(Array.isArray(config?.translations) ? config.translations : []),
198
+ ];
199
+ const translationFiles = listFiles(translationDir, { recursive: true });
200
+ if (translationFiles.length > 0 || configuredTargets.length > 0) {
201
+ return {
202
+ state: 'follow-up available',
203
+ count: translationFiles.length,
204
+ configuredTargets,
205
+ };
206
+ }
207
+ return {
208
+ state: 'none',
209
+ count: 0,
210
+ configuredTargets: [],
211
+ };
212
+ }
213
+
214
+ function detectHistorySignal(manuscriptDir) {
215
+ const historyPath = path.join(manuscriptDir, 'HISTORY.log');
216
+ if (!pathExists(historyPath)) {
217
+ return { state: 'missing', lastFailed: false };
218
+ }
219
+ const lines = readText(historyPath).split(/\r?\n/).filter(Boolean);
220
+ const last = lines[lines.length - 1] || '';
221
+ return {
222
+ state: 'present',
223
+ lastFailed: /\b(fail|failed|error|blocked)\b/i.test(last),
224
+ };
225
+ }
226
+
227
+ function detectContextSignal(manuscriptDir, draftFiles) {
228
+ const contextPath = path.join(manuscriptDir, 'CONTEXT.md');
229
+ const statePath = path.join(manuscriptDir, 'STATE.md');
230
+ const contextStat = safeStat(contextPath);
231
+ const stateStat = safeStat(statePath);
232
+ const newestDraft = newestMtime(draftFiles);
233
+
234
+ if (!contextStat) {
235
+ return { state: 'missing', suggest: '/scr:save' };
236
+ }
237
+ if (stateStat && contextStat.mtimeMs < stateStat.mtimeMs) {
238
+ return { state: 'stale', suggest: '/scr:scan' };
239
+ }
240
+ if (newestDraft > 0 && contextStat.mtimeMs < newestDraft) {
241
+ return { state: 'stale', suggest: '/scr:save' };
242
+ }
243
+ return { state: 'fresh', suggest: null };
244
+ }
245
+
246
+ function detectExportSignal(manuscriptDir, sourceFiles) {
247
+ const newestSource = newestMtime(sourceFiles);
248
+ const newestOutput = findNewestOutput(manuscriptDir);
249
+ if (newestOutput === 0) {
250
+ return { state: sourceFiles.length ? 'missing' : 'none', suggest: sourceFiles.length ? '/scr:export' : null };
251
+ }
252
+ if (newestSource > newestOutput) {
253
+ return { state: 'stale', suggest: '/scr:export' };
254
+ }
255
+ return { state: 'fresh', suggest: null };
256
+ }
257
+
258
+ function detectSaveSignal(historySignal, draftFiles) {
259
+ if (draftFiles.length === 0) return { state: 'clean', suggest: null };
260
+ if (historySignal.state === 'missing') return { state: 'unsaved manuscript changes', suggest: '/scr:save' };
261
+ return { state: 'clean', suggest: null };
262
+ }
263
+
264
+ function chooseRecommendation(signals, counts) {
265
+ if (!signals.hasProject) {
266
+ return {
267
+ command: '/scr:new-work',
268
+ reason: 'No .manuscript directory was found.',
269
+ alternatives: ['/scr:demo', '/scr:import', '/scr:profile-writer'],
270
+ };
271
+ }
272
+ if (!signals.hasState) {
273
+ return {
274
+ command: '/scr:scan',
275
+ reason: 'The project is missing STATE.md.',
276
+ alternatives: ['/scr:health', '/scr:next'],
277
+ };
278
+ }
279
+ if (signals.history.lastFailed) {
280
+ return {
281
+ command: '/scr:troubleshoot',
282
+ reason: 'The last history entry appears to have failed.',
283
+ alternatives: ['/scr:scan', '/scr:health'],
284
+ };
285
+ }
286
+ if (signals.context.state === 'stale') {
287
+ return {
288
+ command: signals.context.suggest || '/scr:scan',
289
+ reason: 'CONTEXT.md is older than the current project state.',
290
+ alternatives: ['/scr:progress', '/scr:resume-work'],
291
+ };
292
+ }
293
+ if (signals.reviews.count > 0) {
294
+ return {
295
+ command: '/scr:editor-review',
296
+ reason: `${signals.reviews.count} review signal(s) still look unresolved.`,
297
+ alternatives: ['/scr:voice-check', '/scr:continuity-check', '/scr:progress'],
298
+ };
299
+ }
300
+ if (counts.drafts === 0) {
301
+ return {
302
+ command: '/scr:plan',
303
+ reason: 'No draft files were found yet.',
304
+ alternatives: ['/scr:discuss', '/scr:draft', '/scr:voice-test'],
305
+ };
306
+ }
307
+ if (signals.translation.state !== 'none') {
308
+ return {
309
+ command: '/scr:back-translate',
310
+ reason: 'Translation work exists and may need a verification pass.',
311
+ alternatives: ['/scr:cultural-adaptation', '/scr:multi-publish', '/scr:progress'],
312
+ };
313
+ }
314
+ if (signals.export.state === 'stale' || signals.export.state === 'missing') {
315
+ return {
316
+ command: signals.export.suggest || '/scr:export',
317
+ reason: `Export output is ${signals.export.state}.`,
318
+ alternatives: ['/scr:publish', '/scr:progress', '/scr:save'],
319
+ };
320
+ }
321
+ if (signals.save.state !== 'clean') {
322
+ return {
323
+ command: signals.save.suggest || '/scr:save',
324
+ reason: 'Draft files exist without a current history signal.',
325
+ alternatives: ['/scr:progress', '/scr:scan'],
326
+ };
327
+ }
328
+ return {
329
+ command: '/scr:next',
330
+ reason: 'Project state looks consistent; continue with the lifecycle route.',
331
+ alternatives: ['/scr:progress', '/scr:editor-review', '/scr:save'],
332
+ };
333
+ }
334
+
335
+ function analyzeProject(projectRoot = process.cwd(), options = {}) {
336
+ const root = path.resolve(projectRoot);
337
+ const manuscriptDir = options.manuscriptDir || path.join(root, '.manuscript');
338
+ const hasProject = pathExists(manuscriptDir);
339
+ const statePath = path.join(manuscriptDir, 'STATE.md');
340
+ const config = readJson(path.join(manuscriptDir, 'config.json')) || {};
341
+
342
+ if (!hasProject) {
343
+ const signals = {
344
+ hasProject: false,
345
+ hasState: false,
346
+ context: { state: 'none', suggest: null },
347
+ history: { state: 'none', lastFailed: false },
348
+ reviews: { state: 'none', count: 0, files: [] },
349
+ translation: { state: 'none', count: 0, configuredTargets: [] },
350
+ export: { state: 'none', suggest: null },
351
+ save: { state: 'clean', suggest: null },
352
+ };
353
+ const recommendation = chooseRecommendation(signals, { drafts: 0 });
354
+ return {
355
+ projectRoot: root,
356
+ manuscriptDir,
357
+ commandUnit: config.command_unit || 'unit',
358
+ workType: config.work_type || '',
359
+ counts: { drafts: 0, plans: 0, reviews: 0 },
360
+ signals,
361
+ recommendation,
362
+ };
363
+ }
364
+
365
+ const draftFiles = listFiles(path.join(manuscriptDir, 'drafts'), { extensions: ['.md'], recursive: true });
366
+ const planCount = countMarkdownFiles(path.join(manuscriptDir, 'plans'));
367
+ const reviewFiles = scanReviewSignals(manuscriptDir);
368
+ const historySignal = detectHistorySignal(manuscriptDir);
369
+ const sourceFiles = [
370
+ statePath,
371
+ path.join(manuscriptDir, 'OUTLINE.md'),
372
+ path.join(manuscriptDir, 'RECORD.md'),
373
+ path.join(manuscriptDir, 'STYLE-GUIDE.md'),
374
+ ...draftFiles,
375
+ ].filter(pathExists);
376
+
377
+ const signals = {
378
+ hasProject: true,
379
+ hasState: pathExists(statePath),
380
+ context: detectContextSignal(manuscriptDir, draftFiles),
381
+ history: historySignal,
382
+ reviews: {
383
+ state: reviewFiles.length ? 'pending' : 'none',
384
+ count: reviewFiles.length,
385
+ files: reviewFiles,
386
+ },
387
+ translation: detectTranslationSignal(manuscriptDir, config),
388
+ export: detectExportSignal(manuscriptDir, sourceFiles),
389
+ save: detectSaveSignal(historySignal, draftFiles),
390
+ };
391
+ const counts = {
392
+ drafts: draftFiles.length,
393
+ plans: planCount,
394
+ reviews: reviewFiles.length,
395
+ };
396
+ const recommendation = chooseRecommendation(signals, counts);
397
+ return {
398
+ projectRoot: root,
399
+ manuscriptDir,
400
+ commandUnit: config.command_unit || 'unit',
401
+ workType: config.work_type || '',
402
+ counts,
403
+ signals,
404
+ recommendation,
405
+ };
406
+ }
407
+
408
+ function formatProactiveChecks(analysis) {
409
+ const { signals } = analysis;
410
+ const stateLine = signals.hasProject
411
+ ? ` State: ${signals.hasState ? 'fresh' : 'missing, suggest /scr:scan'}`
412
+ : ' Project: missing, suggest /scr:new-work';
413
+ return [
414
+ 'Proactive checks:',
415
+ stateLine,
416
+ ` Session: ${signals.context.state}${signals.context.suggest ? `, suggest ${signals.context.suggest}` : ''}`,
417
+ ` Reviews: ${signals.reviews.count ? `${signals.reviews.count} pending, suggest /scr:editor-review` : 'none'}`,
418
+ ` Translation: ${signals.translation.state}`,
419
+ ` Export: ${signals.export.state}${signals.export.suggest ? `, suggest ${signals.export.suggest}` : ''}`,
420
+ ` Save: ${signals.save.state}${signals.save.suggest ? `, suggest ${signals.save.suggest}` : ''}`,
421
+ ].join('\n');
422
+ }
423
+
424
+ function formatAutomationStatus(analysis, options = {}) {
425
+ const trigger = options.trigger || '/scr:next';
426
+ const localOperation = options.localOperation || 'auto-invoke engine: read-only';
427
+ const autoInvoked = options.autoInvoked || `${analysis.recommendation.command}: no`;
428
+ return [
429
+ 'Automation status:',
430
+ `Trigger: ${trigger}`,
431
+ 'Spawned agents:',
432
+ '- none',
433
+ 'Local operations:',
434
+ `- ${localOperation}`,
435
+ `- state route computed: ${analysis.signals.hasProject ? 'yes' : 'no project'}`,
436
+ 'Auto-invoked:',
437
+ `- ${autoInvoked}`,
438
+ `Why: ${analysis.recommendation.reason}`,
439
+ ].join('\n');
440
+ }
441
+
442
+ function formatRecommendation(analysis) {
443
+ const lines = [
444
+ `${analysis.recommendation.command} is the recommended next command.`,
445
+ analysis.recommendation.reason,
446
+ '',
447
+ 'Next commands:',
448
+ `- \`${analysis.recommendation.command}\`: Run the highest-confidence next step from disk state.`,
449
+ ];
450
+ for (const command of analysis.recommendation.alternatives.slice(0, 3)) {
451
+ lines.push(`- \`${command}\`: Use this alternate path if it better matches the writer's intent.`);
452
+ }
453
+ return lines.join('\n');
454
+ }
455
+
456
+ function formatReport(analysis, options = {}) {
457
+ return [
458
+ formatProactiveChecks(analysis),
459
+ '',
460
+ formatAutomationStatus(analysis, options),
461
+ '',
462
+ formatRecommendation(analysis),
463
+ ].join('\n');
464
+ }
465
+
466
+ function getRuntimeAgentSupport(runtimeKey) {
467
+ return DEFAULT_RUNTIME_SUPPORT[runtimeKey] || null;
468
+ }
469
+
470
+ function listRuntimeAgentSupport() {
471
+ return Object.entries(DEFAULT_RUNTIME_SUPPORT).map(([key, value]) => ({
472
+ key,
473
+ ...value,
474
+ }));
475
+ }
476
+
477
+ function parseCliArgs(argv) {
478
+ const out = {
479
+ projectRoot: process.cwd(),
480
+ trigger: '/scr:next',
481
+ json: false,
482
+ };
483
+ for (let i = 0; i < argv.length; i++) {
484
+ const arg = argv[i];
485
+ if (arg === '--project') {
486
+ out.projectRoot = argv[++i] || out.projectRoot;
487
+ } else if (arg.startsWith('--project=')) {
488
+ out.projectRoot = arg.slice('--project='.length);
489
+ } else if (arg === '--trigger') {
490
+ out.trigger = argv[++i] || out.trigger;
491
+ } else if (arg.startsWith('--trigger=')) {
492
+ out.trigger = arg.slice('--trigger='.length);
493
+ } else if (arg === '--json') {
494
+ out.json = true;
495
+ }
496
+ }
497
+ return out;
498
+ }
499
+
500
+ if (require.main === module) {
501
+ const args = parseCliArgs(process.argv.slice(2));
502
+ const analysis = analyzeProject(args.projectRoot);
503
+ if (args.json) {
504
+ console.log(JSON.stringify(analysis, null, 2));
505
+ } else {
506
+ console.log(formatReport(analysis, { trigger: args.trigger }));
507
+ }
508
+ }
509
+
510
+ module.exports = {
511
+ DEFAULT_RUNTIME_SUPPORT,
512
+ analyzeProject,
513
+ formatProactiveChecks,
514
+ formatAutomationStatus,
515
+ formatRecommendation,
516
+ formatReport,
517
+ getRuntimeAgentSupport,
518
+ listRuntimeAgentSupport,
519
+ parseCliArgs,
520
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scriveno",
3
- "version": "2.0.7",
3
+ "version": "2.0.8",
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.0.7",
2
+ "scriveno_version": "2.0.8",
3
3
  "work_type": "",
4
4
  "group": "",
5
5
  "command_unit": "",