scriveno 2.0.9 → 2.0.10

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,11 +2,13 @@
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.9-blue)](CHANGELOG.md)
5
+ [![Version](https://img.shields.io/badge/version-2.0.10-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
8
  [![Status CLI](https://img.shields.io/badge/status%20CLI-scriveno%20status-blue)](docs/runtime-support.md#shared-auto-invoke-engine)
9
9
  [![Route Intelligence](https://img.shields.io/badge/route%20intelligence-agent%20lanes-blue)](docs/auto-invoke-policy.md)
10
+ [![Runtime Smoke](https://img.shields.io/badge/runtime%20smoke-install%20checks-blue)](docs/runtime-support.md#runtime-smoke-and-agent-checks)
11
+ [![Safe Apply](https://img.shields.io/badge/safe%20apply-visible%20gates-blue)](docs/auto-invoke-policy.md#safe-apply-and-audit-commands)
10
12
 
11
13
  **[scriveno on npm](https://www.npmjs.com/package/scriveno)**
12
14
 
@@ -23,6 +25,7 @@ npx scriveno@latest
23
25
 
24
26
  # Optional project status check
25
27
  scriveno status --project .
28
+ scriveno sync --check
26
29
  ```
27
30
 
28
31
  ---
@@ -80,12 +83,19 @@ Scriveno ships a shared read-only status engine for every installer target. The
80
83
  ```bash
81
84
  scriveno status --project .
82
85
  scriveno status . --json
86
+ scriveno status --project . --apply-safe
87
+ scriveno sync --check
88
+ scriveno smoke --json
89
+ scriveno agents --json
90
+ scriveno routes --json
83
91
  ```
84
92
 
85
93
  It inspects disk evidence such as `.manuscript/`, `STATE.md`, `CONTEXT.md`, plan files, drafts, review coverage, notes, revision proposals, translation work, publishing prerequisites, 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).
86
94
 
87
95
  The status report separates `Candidate agents`, `Candidate local helpers`, and `Manual gates`. That means Scriveno can say when a route is ready for a drafter, voice-checker, translator, continuity-checker, or review worker, when a deterministic helper such as save or scan is enough, and when writer approval is required for publishing, export overwrites, track merges, or undo.
88
96
 
97
+ `--apply-safe` runs only read-only checks and reports write-gated helpers instead of touching manuscript files. `sync --check`, `smoke`, `agents`, and `routes` expose the same cross-runtime audit layer for Claude Code, Codex, Cursor, Gemini CLI, OpenCode, GitHub Copilot, Windsurf, Antigravity, Manus, Perplexity Desktop, and the generic skill fallback.
98
+
89
99
  ---
90
100
 
91
101
  ## The Voice DNA system
@@ -192,6 +202,7 @@ Scriveno is built on five principles:
192
202
  - [Architecture](docs/architecture.md) -- How Scriveno works under the hood
193
203
  - [Configuration](docs/configuration.md) -- Package, installer, constraints, and `.manuscript/config.json` surfaces
194
204
  - [Auto-Invoke Policy](docs/auto-invoke-policy.md) -- Shared status engine, route intelligence lanes, visible automation status, and agent-spawn boundaries
205
+ - [Route Graph Audit](docs/route-graph.md) -- Generated route graph, automation lanes, and priority fixtures
195
206
  - [Development](docs/development.md) -- Contributor workflow for changing commands, templates, installer logic, and docs
196
207
  - [Testing](docs/testing.md) -- What the test suite covers and which checks to run before shipping
197
208
  - [Release Notes](docs/release-notes.md) -- Public summary of what changed between package releases
@@ -216,7 +227,7 @@ Scriveno currently ships installer targets for these AI tooling environments:
216
227
  - **Perplexity Desktop** (guided local-MCP setup)
217
228
  - **Generic (SKILL.md)** fallback
218
229
 
219
- **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.
230
+ **Installer baseline:** `Node.js >=20.0.0` for `npx scriveno@latest`, `bin/install.js`, `scriveno status --project .`, and the proactive audit commands. 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.
220
231
 
221
232
  **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.
222
233
 
@@ -224,11 +235,11 @@ Scriveno currently ships installer targets for these AI tooling environments:
224
235
 
225
236
  ## Status
226
237
 
227
- **Version:** 2.0.9
238
+ **Version:** 2.0.10
228
239
 
229
- 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, the shared `scriveno status --project .` auto-invoke engine, and route-intelligence lanes through `2.0.9`. See [Shipped Assets](docs/shipped-assets.md) for the canonical asset inventory and [Runtime Support](docs/runtime-support.md) for the runtime compatibility matrix.
240
+ 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, the shared `scriveno status --project .` auto-invoke engine, route-intelligence lanes, safe apply reporting, runtime smoke checks, agent availability checks, and route graph audits through `2.0.10`. See [Shipped Assets](docs/shipped-assets.md) for the canonical asset inventory and [Runtime Support](docs/runtime-support.md) for the runtime compatibility matrix.
230
241
 
231
- Version `2.0.9` 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.
242
+ Version `2.0.10` 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.
232
243
 
233
244
  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).
234
245
 
package/bin/install.js CHANGED
@@ -823,6 +823,11 @@ function printHelp() {
823
823
  scriveno
824
824
  scriveno status --project .
825
825
  scriveno status . --json
826
+ scriveno status --project . --apply-safe
827
+ scriveno sync --check
828
+ scriveno smoke --json
829
+ scriveno agents --json
830
+ scriveno routes --json
826
831
  scriveno --runtimes codex,claude-code --global --writer --silent
827
832
 
828
833
  Options:
@@ -841,8 +846,15 @@ Status options:
841
846
  status Inspect a project and recommend the next command
842
847
  --project <path> Project root to inspect (default: current directory)
843
848
  --trigger <name> Status trigger label (default: scriveno status)
849
+ --apply-safe Run read-only checks and report write-gated helpers
844
850
  --json Print machine-readable status JSON
845
851
 
852
+ Audit commands:
853
+ sync --check Check shared sync, runtime, and agent surfaces
854
+ smoke Smoke-test installed runtime surfaces
855
+ agents Inspect installed agent prompts and metadata
856
+ routes Audit route graph and automation lanes
857
+
846
858
  Runtime keys:
847
859
  ${Object.keys(RUNTIMES).join(', ')}
848
860
  `);
@@ -861,6 +873,9 @@ function parseArgs(argv) {
861
873
  statusProjectRoot: process.cwd(),
862
874
  statusTrigger: 'scriveno status',
863
875
  statusJson: false,
876
+ statusApplySafe: false,
877
+ auditJson: false,
878
+ syncCheck: false,
864
879
  };
865
880
 
866
881
  if (argv[0] === 'status') {
@@ -873,6 +888,8 @@ function parseArgs(argv) {
873
888
  options.showVersion = true;
874
889
  } else if (arg === '--json') {
875
890
  options.statusJson = true;
891
+ } else if (arg === '--apply-safe') {
892
+ options.statusApplySafe = true;
876
893
  } else if (arg === '--project') {
877
894
  const value = argv[i + 1];
878
895
  if (!value) throw new Error('--project requires a value for status');
@@ -896,6 +913,36 @@ function parseArgs(argv) {
896
913
  return options;
897
914
  }
898
915
 
916
+ if (['sync', 'smoke', 'agents', 'routes'].includes(argv[0])) {
917
+ options.command = argv[0];
918
+ for (let i = 1; i < argv.length; i++) {
919
+ const arg = argv[i];
920
+ if (arg === '--help' || arg === '-h') {
921
+ options.showHelp = true;
922
+ } else if (arg === '--version' || arg === '-v') {
923
+ options.showVersion = true;
924
+ } else if (arg === '--json') {
925
+ options.auditJson = true;
926
+ } else if (arg === '--check' && argv[0] === 'sync') {
927
+ options.syncCheck = true;
928
+ } else if (arg === '--project') {
929
+ const value = argv[i + 1];
930
+ if (!value) throw new Error('--project requires a value');
931
+ options.statusProjectRoot = value;
932
+ i++;
933
+ } else if (arg.startsWith('--project=')) {
934
+ options.statusProjectRoot = arg.slice('--project='.length);
935
+ } else if (arg.startsWith('-')) {
936
+ throw new Error(`Unknown ${argv[0]} argument "${arg}"`);
937
+ } else if (argv[0] === 'sync') {
938
+ options.statusProjectRoot = arg;
939
+ } else {
940
+ throw new Error(`Unknown ${argv[0]} argument "${arg}"`);
941
+ }
942
+ }
943
+ return options;
944
+ }
945
+
899
946
  function addRuntimeList(value) {
900
947
  for (const key of String(value).split(',').map((item) => item.trim()).filter(Boolean)) {
901
948
  if (!Object.prototype.hasOwnProperty.call(RUNTIMES, key)) {
@@ -947,14 +994,73 @@ function parseArgs(argv) {
947
994
  return options;
948
995
  }
949
996
 
950
- function runStatus({ projectRoot, trigger, json }) {
997
+ function runStatus({ projectRoot, trigger, json, applySafe }) {
951
998
  const analysis = autoInvokeEngine.analyzeProject(projectRoot);
999
+ const safeApply = applySafe
1000
+ ? autoInvokeEngine.collectSafeApplyActions(projectRoot, { analysis, trigger })
1001
+ : null;
952
1002
  if (json) {
953
- console.log(JSON.stringify(analysis, null, 2));
1003
+ console.log(JSON.stringify(safeApply ? { analysis, safeApply } : analysis, null, 2));
954
1004
  } else {
955
1005
  console.log(autoInvokeEngine.formatReport(analysis, { trigger }));
1006
+ if (safeApply) {
1007
+ console.log('');
1008
+ console.log(autoInvokeEngine.formatSafeApplyReport(safeApply));
1009
+ }
1010
+ }
1011
+ return safeApply ? { analysis, safeApply } : analysis;
1012
+ }
1013
+
1014
+ function runSyncCheck({ projectRoot, json }) {
1015
+ const analysis = autoInvokeEngine.analyzeProject(projectRoot);
1016
+ const safeApply = autoInvokeEngine.collectSafeApplyActions(projectRoot, { analysis, trigger: 'scriveno sync --check' });
1017
+ const agents = autoInvokeEngine.inspectAgentAvailability();
1018
+ const smoke = autoInvokeEngine.inspectRuntimeSmoke();
1019
+ const result = { analysis, safeApply, agents, smoke };
1020
+ if (json) {
1021
+ console.log(JSON.stringify(result, null, 2));
1022
+ } else {
1023
+ console.log('Sync status:');
1024
+ console.log(`Project: ${analysis.projectRoot}`);
1025
+ console.log(`Recommendation: ${analysis.recommendation.command}`);
1026
+ console.log('');
1027
+ console.log(autoInvokeEngine.formatSafeApplyReport(safeApply));
1028
+ console.log('');
1029
+ console.log(autoInvokeEngine.formatAgentAvailabilityReport(agents));
1030
+ console.log('');
1031
+ console.log(autoInvokeEngine.formatRuntimeSmokeReport(smoke));
1032
+ }
1033
+ return result;
1034
+ }
1035
+
1036
+ function runRuntimeSmoke({ json }) {
1037
+ const result = autoInvokeEngine.inspectRuntimeSmoke();
1038
+ if (json) {
1039
+ console.log(JSON.stringify(result, null, 2));
1040
+ } else {
1041
+ console.log(autoInvokeEngine.formatRuntimeSmokeReport(result));
1042
+ }
1043
+ return result;
1044
+ }
1045
+
1046
+ function runAgentAvailability({ json }) {
1047
+ const result = autoInvokeEngine.inspectAgentAvailability();
1048
+ if (json) {
1049
+ console.log(JSON.stringify(result, null, 2));
1050
+ } else {
1051
+ console.log(autoInvokeEngine.formatAgentAvailabilityReport(result));
956
1052
  }
957
- return analysis;
1053
+ return result;
1054
+ }
1055
+
1056
+ function runRouteAudit({ json }) {
1057
+ const result = autoInvokeEngine.buildRouteGraph();
1058
+ if (json) {
1059
+ console.log(JSON.stringify(result, null, 2));
1060
+ } else {
1061
+ console.log(autoInvokeEngine.formatRouteGraphReport(result));
1062
+ }
1063
+ return result;
958
1064
  }
959
1065
 
960
1066
  function resolveInstallRequest(parsed, detectedRuntimeKeys, { isTTY }) {
@@ -1385,10 +1491,34 @@ async function main() {
1385
1491
  projectRoot: parsed.statusProjectRoot,
1386
1492
  trigger: parsed.statusTrigger,
1387
1493
  json: parsed.statusJson,
1494
+ applySafe: parsed.statusApplySafe,
1495
+ });
1496
+ return;
1497
+ }
1498
+
1499
+ if (parsed.command === 'sync') {
1500
+ runSyncCheck({
1501
+ projectRoot: parsed.statusProjectRoot,
1502
+ json: parsed.auditJson,
1388
1503
  });
1389
1504
  return;
1390
1505
  }
1391
1506
 
1507
+ if (parsed.command === 'smoke') {
1508
+ runRuntimeSmoke({ json: parsed.auditJson });
1509
+ return;
1510
+ }
1511
+
1512
+ if (parsed.command === 'agents') {
1513
+ runAgentAvailability({ json: parsed.auditJson });
1514
+ return;
1515
+ }
1516
+
1517
+ if (parsed.command === 'routes') {
1518
+ runRouteAudit({ json: parsed.auditJson });
1519
+ return;
1520
+ }
1521
+
1392
1522
  const detectedRuntimeKeys = Object.entries(RUNTIMES).filter(([, runtime]) => runtime.detect()).map(([key]) => key);
1393
1523
  const installRequest = resolveInstallRequest(parsed, detectedRuntimeKeys, { isTTY: Boolean(process.stdin.isTTY) });
1394
1524
 
@@ -1774,6 +1904,10 @@ module.exports = {
1774
1904
  parseArgs,
1775
1905
  resolveInstallRequest,
1776
1906
  runStatus,
1907
+ runSyncCheck,
1908
+ runRuntimeSmoke,
1909
+ runAgentAvailability,
1910
+ runRouteAudit,
1777
1911
  collectCommandEntries,
1778
1912
  collectAgentEntries,
1779
1913
  assertNoSkillNameCollisions,
@@ -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.9",
72
+ "scriveno_version": "2.0.10",
73
73
  "work_type": "<chosen>",
74
74
  "group": "<group>",
75
75
  "command_unit": "<unit>",
@@ -14,13 +14,15 @@ This is not a package upgrade command. Do not fetch a newer Scriveno release, do
14
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
15
 
16
16
  ```bash
17
+ scriveno sync --check
17
18
  scriveno status --project "$PWD" --trigger /scr:sync
19
+ scriveno status --project "$PWD" --apply-safe --trigger /scr:sync
18
20
  node lib/auto-invoke-engine.js --project "$PWD" --trigger /scr:sync
19
21
  node "$HOME/.scriveno/lib/auto-invoke-engine.js" --project "$PWD" --trigger /scr:sync
20
22
  node .scriveno/lib/auto-invoke-engine.js --project "$PWD" --trigger /scr:sync
21
23
  ```
22
24
 
23
- Use it for read-only project status and next-command reasoning. Use `bin/install.js` for runtime file synchronization.
25
+ Use `scriveno sync --check` for the full read-only sync audit: project status, safe apply, agent availability, and runtime smoke. Use `scriveno status --project "$PWD" --apply-safe` when you only need project routing and safe-helper reporting. Use `bin/install.js` for runtime file synchronization.
24
26
 
25
27
  ## Prerequisites
26
28
 
@@ -70,7 +72,7 @@ If you cannot find a Scriveno source root, stop and explain that `/scr:sync` nee
70
72
  - Check that installed Codex commands include current response-contract and source-marker behavior after reinstall.
71
73
  - Report each runtime as `current`, `stale`, `missing`, or `unknown`.
72
74
  5. Decide mode:
73
- - `--check`: report only. Do not write files.
75
+ - `--check`: report only. Run `scriveno sync --check` when available. Do not write files.
74
76
  - `--apply`: run the installer.
75
77
  - No flag: if stale installed Scriveno-owned files are detected, ask the writer before applying. If everything is current, report that no sync is needed.
76
78
  6. When applying, run from the source root:
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "./constraints.schema.json",
3
- "version": "2.0.9",
3
+ "version": "2.0.10",
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."
@@ -351,7 +351,7 @@ Codex uses a skill-native variation of this strategy. The installer generates on
351
351
 
352
352
  ### Shared status engine
353
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.
354
+ Scriveno also ships `lib/auto-invoke-engine.js`, exposed through `scriveno status --project .`, `scriveno status . --json`, `scriveno status --project . --apply-safe`, `scriveno sync --check`, `scriveno smoke`, `scriveno agents`, and `scriveno routes`. The installer copies this library into the shared Scriveno asset directory for global and project installs, so command surfaces can call a single status and audit engine before falling back to embedded markdown logic.
355
355
 
356
356
  The engine checks disk evidence only: project presence, required project files, STATE.md, CONTEXT.md freshness, plan files, draft files, review coverage, unresolved notes, revision-track proposals, translation work, publishing prerequisites, 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
357
 
@@ -365,6 +365,14 @@ Every command registry category has an automation lane through `getCommandAutoma
365
365
 
366
366
  This turns disconnected side features into visible routes. A plan without a draft recommends `/scr:draft` and lists the drafter route as a candidate agent path. Drafts without reviews recommend `/scr:editor-review` before export. Notes route to `/scr:check-notes`. Revision proposals route to `/scr:editor-review --proposal` or `/scr:track`. Publishing gaps route to `/scr:front-matter`, `/scr:back-matter`, `/scr:blurb`, `/scr:cover-art`, or `/scr:publish` depending on disk evidence.
367
367
 
368
+ The same engine now exposes:
369
+
370
+ - **Safe apply reporting**: `status --apply-safe` runs read-only checks, identifies safe helpers, lists agent candidates, and marks write-gated actions as skipped.
371
+ - **Sync check**: `sync --check` combines project status, safe apply, agent availability, and runtime smoke into one transcript.
372
+ - **Agent availability**: `agents` verifies prompt fallback readiness for non-Codex runtimes and metadata readiness for Codex.
373
+ - **Runtime smoke**: `smoke` checks installed command, skill, guide, agent, metadata, and shared-engine surfaces.
374
+ - **Route graph audit**: `routes` derives a command graph from constraints, command intents, dependencies, and automation lanes.
375
+
368
376
  ### Installation modes
369
377
 
370
378
  The installer supports two scopes:
@@ -376,7 +384,7 @@ The user chooses during installation. Guided local-MCP targets still write their
376
384
 
377
385
  ### Runtime credibility
378
386
 
379
- 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.
387
+ 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 proactive audit commands, the shared status engine, and the repo's JavaScript test suite, not to the markdown command system once files are installed.
380
388
 
381
389
  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).
382
390
 
@@ -8,6 +8,22 @@ The engine reports candidates instead of silently acting. It can identify planne
8
8
 
9
9
  The same file exports `getCommandAutomationPolicy()`, which classifies every command registry route into an automation lane. Tests assert that every command category is covered, so newly added routes cannot sit outside the proactive policy by accident.
10
10
 
11
+ ## Safe Apply and Audit Commands
12
+
13
+ Scriveno now exposes the proactive layer as executable checks instead of documentation-only guidance:
14
+
15
+ ```bash
16
+ scriveno status --project . --apply-safe
17
+ scriveno sync --check
18
+ scriveno smoke --json
19
+ scriveno agents --json
20
+ scriveno routes --json
21
+ ```
22
+
23
+ `--apply-safe` runs the read-only status sweep, reports safe local helpers that are ready, lists agent candidates, and marks writer-owned or write-gated helpers as skipped instead of mutating files. It is intentionally conservative: `/scr:save`, `/scr:scan`, `/scr:sync --apply`, publish packaging, export overwrites, track merges, and undo remain explicit actions.
24
+
25
+ `sync --check` combines four reports: project status, safe apply, agent availability, and runtime smoke. `smoke` checks installed commands, skills, prompts, Codex metadata, and the shared engine path. `agents` checks prompt fallback readiness and Codex metadata readiness. `routes` builds a route graph from `data/CONSTRAINTS.json` and the automation policy so disconnected command flows are visible in one audit.
26
+
11
27
  ## Cross-Platform Agent Rules
12
28
 
13
29
  Scriveno agent prompts live in `agents/*.md`. Each host runtime exposes them differently:
@@ -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.9",
60
+ "scriveno_version": "2.0.10",
61
61
  "work_type": "<chosen>",
62
62
  "group": "<group>",
63
63
  "command_unit": "<unit>",
@@ -29,10 +29,14 @@ You can also ask Scriveno for a read-only project status from any terminal:
29
29
  ```
30
30
  scriveno status --project .
31
31
  scriveno status . --json
32
+ scriveno status --project . --apply-safe
33
+ scriveno sync --check
32
34
  ```
33
35
 
34
36
  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. Current status output separates candidate agents, candidate local helpers, and manual gates so you can tell whether Scriveno is pointing at a specialist route, a deterministic file helper, or a writer-owned decision.
35
37
 
38
+ Use `--apply-safe` when you want Scriveno to run the read-only checks and show which helpers are safe, skipped, or agent-ready. Use `sync --check` when you want the installed runtime surfaces checked too.
39
+
36
40
  ## Step 2: Explore the Demo (Optional)
37
41
 
38
42
  Not sure what Scriveno does? Try the demo before starting your own project:
@@ -2,6 +2,40 @@
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.10 - 2026-05-16
6
+
7
+ ### What changed
8
+
9
+ - `scriveno status --project . --apply-safe` now runs the read-only proactive checks and reports safe helpers, agent candidates, and write-gated actions.
10
+ - `scriveno sync --check` now produces a read-only sync transcript with project status, safe apply, agent availability, and runtime smoke.
11
+ - `scriveno smoke`, `scriveno agents`, and `scriveno routes` expose installed-surface checks, agent prompt and metadata checks, and generated route graph audits.
12
+ - Runtime checks cover Claude Code, Codex, Cursor, Gemini CLI, OpenCode, GitHub Copilot, Windsurf, Antigravity, Manus, Perplexity Desktop, and the generic skill fallback through the shared engine.
13
+ - README badges, Runtime Support, Auto-Invoke Policy, Architecture, Getting Started, Testing, sync command docs, Route Graph Audit, changelog, and package metadata are aligned on `2.0.10`.
14
+
15
+ ### Why it matters
16
+
17
+ The previous releases made proactive routing visible. This release makes the support tooling executable. Users can now ask Scriveno what is safe to run, whether installed agent surfaces are present, whether Codex metadata is ready, and how every route fits into the automation graph without relying on a hidden host-specific behavior.
18
+
19
+ ### Affected areas
20
+
21
+ - shared auto-invoke engine
22
+ - public CLI audit commands
23
+ - runtime smoke and agent availability checks
24
+ - route graph audit docs
25
+ - README badges and launch copy
26
+ - architecture, runtime support, auto-invoke policy, getting started, testing, and sync command docs
27
+ - package metadata and generated project examples
28
+ - regression tests for safe apply, runtime smoke, agent availability, and route graph coverage
29
+
30
+ ### Verification
31
+
32
+ - `node --test test/auto-invoke-engine.test.js`
33
+ - `node --test test/installer.test.js`
34
+ - `npm test`
35
+ - `npm run release:check`
36
+ - `npm pack --json`
37
+ - `git diff --check`
38
+
5
39
  ## 2.0.9 - 2026-05-16
6
40
 
7
41
  ### What changed
@@ -0,0 +1,51 @@
1
+ # Route Graph Audit
2
+
3
+ Scriveno's route graph is generated from `data/CONSTRAINTS.json` and `lib/auto-invoke-engine.js`. It is not a separate hand-maintained registry.
4
+
5
+ Run the audit with:
6
+
7
+ ```bash
8
+ scriveno routes
9
+ scriveno routes --json
10
+ ```
11
+
12
+ The text report summarizes command count, graph edges, agent-capable routes, local-helper routes, manual-gated routes, read-only routes, and lane counts. The JSON report includes nodes and edges for host adapters, CI checks, and future visualizations.
13
+
14
+ ## Current Shape
15
+
16
+ As of `2.0.10`, the route graph contains:
17
+
18
+ - 112 commands
19
+ - intent-order edges from `command_intents`
20
+ - dependency-chain edges from `dependencies.core_chain`
21
+ - automation lanes from `getCommandAutomationPolicy()`
22
+ - route priority fixtures for high-value state transitions
23
+
24
+ ## Automation Lanes
25
+
26
+ Each command receives one lane:
27
+
28
+ - `read-only`: inspect and recommend
29
+ - `local-helper`: deterministic local helper, visible in status
30
+ - `agent-ready`: bounded specialist route with known agent prompts
31
+ - `agent-or-local`: route may use a specialist or local diagnostic pass
32
+ - `mixed`: lifecycle route whose behavior depends on project state
33
+ - `manual-gated`: writer-owned action that must stay explicit
34
+
35
+ ## Priority Fixtures
36
+
37
+ The engine exports priority fixtures for the flows most likely to regress:
38
+
39
+ - empty workspace routes to `/scr:new-work`
40
+ - scanned project without drafts routes to `/scr:plan`
41
+ - planned work without drafts routes to `/scr:draft`
42
+ - drafts without review coverage route to `/scr:editor-review`
43
+ - revision proposals route to `/scr:editor-review --proposal`
44
+ - translation work routes to `/scr:back-translate`
45
+ - publishing prerequisite gaps route to the first missing packaging prerequisite
46
+
47
+ These fixtures are covered by `test/auto-invoke-engine.test.js`.
48
+
49
+ ## Cross-Runtime Use
50
+
51
+ All runtimes use the same route graph audit. Claude Code, Cursor, Gemini CLI, OpenCode, GitHub Copilot, Windsurf, and Antigravity read command files and prompt agents. Codex reads generated `$scr-*` skills plus `.toml` agent metadata. Manus and the generic fallback read bundled skill files. Perplexity Desktop receives guided local-MCP setup. The graph stays shared even when the host's native spawning behavior differs.
@@ -11,6 +11,8 @@ Node is required for:
11
11
  - running `npx scriveno@latest`
12
12
  - executing `bin/install.js`
13
13
  - running `scriveno status --project .`
14
+ - running `scriveno status --project . --apply-safe`
15
+ - running `scriveno sync --check`, `scriveno smoke`, `scriveno agents`, and `scriveno routes`
14
16
  - executing the shared auto-invoke status engine at `lib/auto-invoke-engine.js`
15
17
  - running the repo's JavaScript test suite
16
18
 
@@ -78,12 +80,30 @@ The public CLI entrypoint is:
78
80
  ```bash
79
81
  scriveno status --project .
80
82
  scriveno status . --json
83
+ scriveno status --project . --apply-safe
81
84
  ```
82
85
 
83
86
  The JSON form is intended for CI, host adapters, and future runtime smoke tests.
84
87
 
85
88
  Status output uses the same route-intelligence shape across runtimes: `Candidate agents`, `Candidate local helpers`, and `Manual gates`. That keeps Claude Code, Codex, Cursor, Gemini CLI, OpenCode, GitHub Copilot, Windsurf, Antigravity, Manus, Perplexity Desktop, and generic skill installs aligned even when their native spawning mechanisms differ.
86
89
 
90
+ ## Runtime Smoke and Agent Checks
91
+
92
+ The package now exposes cross-runtime checks through the public CLI:
93
+
94
+ ```bash
95
+ scriveno sync --check
96
+ scriveno smoke --json
97
+ scriveno agents --json
98
+ scriveno routes --json
99
+ ```
100
+
101
+ `scriveno smoke` checks installed command surfaces, Codex skill directories, bundled skill manifests, agent prompt counts, Codex `.toml` metadata, guided Perplexity setup assets, and the shared engine under `~/.scriveno/lib/auto-invoke-engine.js`.
102
+
103
+ `scriveno agents` checks whether each runtime has the expected Scriveno agent prompts and reports the correct fallback: prompt-run fallback for Claude Code and standard command runtimes, metadata-ready for Codex when `.toml` files are present, guided setup for Perplexity Desktop, and bundled skill prompts for Manus or generic skill installs.
104
+
105
+ `scriveno routes` audits the command graph and automation lanes from `data/CONSTRAINTS.json`. It is useful when adding commands because it surfaces whether a route is read-only, local-helper, agent-ready, agent-or-local, mixed, or manual-gated.
106
+
87
107
  ## What Scriveno Proves Today
88
108
 
89
109
  Scriveno currently proves all of the following in-repo:
@@ -39,6 +39,7 @@ These files ship in `templates/` and `docs/` and provide the trust trio for sess
39
39
  ## Runtime Sync Asset Shipped Today
40
40
 
41
41
  - `commands/scr/sync.md` -- local runtime-surface synchronization command. It compares and refreshes installed Scriveno commands, Codex skills, command mirrors, and agent prompts from the current source tree by delegating to `bin/install.js`.
42
+ - `lib/auto-invoke-engine.js` -- shared project status, safe apply, runtime smoke, agent availability, and route graph audit engine used by `scriveno status`, `scriveno sync --check`, `scriveno smoke`, `scriveno agents`, and `scriveno routes`.
42
43
 
43
44
  ## Export Templates Shipped Today
44
45
 
package/docs/testing.md CHANGED
@@ -136,6 +136,16 @@ For the standard release gate, prefer:
136
136
  npm run release:check
137
137
  ```
138
138
 
139
+ When changing proactive routing, runtime install paths, or agent surfaces, also run:
140
+
141
+ ```bash
142
+ scriveno status --project . --apply-safe
143
+ scriveno sync --check
144
+ scriveno smoke --json
145
+ scriveno agents --json
146
+ scriveno routes --json
147
+ ```
148
+
139
149
  Use those for release prep so you can inspect what would ship without mutating the registry.
140
150
 
141
151
  ## Practical workflow
@@ -1,4 +1,5 @@
1
1
  const fs = require('fs');
2
+ const os = require('os');
2
3
  const path = require('path');
3
4
 
4
5
  const DEFAULT_RUNTIME_SUPPORT = {
@@ -99,6 +100,119 @@ const CORE_PROJECT_FILES = [
99
100
  'config.json',
100
101
  ];
101
102
 
103
+ const DEFAULT_AGENT_NAMES = [
104
+ 'continuity-checker',
105
+ 'drafter',
106
+ 'plan-checker',
107
+ 'researcher',
108
+ 'translator',
109
+ 'voice-checker',
110
+ ];
111
+
112
+ const ROUTE_PRIORITY_FIXTURES = [
113
+ {
114
+ name: 'empty workspace',
115
+ setup: 'no .manuscript directory',
116
+ expectedCommand: '/scr:new-work',
117
+ reason: 'start or import before lifecycle routing',
118
+ },
119
+ {
120
+ name: 'scanned project without drafts',
121
+ setup: 'STATE.md and CONTEXT.md exist, drafts are absent',
122
+ expectedCommand: '/scr:plan',
123
+ reason: 'planning comes before drafting when no plan is ready',
124
+ },
125
+ {
126
+ name: 'planned work without draft',
127
+ setup: 'plan files exist and drafts are absent',
128
+ expectedCommand: '/scr:draft',
129
+ reason: 'connected plan evidence should route to drafting',
130
+ },
131
+ {
132
+ name: 'draft without review coverage',
133
+ setup: 'draft files exist and reviews are absent',
134
+ expectedCommand: '/scr:editor-review',
135
+ reason: 'review should precede export and packaging',
136
+ },
137
+ {
138
+ name: 'revision proposal waiting',
139
+ setup: 'proposal files exist in .manuscript/proposals',
140
+ expectedCommand: '/scr:editor-review --proposal',
141
+ reason: 'proposal review is more urgent than general notes',
142
+ },
143
+ {
144
+ name: 'translation follow-up',
145
+ setup: 'translation folders or target languages exist after review coverage',
146
+ expectedCommand: '/scr:back-translate',
147
+ reason: 'translation needs verification before multi-publish',
148
+ },
149
+ {
150
+ name: 'publishing prerequisite gap',
151
+ setup: 'reviewed drafts exist without front matter, back matter, blurb, or cover handoff',
152
+ expectedCommand: '/scr:front-matter',
153
+ reason: 'specific packaging prerequisites come before final publish',
154
+ },
155
+ ];
156
+
157
+ const RUNTIME_INSTALL_SURFACES = {
158
+ 'claude-code': {
159
+ commands: (homeDir) => path.join(homeDir, '.claude', 'commands'),
160
+ agents: (homeDir) => path.join(homeDir, '.claude', 'agents'),
161
+ commandLayout: 'flat',
162
+ },
163
+ cursor: {
164
+ commands: (homeDir) => path.join(homeDir, '.cursor', 'commands', 'scr'),
165
+ agents: (homeDir) => path.join(homeDir, '.cursor', 'agents'),
166
+ commandLayout: 'nested',
167
+ },
168
+ 'gemini-cli': {
169
+ commands: (homeDir) => path.join(homeDir, '.gemini', 'commands', 'scr'),
170
+ agents: (homeDir) => path.join(homeDir, '.gemini', 'agents'),
171
+ commandLayout: 'nested',
172
+ },
173
+ codex: {
174
+ commands: (homeDir) => path.join(homeDir, '.codex', 'commands', 'scr'),
175
+ skills: (homeDir) => path.join(homeDir, '.codex', 'skills'),
176
+ agents: (homeDir) => path.join(homeDir, '.codex', 'agents'),
177
+ commandLayout: 'nested',
178
+ metadata: 'toml',
179
+ },
180
+ opencode: {
181
+ commands: (homeDir) => path.join(homeDir, '.config', 'opencode', 'commands', 'scr'),
182
+ agents: (homeDir) => path.join(homeDir, '.config', 'opencode', 'agents'),
183
+ commandLayout: 'nested',
184
+ },
185
+ copilot: {
186
+ commands: (homeDir) => path.join(homeDir, '.github', 'commands', 'scr'),
187
+ agents: (homeDir) => path.join(homeDir, '.github', 'agents'),
188
+ commandLayout: 'nested',
189
+ },
190
+ windsurf: {
191
+ commands: (homeDir) => path.join(homeDir, '.windsurf', 'commands', 'scr'),
192
+ agents: (homeDir) => path.join(homeDir, '.windsurf', 'agents'),
193
+ commandLayout: 'nested',
194
+ },
195
+ antigravity: {
196
+ commands: (homeDir) => path.join(homeDir, '.gemini', 'antigravity', 'commands', 'scr'),
197
+ agents: (homeDir) => path.join(homeDir, '.gemini', 'antigravity', 'agents'),
198
+ commandLayout: 'nested',
199
+ },
200
+ manus: {
201
+ skills: (homeDir) => path.join(homeDir, '.manus', 'skills', 'scriveno'),
202
+ agents: (homeDir) => path.join(homeDir, '.manus', 'skills', 'scriveno', 'agents'),
203
+ commandLayout: 'skill-bundle',
204
+ },
205
+ 'perplexity-desktop': {
206
+ guide: (homeDir) => path.join(homeDir, '.scriveno', 'perplexity'),
207
+ commandLayout: 'guided-mcp',
208
+ },
209
+ generic: {
210
+ skills: (homeDir) => path.join(homeDir, '.scriveno', 'skills'),
211
+ agents: (homeDir) => path.join(homeDir, '.scriveno', 'skills', 'agents'),
212
+ commandLayout: 'skill-bundle',
213
+ },
214
+ };
215
+
102
216
  const AGENT_ROUTE_POLICIES = {
103
217
  '/scr:plan': {
104
218
  agents: ['plan-checker'],
@@ -855,6 +969,381 @@ function listRuntimeAgentSupport() {
855
969
  }));
856
970
  }
857
971
 
972
+ function getPackageRoot() {
973
+ return path.resolve(__dirname, '..');
974
+ }
975
+
976
+ function loadConstraints(options = {}) {
977
+ const constraintsPath = options.constraintsPath || path.join(getPackageRoot(), 'data', 'CONSTRAINTS.json');
978
+ const constraints = readJson(constraintsPath);
979
+ return constraints && constraints.commands ? constraints : { commands: {}, command_intents: {}, dependencies: {} };
980
+ }
981
+
982
+ function expectedCommandCount(options = {}) {
983
+ return Object.keys(loadConstraints(options).commands || {}).length;
984
+ }
985
+
986
+ function getExpectedAgentNames(options = {}) {
987
+ if (Array.isArray(options.agentNames) && options.agentNames.length > 0) {
988
+ return options.agentNames.slice().sort();
989
+ }
990
+ const agentsRoot = options.agentsRoot || path.join(getPackageRoot(), 'agents');
991
+ const files = listFiles(agentsRoot, { extensions: ['.md'], recursive: false })
992
+ .map((file) => path.basename(file, '.md'))
993
+ .sort();
994
+ return files.length ? files : DEFAULT_AGENT_NAMES.slice().sort();
995
+ }
996
+
997
+ function collectSafeApplyActions(projectRoot = process.cwd(), options = {}) {
998
+ const analysis = options.analysis || analyzeProject(projectRoot);
999
+ const actions = [
1000
+ {
1001
+ name: 'status sweep',
1002
+ command: 'scriveno status',
1003
+ status: 'ran',
1004
+ mutation: false,
1005
+ reason: 'computed the current route, local-helper, agent, and manual-gate state',
1006
+ },
1007
+ ];
1008
+
1009
+ const readOnlyHelpers = new Set(['/scr:progress', '/scr:session-report', '/scr:check-notes', '/scr:health', '/scr:validate']);
1010
+ const writeOrInstallHelpers = new Set(['/scr:save', '/scr:scan', '/scr:sync']);
1011
+
1012
+ for (const candidate of analysis.automation.localCandidates || []) {
1013
+ const command = candidate.command;
1014
+ if (readOnlyHelpers.has(command)) {
1015
+ actions.push({
1016
+ name: command.replace('/scr:', ''),
1017
+ command,
1018
+ status: 'ready',
1019
+ mutation: false,
1020
+ reason: candidate.reason,
1021
+ });
1022
+ } else if (writeOrInstallHelpers.has(command)) {
1023
+ actions.push({
1024
+ name: command.replace('/scr:', ''),
1025
+ command,
1026
+ status: 'skipped',
1027
+ mutation: true,
1028
+ reason: `${candidate.reason}; safe apply reports this instead of writing files`,
1029
+ });
1030
+ } else {
1031
+ actions.push({
1032
+ name: command.replace('/scr:', ''),
1033
+ command,
1034
+ status: 'suggested',
1035
+ mutation: null,
1036
+ reason: candidate.reason,
1037
+ });
1038
+ }
1039
+ }
1040
+
1041
+ for (const candidate of analysis.automation.spawnCandidates || []) {
1042
+ actions.push({
1043
+ name: candidate.command.replace('/scr:', ''),
1044
+ command: candidate.command,
1045
+ status: 'agent-candidate',
1046
+ mutation: null,
1047
+ reason: `${candidate.agents.join(', ')}: ${candidate.reason}`,
1048
+ });
1049
+ }
1050
+
1051
+ for (const gate of analysis.automation.manualGates || []) {
1052
+ actions.push({
1053
+ name: gate.command.replace('/scr:', ''),
1054
+ command: gate.command,
1055
+ status: 'manual-gate',
1056
+ mutation: true,
1057
+ reason: gate.reason,
1058
+ });
1059
+ }
1060
+
1061
+ return {
1062
+ projectRoot: analysis.projectRoot,
1063
+ trigger: options.trigger || 'scriveno status --apply-safe',
1064
+ appliedCount: actions.filter((action) => action.status === 'ran').length,
1065
+ skippedCount: actions.filter((action) => action.status === 'skipped' || action.status === 'manual-gate').length,
1066
+ safeToRunCount: actions.filter((action) => action.status === 'ready').length,
1067
+ agentCandidateCount: actions.filter((action) => action.status === 'agent-candidate').length,
1068
+ actions,
1069
+ };
1070
+ }
1071
+
1072
+ function formatSafeApplyReport(result) {
1073
+ const actionLines = result.actions.length
1074
+ ? result.actions.map((action) => {
1075
+ const mutation = action.mutation === false ? 'read-only' : action.mutation === true ? 'writes or external action' : 'host-dependent';
1076
+ return `- ${action.command}: ${action.status} (${mutation}) - ${action.reason}`;
1077
+ })
1078
+ : ['- none'];
1079
+ return [
1080
+ 'Safe apply status:',
1081
+ `Trigger: ${result.trigger}`,
1082
+ `Project: ${result.projectRoot}`,
1083
+ `Read-only checks run: ${result.appliedCount}`,
1084
+ `Safe helpers ready: ${result.safeToRunCount}`,
1085
+ `Agent candidates: ${result.agentCandidateCount}`,
1086
+ `Manual or write-gated actions: ${result.skippedCount}`,
1087
+ 'Actions:',
1088
+ ...actionLines,
1089
+ ].join('\n');
1090
+ }
1091
+
1092
+ function runtimeSurfacePaths(runtimeKey, options = {}) {
1093
+ const homeDir = options.homeDir || os.homedir();
1094
+ const surface = RUNTIME_INSTALL_SURFACES[runtimeKey];
1095
+ if (!surface) return null;
1096
+ const out = { runtimeKey };
1097
+ for (const [key, value] of Object.entries(surface)) {
1098
+ if (typeof value === 'function') out[key] = value(homeDir);
1099
+ }
1100
+ out.commandLayout = surface.commandLayout;
1101
+ out.metadata = surface.metadata || 'none';
1102
+ return out;
1103
+ }
1104
+
1105
+ function inspectAgentAvailability(options = {}) {
1106
+ const runtimeKeys = options.runtimeKeys || Object.keys(DEFAULT_RUNTIME_SUPPORT);
1107
+ const agentNames = getExpectedAgentNames(options);
1108
+ const runtimes = [];
1109
+
1110
+ for (const runtimeKey of runtimeKeys) {
1111
+ const support = getRuntimeAgentSupport(runtimeKey);
1112
+ const paths = runtimeSurfacePaths(runtimeKey, options);
1113
+ if (!support || !paths) continue;
1114
+
1115
+ if (runtimeKey === 'perplexity-desktop') {
1116
+ const guideReady = pathExists(path.join(paths.guide || '', 'SETUP.md'));
1117
+ runtimes.push({
1118
+ runtime: runtimeKey,
1119
+ label: support.label,
1120
+ status: guideReady ? 'guided-ready' : 'guided-missing',
1121
+ nativeSpawn: support.nativeSpawn,
1122
+ fallback: support.fallback,
1123
+ agentsDir: null,
1124
+ promptCount: 0,
1125
+ missingPrompts: agentNames,
1126
+ metadataCount: 0,
1127
+ missingMetadata: [],
1128
+ });
1129
+ continue;
1130
+ }
1131
+
1132
+ const agentsDir = paths.agents || path.join(paths.skills || '', 'agents');
1133
+ const promptFiles = agentNames.map((name) => `${name}.md`);
1134
+ const missingPrompts = promptFiles
1135
+ .filter((fileName) => !pathExists(path.join(agentsDir, fileName)))
1136
+ .map((fileName) => path.basename(fileName, '.md'));
1137
+ const metadataFiles = support.metadata === 'toml'
1138
+ ? agentNames.map((name) => `${name}.toml`)
1139
+ : [];
1140
+ const missingMetadata = metadataFiles
1141
+ .filter((fileName) => !pathExists(path.join(agentsDir, fileName)))
1142
+ .map((fileName) => path.basename(fileName, '.toml'));
1143
+ const promptCount = promptFiles.length - missingPrompts.length;
1144
+ const metadataCount = metadataFiles.length - missingMetadata.length;
1145
+ let status = 'missing';
1146
+ if (missingPrompts.length === 0 && missingMetadata.length === 0 && support.metadata === 'toml') {
1147
+ status = 'metadata-ready';
1148
+ } else if (missingPrompts.length === 0) {
1149
+ status = 'prompt-fallback-ready';
1150
+ }
1151
+
1152
+ runtimes.push({
1153
+ runtime: runtimeKey,
1154
+ label: support.label,
1155
+ status,
1156
+ nativeSpawn: support.nativeSpawn,
1157
+ fallback: support.fallback,
1158
+ agentsDir,
1159
+ promptCount,
1160
+ missingPrompts,
1161
+ metadataCount,
1162
+ missingMetadata,
1163
+ });
1164
+ }
1165
+
1166
+ return {
1167
+ checkedAt: new Date().toISOString(),
1168
+ expectedAgents: agentNames,
1169
+ runtimes,
1170
+ };
1171
+ }
1172
+
1173
+ function formatAgentAvailabilityReport(report) {
1174
+ const lines = [
1175
+ 'Agent availability:',
1176
+ `Expected agents: ${report.expectedAgents.join(', ')}`,
1177
+ ];
1178
+ for (const runtime of report.runtimes) {
1179
+ lines.push(`- ${runtime.label}: ${runtime.status}`);
1180
+ if (runtime.agentsDir) lines.push(` Agents: ${runtime.agentsDir}`);
1181
+ lines.push(` Prompts: ${runtime.promptCount}/${report.expectedAgents.length}`);
1182
+ if (runtime.missingPrompts.length) lines.push(` Missing prompts: ${runtime.missingPrompts.join(', ')}`);
1183
+ if (runtime.missingMetadata.length) lines.push(` Missing metadata: ${runtime.missingMetadata.join(', ')}`);
1184
+ lines.push(` Fallback: ${runtime.fallback}`);
1185
+ }
1186
+ return lines.join('\n');
1187
+ }
1188
+
1189
+ function countInstalledCommands(paths) {
1190
+ if (!paths) return 0;
1191
+ if (paths.commandLayout === 'flat') {
1192
+ return listFiles(paths.commands, { extensions: ['.md'], recursive: false })
1193
+ .filter((file) => /^scr-/.test(path.basename(file)))
1194
+ .length;
1195
+ }
1196
+ if (paths.commandLayout === 'skill-bundle') {
1197
+ return countMarkdownFiles(path.join(paths.skills || '', 'commands', 'scr'));
1198
+ }
1199
+ if (paths.commandLayout === 'guided-mcp') {
1200
+ return pathExists(path.join(paths.guide || '', 'SETUP.md')) ? 1 : 0;
1201
+ }
1202
+ return countMarkdownFiles(paths.commands || '');
1203
+ }
1204
+
1205
+ function inspectRuntimeSmoke(options = {}) {
1206
+ const runtimeKeys = options.runtimeKeys || Object.keys(DEFAULT_RUNTIME_SUPPORT);
1207
+ const expectedCommands = options.expectedCommands || expectedCommandCount(options);
1208
+ const expectedAgents = getExpectedAgentNames(options);
1209
+ const dataDir = options.dataDir || path.join(options.homeDir || os.homedir(), '.scriveno');
1210
+ const enginePath = path.join(dataDir, 'lib', 'auto-invoke-engine.js');
1211
+ const results = [];
1212
+
1213
+ for (const runtimeKey of runtimeKeys) {
1214
+ const support = getRuntimeAgentSupport(runtimeKey);
1215
+ const paths = runtimeSurfacePaths(runtimeKey, options);
1216
+ if (!support || !paths) continue;
1217
+ const commandCount = countInstalledCommands(paths);
1218
+ const skillCount = runtimeKey === 'codex' && pathExists(paths.skills || '')
1219
+ ? fs.readdirSync(paths.skills, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.startsWith('scr-')).length
1220
+ : paths.skills && pathExists(path.join(paths.skills, 'SKILL.md')) ? 1 : 0;
1221
+ const agentsDir = paths.agents || (paths.skills ? path.join(paths.skills, 'agents') : null);
1222
+ const promptCount = countFiles(agentsDir, ['.md']);
1223
+ const metadataCount = runtimeKey === 'codex' ? countFiles(agentsDir, ['.toml']) : 0;
1224
+ const commandReady = runtimeKey === 'perplexity-desktop' ? commandCount === 1 : commandCount >= expectedCommands;
1225
+ const skillReady = runtimeKey === 'codex' ? skillCount >= expectedCommands : !paths.skills || skillCount >= 1;
1226
+ const agentReady = runtimeKey === 'perplexity-desktop' ? true : promptCount >= expectedAgents.length;
1227
+ const metadataReady = runtimeKey === 'codex' ? metadataCount >= expectedAgents.length : true;
1228
+ const engineReady = pathExists(enginePath);
1229
+ const ok = commandReady && skillReady && agentReady && metadataReady && engineReady;
1230
+
1231
+ results.push({
1232
+ runtime: runtimeKey,
1233
+ label: support.label,
1234
+ ok,
1235
+ commands: commandCount,
1236
+ expectedCommands: runtimeKey === 'perplexity-desktop' ? 1 : expectedCommands,
1237
+ skills: skillCount,
1238
+ agents: promptCount,
1239
+ expectedAgents: runtimeKey === 'perplexity-desktop' ? 0 : expectedAgents.length,
1240
+ metadata: metadataCount,
1241
+ expectedMetadata: runtimeKey === 'codex' ? expectedAgents.length : 0,
1242
+ engineReady,
1243
+ paths,
1244
+ });
1245
+ }
1246
+
1247
+ return {
1248
+ checkedAt: new Date().toISOString(),
1249
+ dataDir,
1250
+ enginePath,
1251
+ expectedCommands,
1252
+ expectedAgents,
1253
+ ok: results.every((result) => result.ok),
1254
+ runtimes: results,
1255
+ };
1256
+ }
1257
+
1258
+ function formatRuntimeSmokeReport(report) {
1259
+ const lines = [
1260
+ 'Runtime smoke status:',
1261
+ `Shared engine: ${report.enginePath} (${report.runtimes.some((runtime) => runtime.engineReady) ? 'present' : 'missing'})`,
1262
+ `Overall: ${report.ok ? 'pass' : 'needs attention'}`,
1263
+ ];
1264
+ for (const runtime of report.runtimes) {
1265
+ lines.push(`- ${runtime.label}: ${runtime.ok ? 'pass' : 'needs attention'}`);
1266
+ lines.push(` Commands: ${runtime.commands}/${runtime.expectedCommands}`);
1267
+ if (runtime.skills) lines.push(` Skills: ${runtime.skills}`);
1268
+ if (runtime.expectedAgents) lines.push(` Agent prompts: ${runtime.agents}/${runtime.expectedAgents}`);
1269
+ if (runtime.expectedMetadata) lines.push(` Agent metadata: ${runtime.metadata}/${runtime.expectedMetadata}`);
1270
+ lines.push(` Shared engine: ${runtime.engineReady ? 'present' : 'missing'}`);
1271
+ }
1272
+ return lines.join('\n');
1273
+ }
1274
+
1275
+ function buildRouteGraph(options = {}) {
1276
+ const constraints = options.commands ? options : loadConstraints(options);
1277
+ const commands = constraints.commands || {};
1278
+ const categories = {};
1279
+ const lanes = {};
1280
+ const nodes = Object.entries(commands).map(([name, command]) => {
1281
+ const policy = getCommandAutomationPolicy(name, command);
1282
+ categories[command.category || 'uncategorized'] = (categories[command.category || 'uncategorized'] || 0) + 1;
1283
+ lanes[policy.lane] = (lanes[policy.lane] || 0) + 1;
1284
+ return {
1285
+ id: `/scr:${name}`,
1286
+ name,
1287
+ category: command.category || 'uncategorized',
1288
+ lane: policy.lane,
1289
+ level: policy.level,
1290
+ available: command.available || [],
1291
+ reason: policy.reason,
1292
+ };
1293
+ });
1294
+
1295
+ const edges = [];
1296
+ for (const [intent, names] of Object.entries(constraints.command_intents || {})) {
1297
+ for (let i = 0; i < names.length - 1; i++) {
1298
+ if (commands[names[i]] && commands[names[i + 1]]) {
1299
+ edges.push({ from: `/scr:${names[i]}`, to: `/scr:${names[i + 1]}`, type: 'intent-order', label: intent });
1300
+ }
1301
+ }
1302
+ }
1303
+ for (const [chainName, entries] of Object.entries(constraints.dependencies || {})) {
1304
+ if (!Array.isArray(entries)) continue;
1305
+ const commandEntries = entries.filter((entry) => entry && typeof entry === 'object' && entry.command);
1306
+ for (let i = 0; i < commandEntries.length - 1; i++) {
1307
+ const from = commandEntries[i].command;
1308
+ const to = commandEntries[i + 1].command;
1309
+ if (commands[from] && commands[to]) {
1310
+ edges.push({ from: `/scr:${from}`, to: `/scr:${to}`, type: 'dependency-chain', label: chainName });
1311
+ }
1312
+ }
1313
+ }
1314
+
1315
+ return {
1316
+ generatedAt: new Date().toISOString(),
1317
+ commandCount: nodes.length,
1318
+ edgeCount: edges.length,
1319
+ categories,
1320
+ lanes,
1321
+ agentRoutes: nodes.filter((node) => node.lane === 'agent-ready' || node.lane === 'agent-or-local').length,
1322
+ localRoutes: nodes.filter((node) => node.lane === 'local-helper').length,
1323
+ manualRoutes: nodes.filter((node) => node.lane === 'manual-gated').length,
1324
+ readOnlyRoutes: nodes.filter((node) => node.lane === 'read-only').length,
1325
+ nodes,
1326
+ edges,
1327
+ };
1328
+ }
1329
+
1330
+ function formatRouteGraphReport(graph) {
1331
+ const laneLines = Object.entries(graph.lanes)
1332
+ .sort(([a], [b]) => a.localeCompare(b))
1333
+ .map(([lane, count]) => `- ${lane}: ${count}`);
1334
+ return [
1335
+ 'Route graph audit:',
1336
+ `Commands: ${graph.commandCount}`,
1337
+ `Edges: ${graph.edgeCount}`,
1338
+ `Agent-capable routes: ${graph.agentRoutes}`,
1339
+ `Local-helper routes: ${graph.localRoutes}`,
1340
+ `Manual-gated routes: ${graph.manualRoutes}`,
1341
+ `Read-only routes: ${graph.readOnlyRoutes}`,
1342
+ 'Automation lanes:',
1343
+ ...laneLines,
1344
+ ].join('\n');
1345
+ }
1346
+
858
1347
  function parseCliArgs(argv) {
859
1348
  const out = {
860
1349
  projectRoot: process.cwd(),
@@ -892,15 +1381,27 @@ module.exports = {
892
1381
  AGENT_ROUTE_POLICIES,
893
1382
  CATEGORY_ROUTE_POLICIES,
894
1383
  DEFAULT_RUNTIME_SUPPORT,
1384
+ DEFAULT_AGENT_NAMES,
895
1385
  LOCAL_ROUTE_POLICIES,
896
1386
  MANUAL_ROUTE_POLICIES,
1387
+ ROUTE_PRIORITY_FIXTURES,
897
1388
  analyzeProject,
1389
+ buildRouteGraph,
1390
+ collectSafeApplyActions,
1391
+ expectedCommandCount,
1392
+ formatAgentAvailabilityReport,
1393
+ formatRouteGraphReport,
898
1394
  formatProactiveChecks,
899
1395
  formatAutomationStatus,
900
1396
  formatRecommendation,
901
1397
  formatReport,
1398
+ formatRuntimeSmokeReport,
1399
+ formatSafeApplyReport,
902
1400
  getCommandAutomationPolicy,
1401
+ getExpectedAgentNames,
903
1402
  getRuntimeAgentSupport,
1403
+ inspectAgentAvailability,
1404
+ inspectRuntimeSmoke,
904
1405
  listRuntimeAgentSupport,
905
1406
  parseCliArgs,
906
1407
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scriveno",
3
- "version": "2.0.9",
3
+ "version": "2.0.10",
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.9",
2
+ "scriveno_version": "2.0.10",
3
3
  "work_type": "",
4
4
  "group": "",
5
5
  "command_unit": "",