scriveno 2.0.8 → 2.0.9
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 +9 -6
- package/commands/scr/health.md +6 -0
- package/commands/scr/new-work.md +1 -1
- package/commands/scr/next.md +6 -0
- package/commands/scr/progress.md +6 -0
- package/commands/scr/save.md +6 -0
- package/commands/scr/scan.md +6 -0
- package/commands/scr/session-report.md +6 -0
- package/data/CONSTRAINTS.json +3082 -701
- package/docs/architecture.md +13 -3
- package/docs/auto-invoke-policy.md +14 -0
- package/docs/configuration.md +1 -1
- package/docs/getting-started.md +1 -1
- package/docs/release-notes.md +33 -0
- package/docs/runtime-support.md +3 -1
- package/lib/auto-invoke-engine.js +388 -2
- package/package.json +1 -1
- package/templates/config.json +1 -10
|
@@ -91,6 +91,116 @@ const REVIEW_KEYWORDS = [
|
|
|
91
91
|
'CONTINUITY',
|
|
92
92
|
];
|
|
93
93
|
|
|
94
|
+
const CORE_PROJECT_FILES = [
|
|
95
|
+
'WORK.md',
|
|
96
|
+
'OUTLINE.md',
|
|
97
|
+
'STYLE-GUIDE.md',
|
|
98
|
+
'RECORD.md',
|
|
99
|
+
'config.json',
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
const AGENT_ROUTE_POLICIES = {
|
|
103
|
+
'/scr:plan': {
|
|
104
|
+
agents: ['plan-checker'],
|
|
105
|
+
reason: 'planning can validate unit plans before drafting',
|
|
106
|
+
},
|
|
107
|
+
'/scr:draft': {
|
|
108
|
+
agents: ['drafter', 'voice-checker'],
|
|
109
|
+
reason: 'drafting uses fresh-context prose generation and voice checks',
|
|
110
|
+
},
|
|
111
|
+
'/scr:editor-review': {
|
|
112
|
+
agents: ['diagnostic worker'],
|
|
113
|
+
reason: 'editor review can isolate flagged issue groups',
|
|
114
|
+
},
|
|
115
|
+
'/scr:voice-check': {
|
|
116
|
+
agents: ['voice-checker'],
|
|
117
|
+
reason: 'voice review compares drafts against STYLE-GUIDE.md',
|
|
118
|
+
},
|
|
119
|
+
'/scr:continuity-check': {
|
|
120
|
+
agents: ['continuity-checker'],
|
|
121
|
+
reason: 'continuity review checks contradictions and timeline drift',
|
|
122
|
+
},
|
|
123
|
+
'/scr:translate': {
|
|
124
|
+
agents: ['translator'],
|
|
125
|
+
reason: 'translation runs one fresh-context translation pass per unit',
|
|
126
|
+
},
|
|
127
|
+
'/scr:back-translate': {
|
|
128
|
+
agents: ['translator'],
|
|
129
|
+
reason: 'back-translation verifies target-language drift',
|
|
130
|
+
},
|
|
131
|
+
'/scr:beta-reader': {
|
|
132
|
+
agents: ['beta-reader worker'],
|
|
133
|
+
reason: 'beta review benefits from isolated reader perspectives',
|
|
134
|
+
},
|
|
135
|
+
'/scr:quick-write': {
|
|
136
|
+
agents: ['drafter', 'voice-checker'],
|
|
137
|
+
reason: 'quick writing still benefits from voice-aware isolation',
|
|
138
|
+
},
|
|
139
|
+
'/scr:map-manuscript': {
|
|
140
|
+
agents: ['voice analyst', 'structure analyst', 'character analyst', 'theme analyst', 'world analyst', 'pacing analyst'],
|
|
141
|
+
reason: 'manuscript import uses parallel analysis workers when available',
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const LOCAL_ROUTE_POLICIES = {
|
|
146
|
+
'/scr:save': 'refresh CONTEXT.md, HISTORY.log, and project checkpoint state',
|
|
147
|
+
'/scr:scan': 'reconcile STATE.md and disk evidence',
|
|
148
|
+
'/scr:health': 'diagnose project and runtime health',
|
|
149
|
+
'/scr:sync': 'compare and refresh installed runtime surfaces',
|
|
150
|
+
'/scr:validate': 'run project validation checks',
|
|
151
|
+
'/scr:check-notes': 'surface unresolved writer notes',
|
|
152
|
+
'/scr:progress': 'compute read-only project progress',
|
|
153
|
+
'/scr:session-report': 'compute read-only session metrics',
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const MANUAL_ROUTE_POLICIES = {
|
|
157
|
+
'/scr:publish': 'publication packaging can overwrite deliverables and needs writer choices',
|
|
158
|
+
'/scr:export': 'export writes output artifacts and may overwrite packages',
|
|
159
|
+
'/scr:track merge': 'merging revision tracks is a writer-owned decision',
|
|
160
|
+
'/scr:undo': 'undo changes state and should stay explicit',
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const CATEGORY_ROUTE_POLICIES = {
|
|
164
|
+
core: { lane: 'mixed', level: 3, reason: 'core lifecycle routes may read, write, or spawn depending on the current stage' },
|
|
165
|
+
navigation: { lane: 'read-only', level: 1, reason: 'navigation routes should inspect and recommend by default' },
|
|
166
|
+
quality: { lane: 'agent-or-local', level: 3, reason: 'quality routes may run bounded diagnostics or text transforms' },
|
|
167
|
+
character_world: { lane: 'local-helper', level: 2, reason: 'character and world routes update project knowledge files' },
|
|
168
|
+
structure: { lane: 'local-helper', level: 2, reason: 'structure routes update maps, outlines, and state evidence' },
|
|
169
|
+
structure_management: { lane: 'manual-gated', level: 4, reason: 'structure management can rename, remove, or reorder manuscript units' },
|
|
170
|
+
review: { lane: 'agent-or-local', level: 3, reason: 'review routes may invoke bounded diagnostic workers' },
|
|
171
|
+
illustration: { lane: 'local-helper', level: 2, reason: 'illustration routes generate prompts and asset briefs' },
|
|
172
|
+
publishing: { lane: 'manual-gated', level: 4, reason: 'publishing routes write deliverables and package outputs' },
|
|
173
|
+
translation: { lane: 'agent-or-local', level: 3, reason: 'translation routes use translator agents or verification helpers' },
|
|
174
|
+
sacred_exclusive: { lane: 'agent-or-local', level: 3, reason: 'sacred routes perform specialized consistency and reference work' },
|
|
175
|
+
utility: { lane: 'local-helper', level: 2, reason: 'utility routes perform deterministic diagnostics or project updates' },
|
|
176
|
+
session: { lane: 'local-helper', level: 2, reason: 'session routes save, compare, resume, or report project state' },
|
|
177
|
+
collaboration: { lane: 'manual-gated', level: 4, reason: 'collaboration routes change revision tracks and require writer control' },
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
function normalizeCommandRef(commandName) {
|
|
181
|
+
if (commandName.startsWith('/scr:')) return commandName;
|
|
182
|
+
return `/scr:${commandName}`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function getCommandAutomationPolicy(commandName, command = {}) {
|
|
186
|
+
const ref = normalizeCommandRef(commandName);
|
|
187
|
+
if (AGENT_ROUTE_POLICIES[ref]) {
|
|
188
|
+
return { ref, lane: 'agent-ready', level: 3, reason: AGENT_ROUTE_POLICIES[ref].reason };
|
|
189
|
+
}
|
|
190
|
+
if (LOCAL_ROUTE_POLICIES[ref]) {
|
|
191
|
+
return { ref, lane: 'local-helper', level: 2, reason: LOCAL_ROUTE_POLICIES[ref] };
|
|
192
|
+
}
|
|
193
|
+
if (MANUAL_ROUTE_POLICIES[ref]) {
|
|
194
|
+
return { ref, lane: 'manual-gated', level: 4, reason: MANUAL_ROUTE_POLICIES[ref] };
|
|
195
|
+
}
|
|
196
|
+
const categoryPolicy = CATEGORY_ROUTE_POLICIES[command.category] || {
|
|
197
|
+
lane: 'read-only',
|
|
198
|
+
level: 1,
|
|
199
|
+
reason: 'unclassified routes should only suggest until a category policy is added',
|
|
200
|
+
};
|
|
201
|
+
return { ref, ...categoryPolicy };
|
|
202
|
+
}
|
|
203
|
+
|
|
94
204
|
function pathExists(filePath) {
|
|
95
205
|
try {
|
|
96
206
|
fs.accessSync(filePath);
|
|
@@ -156,11 +266,124 @@ function countMarkdownFiles(dir) {
|
|
|
156
266
|
return listFiles(dir, { extensions: ['.md'], recursive: true }).length;
|
|
157
267
|
}
|
|
158
268
|
|
|
269
|
+
function countFiles(dir, extensions = null) {
|
|
270
|
+
return listFiles(dir, { extensions, recursive: true }).length;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function anyPathExists(paths) {
|
|
274
|
+
return paths.some(pathExists);
|
|
275
|
+
}
|
|
276
|
+
|
|
159
277
|
function containsAny(text, keywords) {
|
|
160
278
|
const haystack = text.toUpperCase();
|
|
161
279
|
return keywords.some((keyword) => haystack.includes(keyword.toUpperCase()));
|
|
162
280
|
}
|
|
163
281
|
|
|
282
|
+
function detectProjectReadiness(manuscriptDir) {
|
|
283
|
+
const missing = CORE_PROJECT_FILES.filter((file) => !pathExists(path.join(manuscriptDir, file)));
|
|
284
|
+
return {
|
|
285
|
+
state: missing.length ? 'incomplete' : 'ready',
|
|
286
|
+
missing,
|
|
287
|
+
suggest: missing.length ? '/scr:scan' : null,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function detectPlanSignal(manuscriptDir, draftFiles) {
|
|
292
|
+
const files = listFiles(path.join(manuscriptDir, 'plans'), { extensions: ['.md'], recursive: true });
|
|
293
|
+
if (files.length === 0) {
|
|
294
|
+
return { state: 'missing', count: 0, suggest: '/scr:plan' };
|
|
295
|
+
}
|
|
296
|
+
if (draftFiles.length === 0) {
|
|
297
|
+
return { state: 'ready-to-draft', count: files.length, suggest: '/scr:draft' };
|
|
298
|
+
}
|
|
299
|
+
if (files.length > draftFiles.length) {
|
|
300
|
+
return { state: 'partially-drafted', count: files.length, suggest: '/scr:draft' };
|
|
301
|
+
}
|
|
302
|
+
return { state: 'covered', count: files.length, suggest: null };
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function detectReviewCoverage(draftFiles, reviewFiles) {
|
|
306
|
+
if (draftFiles.length === 0) {
|
|
307
|
+
return { state: 'none', suggest: null };
|
|
308
|
+
}
|
|
309
|
+
if (reviewFiles.length === 0) {
|
|
310
|
+
return { state: 'missing', suggest: '/scr:editor-review' };
|
|
311
|
+
}
|
|
312
|
+
if (reviewFiles.length < draftFiles.length) {
|
|
313
|
+
return { state: 'partial', suggest: '/scr:editor-review' };
|
|
314
|
+
}
|
|
315
|
+
return { state: 'covered', suggest: null };
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function detectNotesSignal(manuscriptDir) {
|
|
319
|
+
const noteFiles = [
|
|
320
|
+
...listFiles(path.join(manuscriptDir, 'notes'), { extensions: ['.md', '.txt'], recursive: true }),
|
|
321
|
+
path.join(manuscriptDir, 'NOTES.md'),
|
|
322
|
+
path.join(manuscriptDir, 'TODO.md'),
|
|
323
|
+
].filter(pathExists);
|
|
324
|
+
const pending = noteFiles.filter((file) => containsAny(readText(file), ['TODO', 'FIXME', 'UNRESOLVED', 'QUESTION:', 'NOTE:']));
|
|
325
|
+
return {
|
|
326
|
+
state: pending.length ? 'pending' : 'none',
|
|
327
|
+
count: pending.length,
|
|
328
|
+
files: pending.map((file) => path.relative(manuscriptDir, file)),
|
|
329
|
+
suggest: pending.length ? '/scr:check-notes' : null,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function detectTrackSignal(manuscriptDir) {
|
|
334
|
+
const tracks = readJson(path.join(manuscriptDir, 'tracks.json'));
|
|
335
|
+
const proposals = listFiles(path.join(manuscriptDir, 'proposals'), { extensions: ['.md'], recursive: true });
|
|
336
|
+
const activeTracks = Array.isArray(tracks?.tracks)
|
|
337
|
+
? tracks.tracks.filter((track) => track && track.status !== 'merged')
|
|
338
|
+
: [];
|
|
339
|
+
let state = 'none';
|
|
340
|
+
let suggest = null;
|
|
341
|
+
if (proposals.length > 0) {
|
|
342
|
+
state = 'proposal-ready';
|
|
343
|
+
suggest = '/scr:editor-review --proposal';
|
|
344
|
+
} else if (activeTracks.length > 0) {
|
|
345
|
+
state = 'active';
|
|
346
|
+
suggest = '/scr:track';
|
|
347
|
+
}
|
|
348
|
+
return {
|
|
349
|
+
state,
|
|
350
|
+
activeCount: activeTracks.length,
|
|
351
|
+
proposalCount: proposals.length,
|
|
352
|
+
suggest,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function detectPublishingSignal(manuscriptDir, draftFiles) {
|
|
357
|
+
const frontMatter = countMarkdownFiles(path.join(manuscriptDir, 'front-matter'));
|
|
358
|
+
const backMatter = countMarkdownFiles(path.join(manuscriptDir, 'back-matter'));
|
|
359
|
+
const blurb = pathExists(path.join(manuscriptDir, 'output', 'blurb.md'));
|
|
360
|
+
const ebookCover = anyPathExists([
|
|
361
|
+
path.join(manuscriptDir, 'build', 'ebook-cover.jpg'),
|
|
362
|
+
path.join(manuscriptDir, 'build', 'ebook-cover.png'),
|
|
363
|
+
]);
|
|
364
|
+
const printCover = anyPathExists([
|
|
365
|
+
path.join(manuscriptDir, 'build', 'paperback-cover.pdf'),
|
|
366
|
+
path.join(manuscriptDir, 'build', 'hardcover-cover.pdf'),
|
|
367
|
+
]);
|
|
368
|
+
const promptFiles = countFiles(path.join(manuscriptDir, 'illustrations', 'cover'), ['.md']);
|
|
369
|
+
const gaps = [];
|
|
370
|
+
if (draftFiles.length > 0 && frontMatter === 0) gaps.push('front-matter');
|
|
371
|
+
if (draftFiles.length > 0 && backMatter === 0) gaps.push('back-matter');
|
|
372
|
+
if (draftFiles.length > 0 && !blurb) gaps.push('blurb');
|
|
373
|
+
if (draftFiles.length > 0 && !ebookCover && promptFiles === 0) gaps.push('cover-art');
|
|
374
|
+
return {
|
|
375
|
+
state: gaps.length ? 'gaps' : draftFiles.length ? 'ready' : 'not-started',
|
|
376
|
+
frontMatter,
|
|
377
|
+
backMatter,
|
|
378
|
+
blurb,
|
|
379
|
+
ebookCover,
|
|
380
|
+
printCover,
|
|
381
|
+
coverPrompts: promptFiles,
|
|
382
|
+
gaps,
|
|
383
|
+
suggest: gaps.length ? `/scr:${gaps[0]}` : null,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
164
387
|
function scanReviewSignals(manuscriptDir) {
|
|
165
388
|
const reviewDirs = [
|
|
166
389
|
'reviews',
|
|
@@ -290,6 +513,13 @@ function chooseRecommendation(signals, counts) {
|
|
|
290
513
|
alternatives: ['/scr:progress', '/scr:resume-work'],
|
|
291
514
|
};
|
|
292
515
|
}
|
|
516
|
+
if (signals.tracks?.state === 'proposal-ready') {
|
|
517
|
+
return {
|
|
518
|
+
command: signals.tracks.suggest,
|
|
519
|
+
reason: `${signals.tracks.proposalCount} revision proposal(s) are waiting for review.`,
|
|
520
|
+
alternatives: ['/scr:track', '/scr:compare', '/scr:progress'],
|
|
521
|
+
};
|
|
522
|
+
}
|
|
293
523
|
if (signals.reviews.count > 0) {
|
|
294
524
|
return {
|
|
295
525
|
command: '/scr:editor-review',
|
|
@@ -297,6 +527,20 @@ function chooseRecommendation(signals, counts) {
|
|
|
297
527
|
alternatives: ['/scr:voice-check', '/scr:continuity-check', '/scr:progress'],
|
|
298
528
|
};
|
|
299
529
|
}
|
|
530
|
+
if (signals.notes?.count > 0) {
|
|
531
|
+
return {
|
|
532
|
+
command: signals.notes.suggest,
|
|
533
|
+
reason: `${signals.notes.count} note file(s) contain unresolved items.`,
|
|
534
|
+
alternatives: ['/scr:progress', '/scr:scan', '/scr:next'],
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
if (signals.plan?.state === 'ready-to-draft' || signals.plan?.state === 'partially-drafted') {
|
|
538
|
+
return {
|
|
539
|
+
command: signals.plan.suggest,
|
|
540
|
+
reason: `${signals.plan.count} plan file(s) exist and drafting is the next connected step.`,
|
|
541
|
+
alternatives: ['/scr:plan', '/scr:voice-test', '/scr:progress'],
|
|
542
|
+
};
|
|
543
|
+
}
|
|
300
544
|
if (counts.drafts === 0) {
|
|
301
545
|
return {
|
|
302
546
|
command: '/scr:plan',
|
|
@@ -304,6 +548,13 @@ function chooseRecommendation(signals, counts) {
|
|
|
304
548
|
alternatives: ['/scr:discuss', '/scr:draft', '/scr:voice-test'],
|
|
305
549
|
};
|
|
306
550
|
}
|
|
551
|
+
if (signals.reviewCoverage?.state === 'missing' || signals.reviewCoverage?.state === 'partial') {
|
|
552
|
+
return {
|
|
553
|
+
command: signals.reviewCoverage.suggest,
|
|
554
|
+
reason: `Drafts exist but review coverage is ${signals.reviewCoverage.state}.`,
|
|
555
|
+
alternatives: ['/scr:voice-check', '/scr:continuity-check', '/scr:progress'],
|
|
556
|
+
};
|
|
557
|
+
}
|
|
307
558
|
if (signals.translation.state !== 'none') {
|
|
308
559
|
return {
|
|
309
560
|
command: '/scr:back-translate',
|
|
@@ -311,6 +562,13 @@ function chooseRecommendation(signals, counts) {
|
|
|
311
562
|
alternatives: ['/scr:cultural-adaptation', '/scr:multi-publish', '/scr:progress'],
|
|
312
563
|
};
|
|
313
564
|
}
|
|
565
|
+
if (signals.publishing?.state === 'gaps' && signals.export.state === 'missing') {
|
|
566
|
+
return {
|
|
567
|
+
command: signals.publishing.suggest || '/scr:publish',
|
|
568
|
+
reason: `Publishing prerequisites have gaps: ${signals.publishing.gaps.join(', ')}.`,
|
|
569
|
+
alternatives: ['/scr:publish', '/scr:export', '/scr:progress'],
|
|
570
|
+
};
|
|
571
|
+
}
|
|
314
572
|
if (signals.export.state === 'stale' || signals.export.state === 'missing') {
|
|
315
573
|
return {
|
|
316
574
|
command: signals.export.suggest || '/scr:export',
|
|
@@ -332,6 +590,90 @@ function chooseRecommendation(signals, counts) {
|
|
|
332
590
|
};
|
|
333
591
|
}
|
|
334
592
|
|
|
593
|
+
function dedupeByCommand(items) {
|
|
594
|
+
const seen = new Set();
|
|
595
|
+
return items.filter((item) => {
|
|
596
|
+
if (seen.has(item.command)) return false;
|
|
597
|
+
seen.add(item.command);
|
|
598
|
+
return true;
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function buildAutomationPlan(signals, recommendation) {
|
|
603
|
+
const spawnPolicy = AGENT_ROUTE_POLICIES[recommendation.command];
|
|
604
|
+
const localPolicy = LOCAL_ROUTE_POLICIES[recommendation.command];
|
|
605
|
+
const manualPolicy = MANUAL_ROUTE_POLICIES[recommendation.command];
|
|
606
|
+
const spawnCandidates = [];
|
|
607
|
+
const localCandidates = [];
|
|
608
|
+
const manualGates = [];
|
|
609
|
+
|
|
610
|
+
if (spawnPolicy) {
|
|
611
|
+
spawnCandidates.push({
|
|
612
|
+
command: recommendation.command,
|
|
613
|
+
agents: spawnPolicy.agents,
|
|
614
|
+
reason: spawnPolicy.reason,
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
if (signals.plan?.state === 'ready-to-draft' || signals.plan?.state === 'partially-drafted') {
|
|
618
|
+
spawnCandidates.push({
|
|
619
|
+
command: '/scr:draft',
|
|
620
|
+
agents: AGENT_ROUTE_POLICIES['/scr:draft'].agents,
|
|
621
|
+
reason: 'planned units can be drafted by the drafter route',
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
if (signals.reviewCoverage?.state === 'missing' || signals.reviewCoverage?.state === 'partial') {
|
|
625
|
+
spawnCandidates.push({
|
|
626
|
+
command: '/scr:editor-review',
|
|
627
|
+
agents: AGENT_ROUTE_POLICIES['/scr:editor-review'].agents,
|
|
628
|
+
reason: 'drafts without review coverage should enter the review route',
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
if (signals.translation?.state !== 'none') {
|
|
632
|
+
spawnCandidates.push({
|
|
633
|
+
command: '/scr:back-translate',
|
|
634
|
+
agents: AGENT_ROUTE_POLICIES['/scr:back-translate'].agents,
|
|
635
|
+
reason: 'translation work needs a verification pass',
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
if (localPolicy) {
|
|
640
|
+
localCandidates.push({ command: recommendation.command, reason: localPolicy });
|
|
641
|
+
}
|
|
642
|
+
if (signals.context?.state === 'stale') {
|
|
643
|
+
localCandidates.push({ command: signals.context.suggest || '/scr:scan', reason: 'refresh stale context before chaining work' });
|
|
644
|
+
}
|
|
645
|
+
if (signals.notes?.count > 0) {
|
|
646
|
+
localCandidates.push({ command: '/scr:check-notes', reason: 'surface unresolved notes before the next writing route' });
|
|
647
|
+
}
|
|
648
|
+
if (signals.save?.state !== 'clean') {
|
|
649
|
+
localCandidates.push({ command: signals.save.suggest || '/scr:save', reason: 'save manuscript changes before branching or packaging' });
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
if (manualPolicy) {
|
|
653
|
+
manualGates.push({ command: recommendation.command, reason: manualPolicy });
|
|
654
|
+
}
|
|
655
|
+
if (signals.publishing?.state === 'gaps') {
|
|
656
|
+
manualGates.push({
|
|
657
|
+
command: '/scr:publish',
|
|
658
|
+
reason: `publishing still needs ${signals.publishing.gaps.join(', ')}`,
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
if (signals.tracks?.state === 'active' || signals.tracks?.state === 'proposal-ready') {
|
|
662
|
+
manualGates.push({
|
|
663
|
+
command: signals.tracks.suggest || '/scr:track',
|
|
664
|
+
reason: 'revision-track decisions belong to the writer',
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
const recommendationIsManual = manualGates.some((gate) => gate.command === recommendation.command);
|
|
669
|
+
return {
|
|
670
|
+
mode: recommendationIsManual ? 'manual-gated' : spawnCandidates.length ? 'agent-ready' : localCandidates.length ? 'local-helper' : 'read-only',
|
|
671
|
+
spawnCandidates: dedupeByCommand(spawnCandidates),
|
|
672
|
+
localCandidates: dedupeByCommand(localCandidates),
|
|
673
|
+
manualGates: dedupeByCommand(manualGates),
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
|
|
335
677
|
function analyzeProject(projectRoot = process.cwd(), options = {}) {
|
|
336
678
|
const root = path.resolve(projectRoot);
|
|
337
679
|
const manuscriptDir = options.manuscriptDir || path.join(root, '.manuscript');
|
|
@@ -346,11 +688,18 @@ function analyzeProject(projectRoot = process.cwd(), options = {}) {
|
|
|
346
688
|
context: { state: 'none', suggest: null },
|
|
347
689
|
history: { state: 'none', lastFailed: false },
|
|
348
690
|
reviews: { state: 'none', count: 0, files: [] },
|
|
691
|
+
reviewCoverage: { state: 'none', suggest: null },
|
|
692
|
+
readiness: { state: 'none', missing: [], suggest: null },
|
|
693
|
+
plan: { state: 'none', count: 0, suggest: null },
|
|
694
|
+
notes: { state: 'none', count: 0, files: [], suggest: null },
|
|
695
|
+
tracks: { state: 'none', activeCount: 0, proposalCount: 0, suggest: null },
|
|
349
696
|
translation: { state: 'none', count: 0, configuredTargets: [] },
|
|
350
697
|
export: { state: 'none', suggest: null },
|
|
698
|
+
publishing: { state: 'not-started', gaps: [], suggest: null },
|
|
351
699
|
save: { state: 'clean', suggest: null },
|
|
352
700
|
};
|
|
353
701
|
const recommendation = chooseRecommendation(signals, { drafts: 0 });
|
|
702
|
+
const automation = buildAutomationPlan(signals, recommendation);
|
|
354
703
|
return {
|
|
355
704
|
projectRoot: root,
|
|
356
705
|
manuscriptDir,
|
|
@@ -359,12 +708,13 @@ function analyzeProject(projectRoot = process.cwd(), options = {}) {
|
|
|
359
708
|
counts: { drafts: 0, plans: 0, reviews: 0 },
|
|
360
709
|
signals,
|
|
361
710
|
recommendation,
|
|
711
|
+
automation,
|
|
362
712
|
};
|
|
363
713
|
}
|
|
364
714
|
|
|
365
715
|
const draftFiles = listFiles(path.join(manuscriptDir, 'drafts'), { extensions: ['.md'], recursive: true });
|
|
366
|
-
const planCount = countMarkdownFiles(path.join(manuscriptDir, 'plans'));
|
|
367
716
|
const reviewFiles = scanReviewSignals(manuscriptDir);
|
|
717
|
+
const allReviewFiles = listFiles(path.join(manuscriptDir, 'reviews'), { extensions: ['.md', '.txt'], recursive: true });
|
|
368
718
|
const historySignal = detectHistorySignal(manuscriptDir);
|
|
369
719
|
const sourceFiles = [
|
|
370
720
|
statePath,
|
|
@@ -379,21 +729,28 @@ function analyzeProject(projectRoot = process.cwd(), options = {}) {
|
|
|
379
729
|
hasState: pathExists(statePath),
|
|
380
730
|
context: detectContextSignal(manuscriptDir, draftFiles),
|
|
381
731
|
history: historySignal,
|
|
732
|
+
readiness: detectProjectReadiness(manuscriptDir),
|
|
733
|
+
plan: detectPlanSignal(manuscriptDir, draftFiles),
|
|
382
734
|
reviews: {
|
|
383
735
|
state: reviewFiles.length ? 'pending' : 'none',
|
|
384
736
|
count: reviewFiles.length,
|
|
385
737
|
files: reviewFiles,
|
|
386
738
|
},
|
|
739
|
+
reviewCoverage: detectReviewCoverage(draftFiles, allReviewFiles),
|
|
740
|
+
notes: detectNotesSignal(manuscriptDir),
|
|
741
|
+
tracks: detectTrackSignal(manuscriptDir),
|
|
387
742
|
translation: detectTranslationSignal(manuscriptDir, config),
|
|
388
743
|
export: detectExportSignal(manuscriptDir, sourceFiles),
|
|
744
|
+
publishing: detectPublishingSignal(manuscriptDir, draftFiles),
|
|
389
745
|
save: detectSaveSignal(historySignal, draftFiles),
|
|
390
746
|
};
|
|
391
747
|
const counts = {
|
|
392
748
|
drafts: draftFiles.length,
|
|
393
|
-
plans:
|
|
749
|
+
plans: signals.plan.count,
|
|
394
750
|
reviews: reviewFiles.length,
|
|
395
751
|
};
|
|
396
752
|
const recommendation = chooseRecommendation(signals, counts);
|
|
753
|
+
const automation = buildAutomationPlan(signals, recommendation);
|
|
397
754
|
return {
|
|
398
755
|
projectRoot: root,
|
|
399
756
|
manuscriptDir,
|
|
@@ -402,6 +759,7 @@ function analyzeProject(projectRoot = process.cwd(), options = {}) {
|
|
|
402
759
|
counts,
|
|
403
760
|
signals,
|
|
404
761
|
recommendation,
|
|
762
|
+
automation,
|
|
405
763
|
};
|
|
406
764
|
}
|
|
407
765
|
|
|
@@ -413,9 +771,15 @@ function formatProactiveChecks(analysis) {
|
|
|
413
771
|
return [
|
|
414
772
|
'Proactive checks:',
|
|
415
773
|
stateLine,
|
|
774
|
+
` Readiness: ${signals.readiness?.state || 'none'}${signals.readiness?.missing?.length ? `, missing ${signals.readiness.missing.join(', ')}` : ''}`,
|
|
416
775
|
` Session: ${signals.context.state}${signals.context.suggest ? `, suggest ${signals.context.suggest}` : ''}`,
|
|
776
|
+
` Plans: ${signals.plan?.state || 'none'}${signals.plan?.suggest ? `, suggest ${signals.plan.suggest}` : ''}`,
|
|
417
777
|
` Reviews: ${signals.reviews.count ? `${signals.reviews.count} pending, suggest /scr:editor-review` : 'none'}`,
|
|
778
|
+
` Review coverage: ${signals.reviewCoverage?.state || 'none'}${signals.reviewCoverage?.suggest ? `, suggest ${signals.reviewCoverage.suggest}` : ''}`,
|
|
779
|
+
` Notes: ${signals.notes?.count ? `${signals.notes.count} pending, suggest ${signals.notes.suggest}` : 'none'}`,
|
|
780
|
+
` Tracks: ${signals.tracks?.state || 'none'}${signals.tracks?.suggest ? `, suggest ${signals.tracks.suggest}` : ''}`,
|
|
418
781
|
` Translation: ${signals.translation.state}`,
|
|
782
|
+
` Publishing: ${signals.publishing?.state || 'none'}${signals.publishing?.gaps?.length ? `, gaps ${signals.publishing.gaps.join(', ')}` : ''}`,
|
|
419
783
|
` Export: ${signals.export.state}${signals.export.suggest ? `, suggest ${signals.export.suggest}` : ''}`,
|
|
420
784
|
` Save: ${signals.save.state}${signals.save.suggest ? `, suggest ${signals.save.suggest}` : ''}`,
|
|
421
785
|
].join('\n');
|
|
@@ -425,14 +789,31 @@ function formatAutomationStatus(analysis, options = {}) {
|
|
|
425
789
|
const trigger = options.trigger || '/scr:next';
|
|
426
790
|
const localOperation = options.localOperation || 'auto-invoke engine: read-only';
|
|
427
791
|
const autoInvoked = options.autoInvoked || `${analysis.recommendation.command}: no`;
|
|
792
|
+
const automation = analysis.automation || { mode: 'read-only', spawnCandidates: [], localCandidates: [], manualGates: [] };
|
|
793
|
+
const candidateAgentLines = automation.spawnCandidates.length
|
|
794
|
+
? automation.spawnCandidates.map((candidate) => `- ${candidate.command}: ${candidate.agents.join(', ')} (${candidate.reason})`)
|
|
795
|
+
: ['- none'];
|
|
796
|
+
const localCandidateLines = automation.localCandidates.length
|
|
797
|
+
? automation.localCandidates.map((candidate) => `- ${candidate.command}: ${candidate.reason}`)
|
|
798
|
+
: ['- none'];
|
|
799
|
+
const manualGateLines = automation.manualGates.length
|
|
800
|
+
? automation.manualGates.map((gate) => `- ${gate.command}: ${gate.reason}`)
|
|
801
|
+
: ['- none'];
|
|
428
802
|
return [
|
|
429
803
|
'Automation status:',
|
|
430
804
|
`Trigger: ${trigger}`,
|
|
805
|
+
`Mode: ${automation.mode}`,
|
|
431
806
|
'Spawned agents:',
|
|
432
807
|
'- none',
|
|
808
|
+
'Candidate agents:',
|
|
809
|
+
...candidateAgentLines,
|
|
433
810
|
'Local operations:',
|
|
434
811
|
`- ${localOperation}`,
|
|
435
812
|
`- state route computed: ${analysis.signals.hasProject ? 'yes' : 'no project'}`,
|
|
813
|
+
'Candidate local helpers:',
|
|
814
|
+
...localCandidateLines,
|
|
815
|
+
'Manual gates:',
|
|
816
|
+
...manualGateLines,
|
|
436
817
|
'Auto-invoked:',
|
|
437
818
|
`- ${autoInvoked}`,
|
|
438
819
|
`Why: ${analysis.recommendation.reason}`,
|
|
@@ -508,12 +889,17 @@ if (require.main === module) {
|
|
|
508
889
|
}
|
|
509
890
|
|
|
510
891
|
module.exports = {
|
|
892
|
+
AGENT_ROUTE_POLICIES,
|
|
893
|
+
CATEGORY_ROUTE_POLICIES,
|
|
511
894
|
DEFAULT_RUNTIME_SUPPORT,
|
|
895
|
+
LOCAL_ROUTE_POLICIES,
|
|
896
|
+
MANUAL_ROUTE_POLICIES,
|
|
512
897
|
analyzeProject,
|
|
513
898
|
formatProactiveChecks,
|
|
514
899
|
formatAutomationStatus,
|
|
515
900
|
formatRecommendation,
|
|
516
901
|
formatReport,
|
|
902
|
+
getCommandAutomationPolicy,
|
|
517
903
|
getRuntimeAgentSupport,
|
|
518
904
|
listRuntimeAgentSupport,
|
|
519
905
|
parseCliArgs,
|
package/package.json
CHANGED
package/templates/config.json
CHANGED
|
@@ -1,30 +1,26 @@
|
|
|
1
1
|
{
|
|
2
|
-
"scriveno_version": "2.0.
|
|
2
|
+
"scriveno_version": "2.0.9",
|
|
3
3
|
"work_type": "",
|
|
4
4
|
"group": "",
|
|
5
5
|
"command_unit": "",
|
|
6
6
|
"developer_mode": false,
|
|
7
7
|
"created_at": "",
|
|
8
8
|
"updated_at": "",
|
|
9
|
-
|
|
10
9
|
"autopilot": {
|
|
11
10
|
"enabled": false,
|
|
12
11
|
"profile": "guided",
|
|
13
12
|
"custom_checkpoints": []
|
|
14
13
|
},
|
|
15
|
-
|
|
16
14
|
"voice": {
|
|
17
15
|
"calibrated": false,
|
|
18
16
|
"last_calibration": null,
|
|
19
17
|
"drift_threshold": 0.3
|
|
20
18
|
},
|
|
21
|
-
|
|
22
19
|
"draft": {
|
|
23
20
|
"rigor": "standard",
|
|
24
21
|
"context_profile": "standard",
|
|
25
22
|
"pitfalls_enabled": true
|
|
26
23
|
},
|
|
27
|
-
|
|
28
24
|
"export": {
|
|
29
25
|
"default_format": "docx_manuscript",
|
|
30
26
|
"include_front_matter": true,
|
|
@@ -36,14 +32,12 @@
|
|
|
36
32
|
"margins": "1in"
|
|
37
33
|
}
|
|
38
34
|
},
|
|
39
|
-
|
|
40
35
|
"translation": {
|
|
41
36
|
"source_language": "en",
|
|
42
37
|
"target_languages": [],
|
|
43
38
|
"name_handling": "keep_original",
|
|
44
39
|
"measurement_system": "source"
|
|
45
40
|
},
|
|
46
|
-
|
|
47
41
|
"technical": {
|
|
48
42
|
"audience_level": "mixed",
|
|
49
43
|
"prerequisite_knowledge": [],
|
|
@@ -52,18 +46,15 @@
|
|
|
52
46
|
"source_of_truth": [],
|
|
53
47
|
"review_mode": "accuracy_first"
|
|
54
48
|
},
|
|
55
|
-
|
|
56
49
|
"collaboration": {
|
|
57
50
|
"tracks_enabled": false,
|
|
58
51
|
"default_track": "canon"
|
|
59
52
|
},
|
|
60
|
-
|
|
61
53
|
"illustration": {
|
|
62
54
|
"style": null,
|
|
63
55
|
"cover_enabled": false,
|
|
64
56
|
"interior_enabled": false
|
|
65
57
|
},
|
|
66
|
-
|
|
67
58
|
"git": {
|
|
68
59
|
"auto_commit": true,
|
|
69
60
|
"commit_message_style": "descriptive",
|