@cyber-dash-tech/revela 0.15.4 → 0.16.2
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/lib/commands/narrative.ts +9 -3
- package/lib/commands/research.ts +25 -18
- package/lib/commands/review.ts +2 -1
- package/lib/decks-state.ts +142 -4
- package/lib/narrative-state/display.ts +40 -0
- package/lib/narrative-state/map-html.ts +133 -6
- package/lib/narrative-state/map.ts +333 -16
- package/lib/narrative-state/render-plan.ts +254 -84
- package/lib/narrative-state/research-gaps.ts +332 -0
- package/package.json +1 -1
- package/tools/decks.ts +7 -2
- package/tools/narrative-view.ts +11 -1
|
@@ -124,7 +124,13 @@ export function buildNarrativeViewPrompt(options: { workspaceRoot: string; langu
|
|
|
124
124
|
})),
|
|
125
125
|
relations: map.claimRelations.map((relation) => ({ id: relation.id, fromClaimId: relation.fromClaimId, toClaimId: relation.toClaimId, relation: relation.relation, rationale: relation.rationale, inferred: relation.inferred })),
|
|
126
126
|
researchGaps: map.researchGaps.map((gap) => ({ id: gap.id, targetType: gap.targetType, targetId: gap.targetId, status: gap.status, priority: gap.priority, question: gap.question })),
|
|
127
|
-
artifactCoverage: map.artifactCoverage.map((artifact) => ({ type: artifact.type, outputPath: artifact.outputPath, stale: artifact.stale, slideRefs: artifact.slideRefs.map((ref) => ({ claimId: ref.claimId, slideIndex: ref.slideIndex, role: ref.role, match: ref.match, location: ref.location })) })),
|
|
127
|
+
artifactCoverage: map.artifactCoverage.map((artifact) => ({ type: artifact.type, outputPath: artifact.outputPath, stale: artifact.stale, coverageStatus: artifact.coverageStatus, affectedClaimIds: artifact.affectedClaimIds, missingClaimIds: artifact.missingClaimIds, slideRefs: artifact.slideRefs.map((ref) => ({ claimId: ref.claimId, slideIndex: ref.slideIndex, role: ref.role, match: ref.match, location: ref.location })) })),
|
|
128
|
+
workbench: {
|
|
129
|
+
summary: map.workbench.summary,
|
|
130
|
+
filters: map.workbench.filters,
|
|
131
|
+
renderTargetAction: map.workbench.renderTargetAction,
|
|
132
|
+
artifactCoverage: map.workbench.artifactCoverage.map((item) => ({ type: item.type, outputPath: item.outputPath, coverageStatus: item.coverageStatus, affectedClaimIds: item.affectedClaimIds, missingClaimIds: item.missingClaimIds, statusNote: item.statusNote, recommendedNextCommand: item.recommendedNextCommand })),
|
|
133
|
+
},
|
|
128
134
|
}
|
|
129
135
|
|
|
130
136
|
return `Prepare the read-only Revela narrative UI display model.
|
|
@@ -141,11 +147,11 @@ Hard rules:
|
|
|
141
147
|
- Do not invent new claims, evidence, relations, slide coverage, source paths, findings files, quotes, or caveats.
|
|
142
148
|
- Preserve every claimId exactly.
|
|
143
149
|
- Preserve every relation endpoint exactly: fromClaimId, toClaimId, relation.
|
|
144
|
-
- You may only organize and localize display copy for the UI: pageTitle, summaryLine, section labels, claim card displayTitle, roleLabel, narrativeJob, evidenceSummary, riskOrGapSummary, relation displayLabel, and relation displayRationale.
|
|
150
|
+
- You may only organize and localize display copy for the UI: pageTitle, summaryLine, section labels including Story workbench labels, claim card displayTitle, roleLabel, narrativeJob, evidenceSummary, riskOrGapSummary, relation displayLabel, and relation displayRationale.
|
|
145
151
|
- For inferred relations, do not provide relation displayLabel or displayRationale; inferred relations are unconfirmed order notes, not causal/support/dependency judgments.
|
|
146
152
|
- relation displayRationale may only localize or clarify an existing canonical relation rationale. If relation.rationale is missing or the relation is inferred, do not provide displayRationale; the UI will show the missing or inferred status.
|
|
147
153
|
- Keep source paths, findings files, claim IDs, narrative hash, and numbers unchanged.
|
|
148
|
-
- Translate normal UI/display text into the target language request: pageTitle, summaryLine, labels, claim displayTitle, roleLabel, narrativeJob, evidenceSummary, riskOrGapSummary, relation displayLabel, and relation displayRationale.
|
|
154
|
+
- Translate normal UI/display text into the target language request: pageTitle, summaryLine, labels including workbench labels, claim displayTitle, roleLabel, narrativeJob, evidenceSummary, riskOrGapSummary, relation displayLabel, and relation displayRationale.
|
|
149
155
|
- Do not translate claim IDs, relation endpoints, narrative hash, source paths, findings files, URLs, numbers, or quoted/source facts.
|
|
150
156
|
- Use natural business and manufacturing terminology in the target language, not word-by-word machine translation.
|
|
151
157
|
- If a fact is missing, describe it as missing instead of filling it in.
|
package/lib/commands/research.ts
CHANGED
|
@@ -25,18 +25,19 @@ Current state:
|
|
|
25
25
|
${workspaceRoot ? `- Current workspace root: \`${workspaceRoot}\`` : ""}
|
|
26
26
|
|
|
27
27
|
Closed-loop workflow:
|
|
28
|
-
1. Call \`revela-decks\` action \`read\`, then \`reviewNarrative\`.
|
|
29
|
-
2. If current research gaps are missing or stale, call \`deriveResearchGaps\` when useful. Do not invent gaps that are not tied to a claim, objection, risk, decision, or narrative issue.
|
|
28
|
+
1. Call \`revela-decks\` action \`read\`, then \`reviewNarrative\`, then \`deriveResearchTargets\`. Treat the returned \`selected\` target as the deterministic first target unless it is clearly blocked by user-only information.
|
|
29
|
+
2. If current research gaps are missing or stale, call \`deriveResearchGaps\` when useful, then call \`deriveResearchTargets\` again. Do not invent gaps that are not tied to a claim, objection, risk, decision, or narrative issue.
|
|
30
30
|
3. Run up to 3 research loops unless the stop conditions below are met earlier.
|
|
31
|
-
4. At the start of each loop,
|
|
32
|
-
5. If a target
|
|
33
|
-
6.
|
|
34
|
-
7.
|
|
35
|
-
8.
|
|
36
|
-
9.
|
|
37
|
-
10.
|
|
38
|
-
11.
|
|
39
|
-
12.
|
|
31
|
+
4. At the start of each loop, use \`deriveResearchTargets\` as the target order. Work the \`selected\` target first, then the next 1-2 highest-priority targets only when they are related. Do not repeat searches for claims already strongly supported.
|
|
32
|
+
5. If a target has \`findingsFile\` or \`kind: "unattached_findings"\`, inspect \`bindingDiagnostic\` before doing external search. Prefer existing findings before external research.
|
|
33
|
+
6. When \`bindingDiagnostic.bindable\` is false, do not bind or package the findings as strong evidence. Report the exact \`failureReasons\` such as \`missing_quote\`, \`unclear_source\`, \`unsupported_scope\`, \`caveat_conflict\`, \`weak_source\`, \`source_mismatch\`, or \`context_only_finding\`, then either narrow the claim safely or run targeted research for the missing fields.
|
|
34
|
+
7. For targets needing external evidence, mark matching gaps \`in_progress\` with \`revela-decks updateResearchGap\`, then delegate search to the \`revela-research\` subagent. Ask it for source URLs, quotes/snippets, dates or locations when available, caveats, remaining gaps, and a \`## Recommended evidence bindings\` section with claimId, quote, source, supportScope, unsupportedScope, caveat, and strength. Save findings with \`revela-research-save\` under \`researches/{topic}/{axis}.md\` using \`## Data\`, \`## Cases\`, \`## Images\`, and \`## Gaps\` sections as applicable.
|
|
35
|
+
8. After findings are saved or existing findings are selected, read or inspect the findings file. Attach it with \`revela-decks attachResearchFindings\` when it maps to an existing research axis. Re-run \`deriveResearchTargets\` so the next loop sees updated \`bindingDiagnostic\` and target order.
|
|
36
|
+
9. Automatically bind evidence only when all binding criteria are met and the diagnostic is \`bindable: true\` or the same fields are explicit in the findings. Use \`revela-decks applyEvidenceCandidates\` for concrete candidate ids when available, or \`upsertNarrative\` to preserve canonical evidence bindings with exact source, URL/path, quote/snippet, support scope, unsupported scope, caveat, and strength.
|
|
37
|
+
10. Binding criteria: claimId exists; quote/snippet is traceable to the source and is not invented; source URL or workspace source path is present; supportScope and unsupportedScope are explicit; strength is strong or useful partial; caveat is preserved; binding does not expand the claim beyond the evidence.
|
|
38
|
+
11. If a claim or relation is broader than the evidence, narrow the claim text, supportedScope, unsupportedScope, or relation rationale through \`upsertNarrative\` only when the narrower wording preserves the user's strategic meaning and source boundaries. Do not silently change the decision ask, central recommendation, or approval meaning.
|
|
39
|
+
12. Update matching gaps after binding: use \`evidence_bound\` when canonical evidence was added, \`closed\` when the gap is resolved or non-researchable, \`findings_saved\` only when findings exist but binding criteria are not met, and \`open\` with notes when more external research is still warranted.
|
|
40
|
+
13. Re-run \`reviewNarrative\` and \`deriveResearchTargets\` after each loop. Compare against the previous loop: fewer open gaps, fewer unattached findings, stronger evidence, narrower unsupported scope, or clearer internal-data caveats should count as progress.
|
|
40
41
|
|
|
41
42
|
Stop conditions:
|
|
42
43
|
- No open externally researchable gaps remain.
|
|
@@ -47,20 +48,26 @@ Stop conditions:
|
|
|
47
48
|
|
|
48
49
|
Report format:
|
|
49
50
|
- Start with \`Research loop completed after <n> round(s).\`
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
-
|
|
51
|
+
- Then use these exact sections in order:
|
|
52
|
+
- \`Selected target\`: report \`kind\`, \`priority\`, \`reason\`, \`question\`, \`targetId\`, \`claimId\`, and any \`findingsFile\`.
|
|
53
|
+
- \`Existing findings inspected\`: for each file, report \`findingsFile\`, \`bindingDiagnostic.bindable\`, \`failureReasons\`, and which explicit fields were present: \`source\`, \`quoteOrSnippet\`, \`supportScope\`, \`unsupportedScope\`, \`caveat\`, \`strength\`. If none were inspected, write \`none\`.
|
|
54
|
+
- \`Attachments\`: list findings attached with axis/status, or \`none\`.
|
|
55
|
+
- \`Evidence bound\`: list evidence bindings by claim id, source, quote/snippet, supportScope, unsupportedScope, caveat, and strength, or \`none\`.
|
|
56
|
+
- \`Unbound findings\`: list every inspected but unbound findings file with structured failure reasons such as \`missing_quote\`, \`unclear_source\`, \`unsupported_scope\`, \`caveat_conflict\`, \`weak_source\`, \`source_mismatch\`, or \`context_only_finding\`. If none, write \`none\`.
|
|
57
|
+
- \`Gap updates\`: list gaps moved to \`in_progress\`, \`findings_saved\`, \`attached\`, \`evidence_bound\`, \`closed\`, or still \`open\` with notes.
|
|
58
|
+
- \`Narrative changes\`: list claims or relations narrowed, with remaining unsupported scope. If none, write \`none\`.
|
|
59
|
+
- \`Remaining caveats\`: use only \`internal_data_needed\`, \`not_publicly_researchable\`, \`source_quality_limit\`, or \`still_open\`.
|
|
60
|
+
- \`Next smallest story action\`: end with one concrete next command or action, not a generic request for confirmation.
|
|
61
|
+
- If no binding happened, the \`Unbound findings\` or \`Remaining caveats\` section must say why binding criteria failed and what exact source type is needed next.
|
|
56
62
|
|
|
57
63
|
Rules:
|
|
58
64
|
- Do not use primary-agent broad websearch. Use the \`revela-research\` subagent for external search.
|
|
59
65
|
- Do not invent quotes, source paths, URLs, page references, locations, or caveats.
|
|
60
66
|
- Do not treat \`researches/**/*.md\` as canonical evidence until attached or evidence-bound, but do not stop at findings_saved when binding criteria are met.
|
|
67
|
+
- Do not bypass \`deriveResearchTargets\`; target selection, \`selected\`, and \`bindingDiagnostic\` are deterministic inputs, not LLM judgement.
|
|
61
68
|
- Do not mutate canonical claims merely to fit a source; narrow only to preserve evidence boundaries and avoid overstated claims.
|
|
62
69
|
- Do not ask the user to approve each evidence binding. Ask only when binding would change strategic meaning, downgrade a central claim, rely on suspicious sources, or require narrative approval.
|
|
63
70
|
- Do not store secrets, credentials, tokens, or sensitive personal information.
|
|
64
71
|
|
|
65
|
-
Start now by reading ${DECKS_STATE_FILE} through \`revela-decks\`, reviewing current readiness, and running the first research/binding loop.`
|
|
72
|
+
Start now by reading ${DECKS_STATE_FILE} through \`revela-decks\`, reviewing current readiness, deriving research targets, and running the first research/binding loop from the selected target.`
|
|
66
73
|
}
|
package/lib/commands/review.ts
CHANGED
|
@@ -205,7 +205,7 @@ Workflow:
|
|
|
205
205
|
8. For substantial decision decks, preserve a compact \`narrativeBrief\` through \`upsertDeck\` when the conversation or confirmed plan supports it. Do not invent stakeholder beliefs, objections, or risks; leave gaps visible if unknown.
|
|
206
206
|
9. For substantial decision decks, launch the Task subagent with \`subagent_type: "revela-narrative-reviewer"\` after deck/slides are up to date. Ask it to read the current \`DECKS.json\`, run only its fixed rubric, use stable finding IDs, return \`Findings: none\` when all checks pass, and avoid optional pre-write improvements. Do not ask it to write state, call \`revela-decks review\`, or produce HTML.
|
|
207
207
|
10. Call \`revela-decks\` action \`review\`. The tool computes and writes \`writeReadiness\` plus structured readiness issues for the current workspace deck.
|
|
208
|
-
11. Briefly report whether the deck is ready. If blocked, list the exact blockers returned by the tool. If warnings exist, list them after blockers as residual risks; separate evidence/source warnings from narrative warnings when possible. If the review result includes \`evidenceCandidates\`, add a separate \`Candidate evidence bindings\` section with candidateId, slide index/title, supported claim scope, sourceKind, findingsFile/sourcePath, quote/snippet, caveat, evidenceDraft summary, unsupportedScope, and recommendedRewrite. Tell the user they may explicitly ask to apply selected candidate IDs; do not apply them during review. If candidates are absent but \`evidenceCandidateSearch\` is present, briefly report searched file counts and the best near misses so the user can tell whether review failed to search or searched but did not find a bindable match. If the reviewer returned findings, include them in a separate \`Narrative reviewer notes\` section and label them advisory.
|
|
208
|
+
11. Briefly report whether the deck is ready. If blocked, list the exact blockers returned by the tool. If warnings exist, list them after blockers as residual risks; separate evidence/source warnings from narrative warnings when possible. If the review result includes \`diagnostics\`, include a \`Plan and coverage diagnostics\` section with plan quality blockers/warnings, artifact \`coverageStatus\`, \`missingClaimIds\`, \`affectedClaimIds\`, stale reasons, and \`nextActions\`. If the review result includes \`evidenceCandidates\`, add a separate \`Candidate evidence bindings\` section with candidateId, slide index/title, supported claim scope, sourceKind, findingsFile/sourcePath, quote/snippet, caveat, evidenceDraft summary, unsupportedScope, and recommendedRewrite. Tell the user they may explicitly ask to apply selected candidate IDs; do not apply them during review. If candidates are absent but \`evidenceCandidateSearch\` is present, briefly report searched file counts and the best near misses so the user can tell whether review failed to search or searched but did not find a bindable match. If the reviewer returned findings, include them in a separate \`Narrative reviewer notes\` section and label them advisory.
|
|
209
209
|
|
|
210
210
|
Minimum conditions for \`ready\`:
|
|
211
211
|
- Topic, audience, slide count, language, and visual style/design are decided.
|
|
@@ -225,6 +225,7 @@ Report format:
|
|
|
225
225
|
- Start with \`Ready: yes/no\`.
|
|
226
226
|
- If blocked, list each blocker with slide index/title when the tool provides it, the issue type, and the suggested next action.
|
|
227
227
|
- If warnings exist but the deck is otherwise ready, say the deck can be written but note the residual risks.
|
|
228
|
+
- Include coverage-driven make diagnostics when returned: whether the active deck artifact coverage is current/stale/partial/missing, which required claims are missing, which claims are affected, and the next command/action recommended by the tool.
|
|
228
229
|
- Report \`narrative_gap\` warnings as story-structure risks such as weak so-what, missing risk/assumption handling, conclusion before support, missing audience framing, or abrupt transition.
|
|
229
230
|
- Do not invent evidence or silently downgrade blockers. Use the tool result as authoritative.
|
|
230
231
|
- Do not convert \`revela-narrative-reviewer\` advisory findings into tool readiness issues. Keep them separate from \`revela-decks review\` blockers and warnings, and preserve the reviewer's stable finding IDs when reporting them.
|
package/lib/decks-state.ts
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
workspaceStatePath,
|
|
9
9
|
writeWorkspaceState,
|
|
10
10
|
} from "./workspace-state/repository"
|
|
11
|
-
import { ensureActiveHtmlDeckRenderTarget } from "./workspace-state/render-targets"
|
|
11
|
+
import { activeHtmlDeckRenderTarget, ensureActiveHtmlDeckRenderTarget } from "./workspace-state/render-targets"
|
|
12
12
|
import {
|
|
13
13
|
activeReviewTargetId,
|
|
14
14
|
appendReviewSnapshot,
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
import { WORKSPACE_STATE_FILE, type RenderTarget, type ReviewSnapshot, type WorkspaceAction } from "./workspace-state/types"
|
|
20
20
|
import { normalizeCanonicalNarrativeState, normalizeNarrativeState } from "./narrative-state/normalize"
|
|
21
21
|
import { computeNarrativeHash } from "./narrative-state/hash"
|
|
22
|
+
import { getArtifactClaimRefs } from "./narrative-state/queries"
|
|
22
23
|
import type { NarrativeStateV1 } from "./narrative-state/types"
|
|
23
24
|
|
|
24
25
|
export const DECKS_STATE_FILE = WORKSPACE_STATE_FILE
|
|
@@ -107,6 +108,13 @@ export interface DeckPlanReview {
|
|
|
107
108
|
confirmedAt?: string
|
|
108
109
|
confirmedBy?: "user"
|
|
109
110
|
summary?: string
|
|
111
|
+
qualityChecks?: DeckPlanQualityCheck[]
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface DeckPlanQualityCheck {
|
|
115
|
+
id: string
|
|
116
|
+
status: "pass" | "warning" | "blocker"
|
|
117
|
+
message: string
|
|
110
118
|
}
|
|
111
119
|
|
|
112
120
|
export interface NarrativeBrief {
|
|
@@ -206,6 +214,24 @@ export interface DeckStateReadinessResult {
|
|
|
206
214
|
warnings: string[]
|
|
207
215
|
issues: ReadinessIssue[]
|
|
208
216
|
evidenceCandidates?: EvidenceBindingCandidate[]
|
|
217
|
+
diagnostics?: DeckReadinessDiagnostics
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export interface DeckReadinessDiagnostics {
|
|
221
|
+
planQuality: DeckPlanQualityCheck[]
|
|
222
|
+
artifactCoverage?: ArtifactCoverageDiagnostic
|
|
223
|
+
nextActions: string[]
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export interface ArtifactCoverageDiagnostic {
|
|
227
|
+
artifactId?: string
|
|
228
|
+
outputPath?: string
|
|
229
|
+
coverageStatus: "current" | "stale" | "partial" | "missing" | "unknown"
|
|
230
|
+
requiredClaimIds: string[]
|
|
231
|
+
coveredClaimIds: string[]
|
|
232
|
+
missingClaimIds: string[]
|
|
233
|
+
affectedClaimIds: string[]
|
|
234
|
+
staleReasons: string[]
|
|
209
235
|
}
|
|
210
236
|
|
|
211
237
|
export type ReadinessSeverity = "blocker" | "warning"
|
|
@@ -214,6 +240,8 @@ export type ReadinessIssueType =
|
|
|
214
240
|
| "missing_required_input"
|
|
215
241
|
| "missing_slide_spec"
|
|
216
242
|
| "slide_plan_unconfirmed"
|
|
243
|
+
| "plan_quality"
|
|
244
|
+
| "artifact_coverage"
|
|
217
245
|
| "research_not_ready"
|
|
218
246
|
| "missing_evidence"
|
|
219
247
|
| "weak_evidence"
|
|
@@ -444,6 +472,7 @@ export function confirmDeckPlan(state: DecksState, options: ConfirmDeckPlanOptio
|
|
|
444
472
|
confirmedAt: options.now ?? new Date().toISOString(),
|
|
445
473
|
confirmedBy: options.approvedBy ?? "user",
|
|
446
474
|
summary: cleanOptionalText(options.note),
|
|
475
|
+
qualityChecks: pending?.qualityChecks,
|
|
447
476
|
}
|
|
448
477
|
deck.requiredInputs = { ...deck.requiredInputs, slidePlanConfirmed: true }
|
|
449
478
|
deck.writeReadiness = { status: "blocked", blockers: [] }
|
|
@@ -579,7 +608,7 @@ export function reviewDeckState(state: DecksState, slug?: string, options: Revie
|
|
|
579
608
|
}
|
|
580
609
|
}
|
|
581
610
|
|
|
582
|
-
const issues = computeDeckReadinessIssues(
|
|
611
|
+
const issues = computeDeckReadinessIssues(normalized, deck, {
|
|
583
612
|
...options,
|
|
584
613
|
narrativeHash: options.narrativeHash ?? computeNarrativeHash(normalizeNarrativeState(normalized)),
|
|
585
614
|
})
|
|
@@ -604,6 +633,7 @@ export function reviewDeckState(state: DecksState, slug?: string, options: Revie
|
|
|
604
633
|
warnings,
|
|
605
634
|
issues,
|
|
606
635
|
evidenceCandidates,
|
|
636
|
+
diagnostics: deckReadinessDiagnostics(normalized, deck, issues),
|
|
607
637
|
}
|
|
608
638
|
appendReviewSnapshot(normalized, createReviewSnapshot(normalized, { slug: deck.slug, result, reviewedAt }))
|
|
609
639
|
return {
|
|
@@ -639,7 +669,7 @@ export function evaluateDeckStateWriteReadiness(state: DecksState, filePath: str
|
|
|
639
669
|
}
|
|
640
670
|
}
|
|
641
671
|
|
|
642
|
-
const issues = computeDeckReadinessIssues(
|
|
672
|
+
const issues = computeDeckReadinessIssues(normalized, deck, {
|
|
643
673
|
narrativeHash: computeNarrativeHash(normalizeNarrativeState(normalized)),
|
|
644
674
|
})
|
|
645
675
|
const blockers = issues.filter((issue) => issue.severity === "blocker").map((issue) => issue.message)
|
|
@@ -715,6 +745,7 @@ export function evaluateDeckStateWriteReadiness(state: DecksState, filePath: str
|
|
|
715
745
|
blockers,
|
|
716
746
|
warnings,
|
|
717
747
|
issues,
|
|
748
|
+
diagnostics: deckReadinessDiagnostics(normalized, deck, issues),
|
|
718
749
|
}
|
|
719
750
|
}
|
|
720
751
|
|
|
@@ -871,9 +902,23 @@ function normalizeDeckPlanReview(input: DeckPlanReview | undefined): DeckPlanRev
|
|
|
871
902
|
confirmedAt: cleanOptionalText(input.confirmedAt),
|
|
872
903
|
confirmedBy: input.confirmedBy === "user" ? "user" : undefined,
|
|
873
904
|
summary: cleanOptionalText(input.summary),
|
|
905
|
+
qualityChecks: normalizeDeckPlanQualityChecks(input.qualityChecks),
|
|
874
906
|
}
|
|
875
907
|
}
|
|
876
908
|
|
|
909
|
+
function normalizeDeckPlanQualityChecks(input: DeckPlanQualityCheck[] | undefined): DeckPlanQualityCheck[] | undefined {
|
|
910
|
+
if (!Array.isArray(input)) return undefined
|
|
911
|
+
const checks = input.flatMap((item): DeckPlanQualityCheck[] => {
|
|
912
|
+
if (!item || typeof item !== "object") return []
|
|
913
|
+
const id = cleanOptionalText(item.id)
|
|
914
|
+
const message = cleanOptionalText(item.message)
|
|
915
|
+
if (!id || !message) return []
|
|
916
|
+
const status = item.status === "blocker" || item.status === "warning" ? item.status : "pass"
|
|
917
|
+
return [{ id, status, message }]
|
|
918
|
+
})
|
|
919
|
+
return checks.length > 0 ? checks : undefined
|
|
920
|
+
}
|
|
921
|
+
|
|
877
922
|
function currentDeckKey(state: DecksState): string | undefined {
|
|
878
923
|
if (state.activeDeck && state.decks[state.activeDeck]) return state.activeDeck
|
|
879
924
|
const keys = Object.keys(state.decks)
|
|
@@ -887,8 +932,9 @@ function currentDeckBlocker(state: DecksState): string {
|
|
|
887
932
|
return `${DECKS_STATE_FILE} contains multiple deck records and no activeDeck. Select one current deck explicitly or move extra decks to separate workspaces.`
|
|
888
933
|
}
|
|
889
934
|
|
|
890
|
-
function computeDeckReadinessIssues(
|
|
935
|
+
function computeDeckReadinessIssues(state: DecksState, deck: DeckSpec, options: ReviewDeckStateOptions = {}): ReadinessIssue[] {
|
|
891
936
|
const issues: ReadinessIssue[] = []
|
|
937
|
+
const workspace = state.workspace
|
|
892
938
|
if (!deck.goal.trim()) issues.push(blockerIssue("missing_slide_spec", "Deck goal is missing", "Set the deck goal through revela-decks upsertDeck."))
|
|
893
939
|
if (!isDeckHtmlPath(deck.outputPath)) {
|
|
894
940
|
issues.push(blockerIssue(
|
|
@@ -919,6 +965,8 @@ function computeDeckReadinessIssues(deck: DeckSpec, workspace: DecksState["works
|
|
|
919
965
|
))
|
|
920
966
|
}
|
|
921
967
|
}
|
|
968
|
+
issues.push(...deckPlanQualityIssues(deck))
|
|
969
|
+
issues.push(...artifactCoverageIssues(state, deck))
|
|
922
970
|
for (const slide of deck.slides) {
|
|
923
971
|
const slideRef = { slideIndex: slide.index, slideTitle: slide.title }
|
|
924
972
|
if (!slide.title.trim()) issues.push(blockerIssue("missing_slide_spec", `Slide ${slide.index} title is missing`, "Add a slide title to the slide spec.", slideRef))
|
|
@@ -985,6 +1033,96 @@ function computeDeckReadinessIssues(deck: DeckSpec, workspace: DecksState["works
|
|
|
985
1033
|
return issues
|
|
986
1034
|
}
|
|
987
1035
|
|
|
1036
|
+
function deckPlanQualityIssues(deck: DeckSpec): ReadinessIssue[] {
|
|
1037
|
+
const checks = deck.planReview?.qualityChecks ?? []
|
|
1038
|
+
return checks.flatMap((check): ReadinessIssue[] => {
|
|
1039
|
+
if (check.status === "pass") return []
|
|
1040
|
+
const suggestedAction = check.status === "blocker"
|
|
1041
|
+
? "Re-run compileDeckPlan or revise the deck projection so the deterministic plan includes Cover, TOC, central claim coverage, compatible components, and Closing/Decision Ask before confirming or writing the deck."
|
|
1042
|
+
: "Keep the stated claim boundaries visible in the plan and rendered artifact; do not stretch partial evidence beyond the supported scope."
|
|
1043
|
+
return [{
|
|
1044
|
+
type: "plan_quality",
|
|
1045
|
+
severity: check.status,
|
|
1046
|
+
message: check.message,
|
|
1047
|
+
suggestedAction,
|
|
1048
|
+
}]
|
|
1049
|
+
})
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
function artifactCoverageIssues(state: DecksState, deck: DeckSpec): ReadinessIssue[] {
|
|
1053
|
+
const coverage = artifactCoverageDiagnostic(state, deck)
|
|
1054
|
+
if (!coverage) return []
|
|
1055
|
+
const issues: ReadinessIssue[] = []
|
|
1056
|
+
if (coverage.missingClaimIds.length > 0) {
|
|
1057
|
+
issues.push(blockerIssue(
|
|
1058
|
+
"artifact_coverage",
|
|
1059
|
+
`Active deck plan is missing required narrative claims: ${coverage.missingClaimIds.join(", ")}`,
|
|
1060
|
+
"Re-run compileDeckPlan or revise the deck projection so every central or evidence-required claim appears in the planned slides before writing the deck.",
|
|
1061
|
+
))
|
|
1062
|
+
}
|
|
1063
|
+
if (coverage.coverageStatus === "stale") {
|
|
1064
|
+
issues.push(blockerIssue(
|
|
1065
|
+
"artifact_coverage",
|
|
1066
|
+
`Active deck artifact coverage is stale: ${coverage.staleReasons.join("; ") || "narrative or render target changed"}`,
|
|
1067
|
+
"Re-run /revela make --deck so the deck plan and artifact coverage are regenerated from the current approved narrative state.",
|
|
1068
|
+
))
|
|
1069
|
+
} else if (coverage.coverageStatus === "partial") {
|
|
1070
|
+
issues.push(warningIssue(
|
|
1071
|
+
"artifact_coverage",
|
|
1072
|
+
`Active deck artifact coverage is partial: ${coverage.affectedClaimIds.join(", ") || "some claims are not fully mapped"}`,
|
|
1073
|
+
"Keep the partial coverage visible in the make report and review the affected claims before exporting or presenting the deck.",
|
|
1074
|
+
))
|
|
1075
|
+
}
|
|
1076
|
+
return issues
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
function deckReadinessDiagnostics(state: DecksState, deck: DeckSpec, issues: ReadinessIssue[]): DeckReadinessDiagnostics {
|
|
1080
|
+
const planQuality = deck.planReview?.qualityChecks ?? []
|
|
1081
|
+
const artifactCoverage = artifactCoverageDiagnostic(state, deck)
|
|
1082
|
+
return {
|
|
1083
|
+
planQuality,
|
|
1084
|
+
...(artifactCoverage ? { artifactCoverage } : {}),
|
|
1085
|
+
nextActions: readinessNextActions(issues, artifactCoverage),
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
function artifactCoverageDiagnostic(state: DecksState, deck: DeckSpec): ArtifactCoverageDiagnostic | undefined {
|
|
1090
|
+
const target = activeHtmlDeckRenderTarget(state)
|
|
1091
|
+
const artifact = getArtifactClaimRefs(state).find((item) => item.type === "html_deck" && normalizeDeckPath(item.outputPath ?? "") === normalizeDeckPath(deck.outputPath))
|
|
1092
|
+
const data = target?.data ?? {}
|
|
1093
|
+
const requiredClaimIds = stringArray(data.requiredClaimIds)
|
|
1094
|
+
const coveredClaimIds = stringArray(data.coveredClaimIds)
|
|
1095
|
+
const missingClaimIds = [...new Set([...(artifact?.missingClaimIds ?? []), ...stringArray(data.missingClaimIds)])].sort()
|
|
1096
|
+
const affectedClaimIds = [...new Set([...(artifact?.affectedClaimIds ?? []), ...missingClaimIds])].sort()
|
|
1097
|
+
const staleReasons = artifact?.staleReasons ?? []
|
|
1098
|
+
const coverageStatus = artifact?.coverageStatus ?? (target ? (missingClaimIds.length > 0 ? "missing" : "current") : "unknown")
|
|
1099
|
+
if (!target && !artifact && requiredClaimIds.length === 0 && coveredClaimIds.length === 0 && missingClaimIds.length === 0) return undefined
|
|
1100
|
+
return {
|
|
1101
|
+
artifactId: artifact?.artifactId ?? target?.id,
|
|
1102
|
+
outputPath: artifact?.outputPath ?? target?.outputPath ?? deck.outputPath,
|
|
1103
|
+
coverageStatus,
|
|
1104
|
+
requiredClaimIds: [...new Set([...(artifact?.claimIds ?? []), ...requiredClaimIds, ...missingClaimIds])].filter((id) => requiredClaimIds.length === 0 || requiredClaimIds.includes(id) || missingClaimIds.includes(id)).sort(),
|
|
1105
|
+
coveredClaimIds: [...new Set([...(artifact?.claimIds ?? []), ...coveredClaimIds])].filter((id) => missingClaimIds.length === 0 || !missingClaimIds.includes(id)).sort(),
|
|
1106
|
+
missingClaimIds,
|
|
1107
|
+
affectedClaimIds,
|
|
1108
|
+
staleReasons,
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
function readinessNextActions(issues: ReadinessIssue[], coverage?: ArtifactCoverageDiagnostic): string[] {
|
|
1113
|
+
const actions = issues
|
|
1114
|
+
.filter((issue) => issue.severity === "blocker" || issue.type === "plan_quality" || issue.type === "artifact_coverage")
|
|
1115
|
+
.map((issue) => issue.suggestedAction)
|
|
1116
|
+
if (coverage?.missingClaimIds.length) actions.unshift("Review missingClaimIds in artifactCoverage and recompile the deterministic deck plan before writing HTML.")
|
|
1117
|
+
if (coverage?.coverageStatus === "stale") actions.unshift("Regenerate the deck plan from the current narrative before writing or exporting artifacts.")
|
|
1118
|
+
return [...new Set(actions)].slice(0, 5)
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
function stringArray(value: unknown): string[] {
|
|
1122
|
+
if (!Array.isArray(value)) return []
|
|
1123
|
+
return [...new Set(value.filter((item): item is string => typeof item === "string" && item.trim().length > 0).map((item) => item.trim()))].sort()
|
|
1124
|
+
}
|
|
1125
|
+
|
|
988
1126
|
function findEvidenceBindingCandidates(deck: DeckSpec, slide: SlideSpec, claimText: string, options: ReviewDeckStateOptions): { candidates: EvidenceBindingCandidate[]; search?: EvidenceCandidateSearchDiagnostic } {
|
|
989
1127
|
if (!options.workspaceRoot) return { candidates: [] }
|
|
990
1128
|
const queryText = slideSearchText(slide)
|
|
@@ -29,6 +29,16 @@ export interface NarrativeDisplayLabels {
|
|
|
29
29
|
risks: string
|
|
30
30
|
researchGaps: string
|
|
31
31
|
coveredSlides: string
|
|
32
|
+
storyWorkbench: string
|
|
33
|
+
workbenchNote: string
|
|
34
|
+
artifactCoverage: string
|
|
35
|
+
noRenderTargets: string
|
|
36
|
+
nextActions: string
|
|
37
|
+
missingClaims: string
|
|
38
|
+
affectedClaims: string
|
|
39
|
+
affectedSlides: string
|
|
40
|
+
notes: string
|
|
41
|
+
recommendedNextCommand: string
|
|
32
42
|
noClaims: string
|
|
33
43
|
none: string
|
|
34
44
|
}
|
|
@@ -79,6 +89,16 @@ export function defaultNarrativeDisplayLabels(language: NarrativeViewLanguage):
|
|
|
79
89
|
risks: "风险",
|
|
80
90
|
researchGaps: "研究缺口",
|
|
81
91
|
coveredSlides: "已覆盖页面",
|
|
92
|
+
storyWorkbench: "Story 工作台",
|
|
93
|
+
workbenchNote: "按证据缺口、风险、异议和产物覆盖过滤主张;这里只读展示下一步命令,不修改叙事状态。",
|
|
94
|
+
artifactCoverage: "产物覆盖",
|
|
95
|
+
noRenderTargets: "未记录 render target",
|
|
96
|
+
nextActions: "下一步",
|
|
97
|
+
missingClaims: "缺失主张",
|
|
98
|
+
affectedClaims: "受影响主张",
|
|
99
|
+
affectedSlides: "受影响页面",
|
|
100
|
+
notes: "说明",
|
|
101
|
+
recommendedNextCommand: "建议命令",
|
|
82
102
|
noClaims: "没有记录主张",
|
|
83
103
|
none: "无",
|
|
84
104
|
}
|
|
@@ -101,6 +121,16 @@ export function defaultNarrativeDisplayLabels(language: NarrativeViewLanguage):
|
|
|
101
121
|
risks: "リスク",
|
|
102
122
|
researchGaps: "調査ギャップ",
|
|
103
123
|
coveredSlides: "対応スライド",
|
|
124
|
+
storyWorkbench: "Story ワークベンチ",
|
|
125
|
+
workbenchNote: "根拠ギャップ、リスク、反論、成果物カバレッジでクレームを絞り込みます。ここでは次のコマンドだけを読み取り専用で示し、ナラティブ状態は変更しません。",
|
|
126
|
+
artifactCoverage: "成果物カバレッジ",
|
|
127
|
+
noRenderTargets: "render target は記録されていません",
|
|
128
|
+
nextActions: "次のアクション",
|
|
129
|
+
missingClaims: "不足クレーム",
|
|
130
|
+
affectedClaims: "影響クレーム",
|
|
131
|
+
affectedSlides: "影響スライド",
|
|
132
|
+
notes: "メモ",
|
|
133
|
+
recommendedNextCommand: "推奨コマンド",
|
|
104
134
|
noClaims: "クレームは記録されていません",
|
|
105
135
|
none: "なし",
|
|
106
136
|
}
|
|
@@ -122,6 +152,16 @@ export function defaultNarrativeDisplayLabels(language: NarrativeViewLanguage):
|
|
|
122
152
|
risks: "Risks",
|
|
123
153
|
researchGaps: "Research gaps",
|
|
124
154
|
coveredSlides: "Covered slides",
|
|
155
|
+
storyWorkbench: "Story workbench",
|
|
156
|
+
workbenchNote: "Filter claims by evidence gaps, risks, objections, and artifact coverage. This view only suggests next commands; it does not mutate narrative state.",
|
|
157
|
+
artifactCoverage: "Artifact coverage",
|
|
158
|
+
noRenderTargets: "No render targets recorded",
|
|
159
|
+
nextActions: "Next actions",
|
|
160
|
+
missingClaims: "Missing claims",
|
|
161
|
+
affectedClaims: "Affected claims",
|
|
162
|
+
affectedSlides: "Affected slides",
|
|
163
|
+
notes: "Notes",
|
|
164
|
+
recommendedNextCommand: "Recommended next command",
|
|
125
165
|
noClaims: "No claims recorded",
|
|
126
166
|
none: "None",
|
|
127
167
|
}
|