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 +23 -5
- package/bin/install.js +76 -2
- package/commands/scr/new-work.md +1 -1
- package/commands/scr/next.md +11 -0
- package/commands/scr/progress.md +11 -0
- package/commands/scr/session-report.md +11 -0
- package/commands/scr/sync.md +11 -0
- package/data/CONSTRAINTS.json +1 -1
- package/docs/architecture.md +7 -1
- package/docs/auto-invoke-policy.md +3 -0
- package/docs/configuration.md +1 -1
- package/docs/getting-started.md +12 -0
- package/docs/release-notes.md +35 -0
- package/docs/runtime-support.md +22 -1
- package/lib/auto-invoke-engine.js +520 -0
- package/package.json +1 -1
- package/templates/config.json +1 -1
package/README.md
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/aihxp/scriveno/actions/workflows/ci.yml)
|
|
4
4
|
[](LICENSE)
|
|
5
|
-
[](CHANGELOG.md)
|
|
6
6
|
[](https://www.npmjs.com/package/scriveno)
|
|
7
7
|
[](https://www.npmjs.com/package/scriveno)
|
|
8
|
+
[](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
|
|
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.
|
|
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
|
|
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.
|
|
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
|
};
|
package/commands/scr/new-work.md
CHANGED
|
@@ -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.
|
|
72
|
+
"scriveno_version": "2.0.8",
|
|
73
73
|
"work_type": "<chosen>",
|
|
74
74
|
"group": "<group>",
|
|
75
75
|
"command_unit": "<unit>",
|
package/commands/scr/next.md
CHANGED
|
@@ -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).
|
package/commands/scr/progress.md
CHANGED
|
@@ -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.
|
package/commands/scr/sync.md
CHANGED
|
@@ -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
|
package/data/CONSTRAINTS.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./constraints.schema.json",
|
|
3
|
-
"version": "2.0.
|
|
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."
|
package/docs/architecture.md
CHANGED
|
@@ -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
|
|
package/docs/configuration.md
CHANGED
package/docs/getting-started.md
CHANGED
|
@@ -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
|
package/docs/release-notes.md
CHANGED
|
@@ -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
|
package/docs/runtime-support.md
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
|
|
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
package/templates/config.json
CHANGED