@cyber-dash-tech/revela 0.16.2 → 0.16.4
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/agents/research-prompt.ts +7 -7
- package/lib/commands/narrative.ts +6 -8
- package/lib/commands/research.ts +3 -2
- package/lib/narrative-state/display.ts +12 -40
- package/lib/narrative-state/map-html.ts +23 -137
- package/lib/narrative-state/map.ts +2 -320
- package/lib/refine/server.ts +472 -5
- package/lib/refine/visual-targets.ts +295 -0
- package/package.json +1 -1
- package/plugin.ts +3 -0
- package/tools/narrative-view.ts +6 -10
|
@@ -26,7 +26,7 @@ Write your findings to a single file and return a brief summary.
|
|
|
26
26
|
Given a research brief specifying your topic and axis, you will:
|
|
27
27
|
|
|
28
28
|
1. Understand the axis-specific research brief and its evidence needs
|
|
29
|
-
2. Use
|
|
29
|
+
2. Use the workspace and narrative context supplied by the primary agent in the brief
|
|
30
30
|
3. Run a lightweight workspace freshness check when needed
|
|
31
31
|
4. Search the web for current data, reports, and case studies when the brief requires it
|
|
32
32
|
5. Write all findings to ONE structured file: \`researches/{topic-key}/{axis-name}.md\` with source trace detailed enough for slide-level evidence mapping
|
|
@@ -41,13 +41,13 @@ Start from the research brief supplied by the primary agent. It should include:
|
|
|
41
41
|
- your axis filename
|
|
42
42
|
- the specific question for this axis
|
|
43
43
|
- time period, geography, and evidence standard
|
|
44
|
-
- known workspace sources from \`DECKS.json\` or user-provided files
|
|
44
|
+
- known workspace sources from the primary agent's \`DECKS.json\` readout or user-provided files
|
|
45
45
|
- whether web research is needed
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
Do not call \`revela-decks\`. The primary agent owns canonical workspace state and
|
|
48
|
+
will supply the relevant source-material index, open questions, and target claim
|
|
49
|
+
context in the brief. Treat supplied sourceMaterials as a candidate index, not as
|
|
50
|
+
proof by itself.
|
|
51
51
|
|
|
52
52
|
Before extracting or deeply reading a workspace document, check whether its
|
|
53
53
|
\`workspace.sourceMaterials\` record has the same fingerprint and valid
|
|
@@ -172,8 +172,8 @@ Gaps:
|
|
|
172
172
|
- **NEVER** ask the user for information you can find through search or workspace files
|
|
173
173
|
- **NEVER** use the raw \`write\` tool — always use \`revela-research-save\`
|
|
174
174
|
- **NEVER** write or patch \`DECKS.json\` — the primary agent decides what stable state to preserve
|
|
175
|
+
- **NEVER** call \`revela-decks\` — the primary agent supplies workspace state and handles all canonical updates
|
|
175
176
|
- **NEVER** fabricate image URLs — only record URLs you actually found
|
|
176
|
-
- **Always** use \`revela-decks\` action \`read\` first when \`DECKS.json\` exists or is referenced by the brief
|
|
177
177
|
- **Always** call \`revela-extract-document-materials\` for every selected workspace file before deciding which extracted materials to read next
|
|
178
178
|
- **Avoid** repeated extraction or deep reading for files that are clearly irrelevant to this axis
|
|
179
179
|
- **Always** include source attribution on every data point
|
|
@@ -123,14 +123,10 @@ export function buildNarrativeViewPrompt(options: { workspaceRoot: string; langu
|
|
|
123
123
|
evidence: claim.evidence.map((evidence) => ({ source: evidence.source, strength: evidence.strength, findingsFile: evidence.findingsFile, location: evidence.location, quote: evidence.quote, caveat: evidence.caveat, unsupportedScope: evidence.unsupportedScope })),
|
|
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
|
+
objections: map.objections.map((objection) => ({ id: objection.id, claimId: objection.claimId, priority: objection.priority, text: objection.text, response: objection.response })),
|
|
127
|
+
risks: map.risks.map((risk) => ({ id: risk.id, claimId: risk.claimId, severity: risk.severity, text: risk.text, mitigation: risk.mitigation })),
|
|
126
128
|
researchGaps: map.researchGaps.map((gap) => ({ id: gap.id, targetType: gap.targetType, targetId: gap.targetId, status: gap.status, priority: gap.priority, question: gap.question })),
|
|
127
129
|
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
|
-
},
|
|
134
130
|
}
|
|
135
131
|
|
|
136
132
|
return `Prepare the read-only Revela narrative UI display model.
|
|
@@ -147,11 +143,13 @@ Hard rules:
|
|
|
147
143
|
- Do not invent new claims, evidence, relations, slide coverage, source paths, findings files, quotes, or caveats.
|
|
148
144
|
- Preserve every claimId exactly.
|
|
149
145
|
- Preserve every relation endpoint exactly: fromClaimId, toClaimId, relation.
|
|
150
|
-
- You may only organize and localize display copy for the UI: pageTitle, summaryLine, section labels
|
|
146
|
+
- You may only organize and localize display copy for the UI: pageTitle, summaryLine, section labels, claim card displayTitle, roleLabel, narrativeJob, evidenceSummary, supportRationale, supportedScope, unsupportedScope, objectionsSummary, risksSummary, riskOrGapSummary, researchGapsSummary, relation displayLabel, and relation displayRationale.
|
|
151
147
|
- For inferred relations, do not provide relation displayLabel or displayRationale; inferred relations are unconfirmed order notes, not causal/support/dependency judgments.
|
|
152
148
|
- 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.
|
|
153
149
|
- Keep source paths, findings files, claim IDs, narrative hash, and numbers unchanged.
|
|
154
|
-
- Translate normal UI/display text into the target language request: pageTitle, summaryLine, labels
|
|
150
|
+
- Translate normal UI/display text into the target language request: pageTitle, summaryLine, labels, claim displayTitle, roleLabel, narrativeJob, evidenceSummary, supportRationale, supportedScope, unsupportedScope, objectionsSummary, risksSummary, riskOrGapSummary, researchGapsSummary, relation displayLabel, and relation displayRationale.
|
|
151
|
+
- For every claim in a non-English target language, provide displayTitle so the selected-claim panel does not fall back to canonical English claim text.
|
|
152
|
+
- For every selected-claim detail field that has canonical user-facing text, provide the matching localized display field when it exists: supportedScope for supported scope, unsupportedScope for evidence boundaries, supportRationale for why the evidence supports the claim, objectionsSummary for objections, risksSummary for risks, and researchGapsSummary for research gaps.
|
|
155
153
|
- Do not translate claim IDs, relation endpoints, narrative hash, source paths, findings files, URLs, numbers, or quoted/source facts.
|
|
156
154
|
- Use natural business and manufacturing terminology in the target language, not word-by-word machine translation.
|
|
157
155
|
- If a fact is missing, describe it as missing instead of filling it in.
|
package/lib/commands/research.ts
CHANGED
|
@@ -33,9 +33,9 @@ Closed-loop workflow:
|
|
|
33
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
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
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
|
|
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. Do not use \`upsertNarrative\` during research to add evidence or update narrative arrays.
|
|
37
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,
|
|
38
|
+
11. If a claim or relation is broader than the evidence, do not mutate canonical claims during research. Report the needed claim/relation narrowing in \`Narrative changes\`, keep unsupported scope visible, and make \`/revela story\` or explicit user confirmation the next action when strategic wording must change.
|
|
39
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
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.
|
|
41
41
|
|
|
@@ -66,6 +66,7 @@ Rules:
|
|
|
66
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
67
|
- Do not bypass \`deriveResearchTargets\`; target selection, \`selected\`, and \`bindingDiagnostic\` are deterministic inputs, not LLM judgement.
|
|
68
68
|
- Do not mutate canonical claims merely to fit a source; narrow only to preserve evidence boundaries and avoid overstated claims.
|
|
69
|
+
- Do not call \`upsertNarrative\` during research. Research may update gaps, attach findings, and apply explicit evidence candidates; broader narrative rewrites must be reported for Story/user confirmation.
|
|
69
70
|
- 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.
|
|
70
71
|
- Do not store secrets, credentials, tokens, or sensitive personal information.
|
|
71
72
|
|
|
@@ -29,16 +29,6 @@ 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
|
|
42
32
|
noClaims: string
|
|
43
33
|
none: string
|
|
44
34
|
}
|
|
@@ -49,7 +39,13 @@ export interface NarrativeDisplayClaimCard {
|
|
|
49
39
|
roleLabel?: string
|
|
50
40
|
narrativeJob?: string
|
|
51
41
|
evidenceSummary?: string
|
|
42
|
+
supportRationale?: string
|
|
43
|
+
supportedScope?: string
|
|
44
|
+
unsupportedScope?: string
|
|
45
|
+
objectionsSummary?: string
|
|
46
|
+
risksSummary?: string
|
|
52
47
|
riskOrGapSummary?: string
|
|
48
|
+
researchGapsSummary?: string
|
|
53
49
|
}
|
|
54
50
|
|
|
55
51
|
export interface NarrativeDisplayRelation {
|
|
@@ -89,16 +85,6 @@ export function defaultNarrativeDisplayLabels(language: NarrativeViewLanguage):
|
|
|
89
85
|
risks: "风险",
|
|
90
86
|
researchGaps: "研究缺口",
|
|
91
87
|
coveredSlides: "已覆盖页面",
|
|
92
|
-
storyWorkbench: "Story 工作台",
|
|
93
|
-
workbenchNote: "按证据缺口、风险、异议和产物覆盖过滤主张;这里只读展示下一步命令,不修改叙事状态。",
|
|
94
|
-
artifactCoverage: "产物覆盖",
|
|
95
|
-
noRenderTargets: "未记录 render target",
|
|
96
|
-
nextActions: "下一步",
|
|
97
|
-
missingClaims: "缺失主张",
|
|
98
|
-
affectedClaims: "受影响主张",
|
|
99
|
-
affectedSlides: "受影响页面",
|
|
100
|
-
notes: "说明",
|
|
101
|
-
recommendedNextCommand: "建议命令",
|
|
102
88
|
noClaims: "没有记录主张",
|
|
103
89
|
none: "无",
|
|
104
90
|
}
|
|
@@ -121,16 +107,6 @@ export function defaultNarrativeDisplayLabels(language: NarrativeViewLanguage):
|
|
|
121
107
|
risks: "リスク",
|
|
122
108
|
researchGaps: "調査ギャップ",
|
|
123
109
|
coveredSlides: "対応スライド",
|
|
124
|
-
storyWorkbench: "Story ワークベンチ",
|
|
125
|
-
workbenchNote: "根拠ギャップ、リスク、反論、成果物カバレッジでクレームを絞り込みます。ここでは次のコマンドだけを読み取り専用で示し、ナラティブ状態は変更しません。",
|
|
126
|
-
artifactCoverage: "成果物カバレッジ",
|
|
127
|
-
noRenderTargets: "render target は記録されていません",
|
|
128
|
-
nextActions: "次のアクション",
|
|
129
|
-
missingClaims: "不足クレーム",
|
|
130
|
-
affectedClaims: "影響クレーム",
|
|
131
|
-
affectedSlides: "影響スライド",
|
|
132
|
-
notes: "メモ",
|
|
133
|
-
recommendedNextCommand: "推奨コマンド",
|
|
134
110
|
noClaims: "クレームは記録されていません",
|
|
135
111
|
none: "なし",
|
|
136
112
|
}
|
|
@@ -152,16 +128,6 @@ export function defaultNarrativeDisplayLabels(language: NarrativeViewLanguage):
|
|
|
152
128
|
risks: "Risks",
|
|
153
129
|
researchGaps: "Research gaps",
|
|
154
130
|
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",
|
|
165
131
|
noClaims: "No claims recorded",
|
|
166
132
|
none: "None",
|
|
167
133
|
}
|
|
@@ -234,7 +200,13 @@ function cleanClaimCard(card: NarrativeDisplayClaimCard): NarrativeDisplayClaimC
|
|
|
234
200
|
roleLabel: clean(card.roleLabel),
|
|
235
201
|
narrativeJob: clean(card.narrativeJob),
|
|
236
202
|
evidenceSummary: clean(card.evidenceSummary),
|
|
203
|
+
supportRationale: clean(card.supportRationale),
|
|
204
|
+
supportedScope: clean(card.supportedScope),
|
|
205
|
+
unsupportedScope: clean(card.unsupportedScope),
|
|
206
|
+
objectionsSummary: clean(card.objectionsSummary),
|
|
207
|
+
risksSummary: clean(card.risksSummary),
|
|
237
208
|
riskOrGapSummary: clean(card.riskOrGapSummary),
|
|
209
|
+
researchGapsSummary: clean(card.researchGapsSummary),
|
|
238
210
|
}
|
|
239
211
|
}
|
|
240
212
|
|
|
@@ -44,23 +44,6 @@ export function renderNarrativeMapHtmlWithDisplay(map: NarrativeMap, display: Va
|
|
|
44
44
|
.layout { display:grid; grid-template-columns:minmax(0,1fr) minmax(360px,430px); gap:18px; margin-top:18px; align-items:start; }
|
|
45
45
|
.flow,.detail-panel { background:rgba(255,253,248,.92); border:1px solid var(--line); border-radius:24px; box-shadow:var(--shadow); }
|
|
46
46
|
.flow { padding:20px; }
|
|
47
|
-
.workbench { margin-top:18px; background:rgba(255,253,248,.92); border:1px solid var(--line); border-radius:24px; box-shadow:var(--shadow); padding:18px 20px; }
|
|
48
|
-
.workbench h2 { margin:0; font-size:18px; letter-spacing:-.025em; }
|
|
49
|
-
.workbench-summary { display:grid; grid-template-columns:repeat(auto-fit,minmax(190px,1fr)); gap:10px; margin-top:14px; }
|
|
50
|
-
.summary-item { border:1px solid var(--line); border-radius:14px; background:#fff; padding:11px 12px; }
|
|
51
|
-
.summary-label { display:block; color:var(--muted); font-size:11px; font-weight:850; letter-spacing:.05em; text-transform:uppercase; }
|
|
52
|
-
.summary-value { display:block; margin-top:4px; color:#51483f; font-size:14px; font-weight:850; }
|
|
53
|
-
.filter-row { display:flex; flex-wrap:wrap; gap:8px; margin-top:14px; }
|
|
54
|
-
.filter-button { cursor:pointer; border:1px solid var(--line); border-radius:999px; background:#fff; color:var(--muted); padding:8px 11px; font-size:12px; font-weight:850; }
|
|
55
|
-
.filter-button.active { border-color:var(--accent); color:var(--accent); background:#fff4ea; }
|
|
56
|
-
.filter-status { margin:10px 0 0; color:var(--muted); font-size:12px; font-weight:780; }
|
|
57
|
-
.filter-empty { display:none; margin-top:10px; border:1px dashed var(--line); border-radius:14px; padding:12px; color:var(--muted); background:#fffaf3; font-size:13px; }
|
|
58
|
-
.coverage-grid { margin-top:16px; display:grid; grid-template-columns:repeat(auto-fit,minmax(260px,1fr)); gap:10px; }
|
|
59
|
-
.coverage-item { border:1px solid var(--line); border-radius:16px; background:#fff; padding:13px; }
|
|
60
|
-
.coverage-item h3 { margin:0; font-size:14px; line-height:1.2; }
|
|
61
|
-
.coverage-meta { display:flex; flex-wrap:wrap; gap:6px; margin-top:9px; }
|
|
62
|
-
.coverage-detail { margin:9px 0 0; color:var(--muted); font-size:12px; line-height:1.45; }
|
|
63
|
-
.coverage-detail strong { color:#51483f; }
|
|
64
47
|
.flow-head { display:flex; justify-content:space-between; gap:14px; align-items:flex-start; margin-bottom:18px; }
|
|
65
48
|
.flow-head h2 { margin:0; font-size:18px; letter-spacing:-.025em; }
|
|
66
49
|
.flow-note { margin:4px 0 0; color:var(--muted); font-size:13px; line-height:1.45; }
|
|
@@ -81,10 +64,6 @@ export function renderNarrativeMapHtmlWithDisplay(map: NarrativeMap, display: Va
|
|
|
81
64
|
.claim-section { border-top:1px solid #eee4d8; padding-top:9px; }
|
|
82
65
|
.section-label { display:block; margin-bottom:3px; color:var(--accent); font-size:10px; font-weight:900; letter-spacing:.08em; text-transform:uppercase; }
|
|
83
66
|
.section-text { display:block; color:#51483f; font-size:13px; line-height:1.46; white-space:pre-line; }
|
|
84
|
-
.next-actions { display:flex; flex-direction:column; gap:8px; }
|
|
85
|
-
.next-action { border:1px solid #eee4d8; border-radius:12px; padding:9px; background:#fffaf3; }
|
|
86
|
-
.next-action strong { display:block; color:#51483f; font-size:13px; }
|
|
87
|
-
.next-action code { display:inline-block; margin-top:5px; color:#9c4d1d; font-size:12px; }
|
|
88
67
|
.relation-strip { margin-top:12px; display:grid; gap:7px; }
|
|
89
68
|
.relation { display:grid; grid-template-columns:auto minmax(0,1fr); gap:8px; align-items:flex-start; color:var(--muted); font-size:13px; line-height:1.35; }
|
|
90
69
|
.relation-badge { flex:0 0 auto; border-radius:999px; padding:3px 7px; background:#fff4e8; color:#9c4d1d; border:1px solid #efcfb8; font-size:10px; font-weight:850; text-transform:uppercase; letter-spacing:.04em; }
|
|
@@ -142,16 +121,12 @@ export function renderNarrativeMapHtmlWithDisplay(map: NarrativeMap, display: Va
|
|
|
142
121
|
<div class="detail-body" id="detail-body">${initial?.detailHtml ?? emptyCard(display.labels.claimFlow, display.labels.noClaims)}</div>
|
|
143
122
|
</aside>
|
|
144
123
|
</div>
|
|
145
|
-
${renderWorkbench(map, display)}
|
|
146
124
|
</main>
|
|
147
125
|
<div class="hidden-detail">
|
|
148
126
|
${nodes.map((node) => `<template id="detail-${escapeAttr(node.id)}" data-title="${escapeHtml(node.title)}" data-subtitle="${escapeHtml(claimSubtitle(node.claim, display))}">${node.detailHtml}</template>`).join("")}
|
|
149
127
|
</div>
|
|
150
128
|
<script>
|
|
151
129
|
const buttons = Array.from(document.querySelectorAll('.claim-card'));
|
|
152
|
-
const filters = Array.from(document.querySelectorAll('.filter-button'));
|
|
153
|
-
const filterStatus = document.getElementById('filter-status');
|
|
154
|
-
const filterEmpty = document.getElementById('filter-empty');
|
|
155
130
|
const title = document.getElementById('detail-title');
|
|
156
131
|
const sub = document.getElementById('detail-sub');
|
|
157
132
|
const body = document.getElementById('detail-body');
|
|
@@ -164,19 +139,6 @@ export function renderNarrativeMapHtmlWithDisplay(map: NarrativeMap, display: Va
|
|
|
164
139
|
buttons.forEach((button) => button.classList.toggle('active', button.dataset.nodeId === id));
|
|
165
140
|
}
|
|
166
141
|
buttons.forEach((button) => button.addEventListener('click', () => selectClaim(button.dataset.nodeId)));
|
|
167
|
-
filters.forEach((button) => button.addEventListener('click', () => {
|
|
168
|
-
const filter = button.dataset.filterId || 'all';
|
|
169
|
-
filters.forEach((item) => item.classList.toggle('active', item === button));
|
|
170
|
-
buttons.forEach((claimButton) => {
|
|
171
|
-
const flags = (claimButton.dataset.filters || '').split(' ');
|
|
172
|
-
claimButton.closest('.claim-step').style.display = filter === 'all' || flags.includes(filter) ? '' : 'none';
|
|
173
|
-
});
|
|
174
|
-
const visibleButtons = buttons.filter((claimButton) => claimButton.closest('.claim-step').style.display !== 'none');
|
|
175
|
-
const activeButton = buttons.find((claimButton) => claimButton.classList.contains('active'));
|
|
176
|
-
if (visibleButtons.length > 0 && (!activeButton || activeButton.closest('.claim-step').style.display === 'none')) selectClaim(visibleButtons[0].dataset.nodeId);
|
|
177
|
-
if (filterStatus) filterStatus.textContent = (button.dataset.filterLabel || filter) + ': ' + visibleButtons.length;
|
|
178
|
-
if (filterEmpty) filterEmpty.style.display = visibleButtons.length === 0 ? 'block' : 'none';
|
|
179
|
-
}));
|
|
180
142
|
</script>
|
|
181
143
|
</body>
|
|
182
144
|
</html>`
|
|
@@ -196,7 +158,7 @@ function renderStep(node: FlowNode, map: NarrativeMap, display: ValidatedNarrati
|
|
|
196
158
|
const outgoing = map.claimRelations.filter((relation) => relation.fromClaimId === node.claim.id)
|
|
197
159
|
return `<div class="claim-step">
|
|
198
160
|
<div class="step-rail"><div class="step-dot">${index + 1}</div><div class="step-line"></div></div>
|
|
199
|
-
<button class="claim-card ${escapeAttr(node.claim.evidenceStatus)}${active ? " active" : ""}" data-node-id="${escapeAttr(node.id)}"
|
|
161
|
+
<button class="claim-card ${escapeAttr(node.claim.evidenceStatus)}${active ? " active" : ""}" data-node-id="${escapeAttr(node.id)}" type="button">
|
|
200
162
|
<span class="claim-title">${escapeHtml(node.title)}</span>
|
|
201
163
|
<span class="claim-meta"><span class="tag">${escapeHtml(localizeValue(node.claim.kind, display))}</span><span class="tag">${escapeHtml(localizeValue(node.claim.importance, display))}</span><span class="tag">${escapeHtml(localizeValue(node.claim.evidenceStatus, display))}</span><span class="tag">${escapeHtml(node.claim.id)}</span></span>
|
|
202
164
|
${renderDisplayCardSummary(node.displayCard, display)}
|
|
@@ -235,80 +197,34 @@ function claimDetail(claim: NarrativeMapClaim, map: NarrativeMap, display: Valid
|
|
|
235
197
|
const coverageGaps = map.artifactCoverage.filter((artifact) => artifact.missingClaimIds.includes(claim.id) || artifact.affectedClaimIds.includes(claim.id))
|
|
236
198
|
const card = display.claimCards.get(claim.id)
|
|
237
199
|
return detailCards([
|
|
238
|
-
[display.labels.claim, claim.text],
|
|
200
|
+
[display.labels.claim, displayClaimText(claim.id, claim.text, display)],
|
|
239
201
|
...(card?.narrativeJob ? [[card.roleLabel || display.labels.claim, card.narrativeJob] as [string, string]] : []),
|
|
240
202
|
...(card?.evidenceSummary ? [[display.labels.evidence, card.evidenceSummary] as [string, string]] : []),
|
|
203
|
+
...(card?.supportRationale ? [[systemTerm("supportRationale", display), card.supportRationale] as [string, string]] : []),
|
|
241
204
|
...(card?.riskOrGapSummary ? [[display.labels.researchGaps, card.riskOrGapSummary] as [string, string]] : []),
|
|
242
205
|
[display.labels.claimId, claim.id],
|
|
243
206
|
[display.labels.status, `${localizeValue(claim.evidenceStatus, display)} / ${localizeValue(claim.importance, display)} / ${localizeValue(claim.kind, display)}`],
|
|
244
|
-
...(
|
|
245
|
-
...(
|
|
207
|
+
...localizedOptionalRow(display.labels.supportedScope, card?.supportedScope, claim.supportedScope, display),
|
|
208
|
+
...localizedOptionalRow(display.labels.unsupportedScope, card?.unsupportedScope, claim.unsupportedScope, display),
|
|
246
209
|
[display.labels.incomingRelations, incoming.length ? incoming.map((relation) => relationText(relation, display)).join("<br><br>") : display.labels.none],
|
|
247
210
|
[display.labels.outgoingRelations, outgoing.length ? outgoing.map((relation) => relationText(relation, display)).join("<br><br>") : display.labels.none],
|
|
248
|
-
...(claim.evidence.length ? claim.evidence.map((evidence) => [`${display.labels.evidence}: ${evidence.source}`, evidenceDetailText(evidence, display)] as [string, string]) : [[display.labels.evidence, display.labels.none] as [string, string]]),
|
|
249
|
-
...(
|
|
250
|
-
...(
|
|
251
|
-
...(
|
|
211
|
+
...(claim.evidence.length ? claim.evidence.map((evidence) => [`${display.labels.evidence}: ${evidence.source}`, evidenceDetailText(evidence, display, card)] as [string, string]) : [[display.labels.evidence, display.labels.none] as [string, string]]),
|
|
212
|
+
...localizedOptionalRow(display.labels.objections, card?.objectionsSummary, objections.length ? objections.map((item) => `${item.text}${item.response ? ` -> ${item.response}` : ""}`).join("<br>") : undefined, display),
|
|
213
|
+
...localizedOptionalRow(display.labels.risks, card?.risksSummary, risks.length ? risks.map((item) => `${item.text}${item.mitigation ? ` -> ${item.mitigation}` : ""}`).join("<br>") : undefined, display),
|
|
214
|
+
...localizedOptionalRow(display.labels.researchGaps, card?.researchGapsSummary ?? card?.riskOrGapSummary, gaps.length ? gaps.map((item) => `${item.question} [${localizeValue(item.status, display)}/${localizeValue(item.priority, display)}]`).join("<br>") : undefined, display),
|
|
252
215
|
...(slideRefs.length ? [[display.labels.coveredSlides, slideRefs.map((ref) => localizeSlideRef(ref, display)).join("<br>")] as [string, string]] : []),
|
|
253
|
-
...(coverageGaps.length ? [[
|
|
254
|
-
...(claim.nextActions.length ? [[systemTerm("nextActions", display), renderNextActions(claim, display), true] as [string, string, boolean]] : []),
|
|
216
|
+
...(coverageGaps.length ? [[display.labels.coveredSlides, coverageGaps.map((artifact) => `${artifact.type}: ${localizeValue(artifact.coverageStatus, display)}${artifact.staleReasons.length ? ` - ${artifact.staleReasons.join("; ")}` : ""}`).join("<br>")] as [string, string]] : []),
|
|
255
217
|
])
|
|
256
218
|
}
|
|
257
219
|
|
|
258
|
-
function
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
${renderWorkbenchSummary(map, display)}
|
|
263
|
-
<div class="filter-row" aria-label="Story filters">
|
|
264
|
-
${map.workbench.filters.map((filter, index) => `<button type="button" class="filter-button${index === 0 ? " active" : ""}" data-filter-id="${escapeAttr(filter.id)}" data-filter-label="${escapeHtml(localizeFilter(filter.label, display))}">${escapeHtml(localizeFilter(filter.label, display))} (${filter.count})</button>`).join("")}
|
|
265
|
-
</div>
|
|
266
|
-
<p class="filter-status" id="filter-status">${escapeHtml(localizeFilter(map.workbench.filters[0]?.label ?? "All claims", display))}: ${map.workbench.filters[0]?.count ?? 0}</p>
|
|
267
|
-
<div class="filter-empty" id="filter-empty">${escapeHtml(noClaimsMatchFilter(display))}</div>
|
|
268
|
-
<div class="coverage-grid">
|
|
269
|
-
${map.workbench.artifactCoverage.length ? map.workbench.artifactCoverage.map((item) => renderCoverageItem(item, display)).join("") : renderNoRenderTargetCard(map, display)}
|
|
270
|
-
</div>
|
|
271
|
-
</section>`
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
function renderWorkbenchSummary(map: NarrativeMap, display: ValidatedNarrativeDisplayModel): string {
|
|
275
|
-
const summary = map.workbench.summary
|
|
276
|
-
return `<div class="workbench-summary" aria-label="Story readiness summary">
|
|
277
|
-
<div class="summary-item"><span class="summary-label">${escapeHtml(systemTerm("approval", display))}</span><span class="summary-value">${escapeHtml(localizeValue(summary.approval, display))}</span></div>
|
|
278
|
-
<div class="summary-item"><span class="summary-label">${escapeHtml(readinessSummaryTerm("evidenceBlockers", display))}</span><span class="summary-value">${summary.evidenceBlockersCount}</span></div>
|
|
279
|
-
<div class="summary-item"><span class="summary-label">${escapeHtml(readinessSummaryTerm("artifactStatus", display))}</span><span class="summary-value">${escapeHtml(localizeValue(summary.artifactStatus, display))}</span></div>
|
|
280
|
-
<div class="summary-item"><span class="summary-label">${escapeHtml(readinessSummaryTerm("primaryNextCommand", display))}</span><span class="summary-value"><code>${escapeHtml(summary.primaryNextCommand)}</code></span></div>
|
|
281
|
-
<div class="summary-item"><span class="summary-label">${escapeHtml(readinessSummaryTerm("primaryNextReason", display))}</span><span class="summary-value">${escapeHtml(summary.primaryNextReason)}</span></div>
|
|
282
|
-
</div>`
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
function renderNoRenderTargetCard(map: NarrativeMap, display: ValidatedNarrativeDisplayModel): string {
|
|
286
|
-
const action = map.workbench.renderTargetAction
|
|
287
|
-
if (!action) return emptyCard(systemTerm("artifactCoverage", display), systemTerm("noRenderTargets", display))
|
|
288
|
-
return `<article class="coverage-item">
|
|
289
|
-
<h3>${escapeHtml(systemTerm("artifactCoverage", display))}</h3>
|
|
290
|
-
<p class="coverage-detail">${escapeHtml(systemTerm("noRenderTargets", display))}</p>
|
|
291
|
-
<p class="coverage-detail"><strong>${escapeHtml(systemTerm("notes", display))}:</strong> ${escapeHtml(localizeAction(action.label, display))} - ${escapeHtml(action.reason)}</p>
|
|
292
|
-
<p class="coverage-detail"><strong>${escapeHtml(systemTerm("recommendedNextCommand", display))}:</strong> <code>${escapeHtml(action.command)}</code></p>
|
|
293
|
-
</article>`
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
function renderCoverageItem(item: NarrativeMap["workbench"]["artifactCoverage"][number], display: ValidatedNarrativeDisplayModel): string {
|
|
297
|
-
const title = item.outputPath ?? item.artifactId
|
|
298
|
-
const slides = item.affectedSlides.map((slide) => `${localizeSlideRef(`slide ${slide.slideIndex}`, display)}: ${slide.slideTitle} (${slide.claimId}, ${slide.role}/${slide.location})`).join("<br>")
|
|
299
|
-
return `<article class="coverage-item">
|
|
300
|
-
<h3>${escapeHtml(title)}</h3>
|
|
301
|
-
<div class="coverage-meta"><span class="pill ${escapeAttr(item.coverageStatus)}">${escapeHtml(localizeValue(item.coverageStatus, display))}</span><span class="tag">${escapeHtml(item.type)}</span>${item.contractStatus ? `<span class="tag">${escapeHtml(item.contractStatus)}</span>` : ""}</div>
|
|
302
|
-
<p class="coverage-detail"><strong>${escapeHtml(systemTerm("missingClaims", display))}:</strong> ${escapeHtml(item.missingClaimIds.join(", ") || systemTerm("none", display))}</p>
|
|
303
|
-
<p class="coverage-detail"><strong>${escapeHtml(systemTerm("affectedClaims", display))}:</strong> ${escapeHtml(item.affectedClaimIds.join(", ") || systemTerm("none", display))}</p>
|
|
304
|
-
<p class="coverage-detail"><strong>${escapeHtml(systemTerm("affectedSlides", display))}:</strong> ${slides ? allowBreaks(slides) : escapeHtml(systemTerm("none", display))}</p>
|
|
305
|
-
<p class="coverage-detail"><strong>${escapeHtml(systemTerm("notes", display))}:</strong> ${escapeHtml([item.statusNote, ...item.staleReasons].filter(Boolean).join("; ") || systemTerm("none", display))}</p>
|
|
306
|
-
<p class="coverage-detail"><strong>${escapeHtml(systemTerm("recommendedNextCommand", display))}:</strong> <code>${escapeHtml(item.recommendedNextCommand)}</code></p>
|
|
307
|
-
</article>`
|
|
220
|
+
function localizedOptionalRow(label: string, localized: string | undefined, fallback: string | undefined, display: ValidatedNarrativeDisplayModel): Array<[string, string]> {
|
|
221
|
+
if (localized) return [[label, localized]]
|
|
222
|
+
if (!fallback || isLocalizedDisplay(display)) return []
|
|
223
|
+
return [[label, fallback]]
|
|
308
224
|
}
|
|
309
225
|
|
|
310
|
-
function
|
|
311
|
-
return
|
|
226
|
+
function isLocalizedDisplay(display: ValidatedNarrativeDisplayModel): boolean {
|
|
227
|
+
return display.language !== "en"
|
|
312
228
|
}
|
|
313
229
|
|
|
314
230
|
function relationText(relation: NarrativeMapClaimRelation, display: ValidatedNarrativeDisplayModel): string {
|
|
@@ -333,6 +249,7 @@ function relationDisplayRationale(relation: NarrativeMapClaimRelation, display:
|
|
|
333
249
|
const displayRationale = display.relations.get(relationKey(relation))?.displayRationale
|
|
334
250
|
if (displayRationale) return displayRationale
|
|
335
251
|
if (relation.inferred) return inferredRationale(display)
|
|
252
|
+
if (relation.rationale?.trim() && isLocalizedDisplay(display)) return undefined
|
|
336
253
|
return relation.rationale ?? missingRationale(display)
|
|
337
254
|
}
|
|
338
255
|
|
|
@@ -362,14 +279,14 @@ function emptyCard(label: string, value: string): string {
|
|
|
362
279
|
return `<div class="detail-card"><h3>${escapeHtml(label)}</h3><p class="empty">${escapeHtml(value)}</p></div>`
|
|
363
280
|
}
|
|
364
281
|
|
|
365
|
-
function evidenceDetailText(evidence: NarrativeMapClaim["evidence"][number], display: ValidatedNarrativeDisplayModel): string {
|
|
282
|
+
function evidenceDetailText(evidence: NarrativeMapClaim["evidence"][number], display: ValidatedNarrativeDisplayModel, card: ReturnType<ValidatedNarrativeDisplayModel["claimCards"]["get"]>): string {
|
|
366
283
|
return [
|
|
367
284
|
`${systemTerm("strength", display)}: ${localizeValue(evidence.strength, display)}`,
|
|
368
285
|
evidence.findingsFile ? `${systemTerm("findingsFile", display)}: ${evidence.findingsFile}` : "",
|
|
369
286
|
evidence.location ? `${systemTerm("location", display)}: ${evidence.location}` : "",
|
|
370
287
|
evidence.quote ? `${systemTerm("quote", display)}: ${evidence.quote}` : "",
|
|
371
|
-
evidence.unsupportedScope ? `${display.labels.unsupportedScope}: ${evidence.unsupportedScope}` : "",
|
|
372
|
-
evidence.caveat ? `${systemTerm("caveat", display)}: ${evidence.caveat}` : "",
|
|
288
|
+
card?.unsupportedScope ? `${display.labels.unsupportedScope}: ${card.unsupportedScope}` : evidence.unsupportedScope && !isLocalizedDisplay(display) ? `${display.labels.unsupportedScope}: ${evidence.unsupportedScope}` : "",
|
|
289
|
+
card?.supportRationale ? `${systemTerm("supportRationale", display)}: ${card.supportRationale}` : evidence.caveat && !isLocalizedDisplay(display) ? `${systemTerm("caveat", display)}: ${evidence.caveat}` : "",
|
|
373
290
|
].filter(Boolean).join(" | ")
|
|
374
291
|
}
|
|
375
292
|
|
|
@@ -387,41 +304,10 @@ function sectionLabels(display: ValidatedNarrativeDisplayModel): Record<string,
|
|
|
387
304
|
return { role: "Role", narrativeJob: "Narrative job", evidenceSummary: "Evidence summary", riskOrGapSummary: "Risk / gap" }
|
|
388
305
|
}
|
|
389
306
|
|
|
390
|
-
function workbenchNote(display: ValidatedNarrativeDisplayModel): string {
|
|
391
|
-
return display.labels.workbenchNote
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
function localizeFilter(value: string, display: ValidatedNarrativeDisplayModel): string {
|
|
395
|
-
const zh: Record<string, string> = { "All claims": "全部主张", "Missing evidence": "证据缺失", "Partial evidence": "部分证据", "Stale artifacts": "过期产物", "Open gaps": "开放缺口", Risks: "风险", "High-priority objections": "高优先级异议" }
|
|
396
|
-
const ja: Record<string, string> = { "All claims": "すべてのクレーム", "Missing evidence": "根拠不足", "Partial evidence": "一部根拠", "Stale artifacts": "古い成果物", "Open gaps": "未解決ギャップ", Risks: "リスク", "High-priority objections": "高優先度の反論" }
|
|
397
|
-
const table = isChineseLanguage(display.language) ? zh : isJapaneseLanguage(display.language) ? ja : {}
|
|
398
|
-
return table[value] ?? value
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
function localizeAction(value: string, display: ValidatedNarrativeDisplayModel): string {
|
|
402
|
-
const zh: Record<string, string> = { "Research this gap": "研究这个缺口", "Attach findings": "附加研究发现", "Narrow claim": "收窄主张", "Approve narrative": "批准叙事", "Make deck": "制作 deck", "Remake stale artifact": "重新生成过期产物" }
|
|
403
|
-
const ja: Record<string, string> = { "Research this gap": "このギャップを調査", "Attach findings": "調査結果を紐付け", "Narrow claim": "クレームを絞る", "Approve narrative": "ナラティブを承認", "Make deck": "デッキを作成", "Remake stale artifact": "古い成果物を再生成" }
|
|
404
|
-
const table = isChineseLanguage(display.language) ? zh : isJapaneseLanguage(display.language) ? ja : {}
|
|
405
|
-
return table[value] ?? value
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
function readinessSummaryTerm(value: string, display: ValidatedNarrativeDisplayModel): string {
|
|
409
|
-
const zh: Record<string, string> = { evidenceBlockers: "证据阻塞", artifactStatus: "产物状态", primaryNextCommand: "首要建议命令", primaryNextReason: "首要建议原因" }
|
|
410
|
-
const ja: Record<string, string> = { evidenceBlockers: "根拠ブロッカー", artifactStatus: "成果物ステータス", primaryNextCommand: "最優先コマンド", primaryNextReason: "最優先理由" }
|
|
411
|
-
const en: Record<string, string> = { evidenceBlockers: "Evidence blockers", artifactStatus: "Artifact status", primaryNextCommand: "Primary next command", primaryNextReason: "Primary next reason" }
|
|
412
|
-
return (isChineseLanguage(display.language) ? zh : isJapaneseLanguage(display.language) ? ja : en)[value] ?? value
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
function noClaimsMatchFilter(display: ValidatedNarrativeDisplayModel): string {
|
|
416
|
-
if (isChineseLanguage(display.language)) return "没有主张匹配这个过滤器。"
|
|
417
|
-
if (isJapaneseLanguage(display.language)) return "このフィルターに一致するクレームはありません。"
|
|
418
|
-
return "No claims match this filter."
|
|
419
|
-
}
|
|
420
|
-
|
|
421
307
|
function systemTerm(term: string, display: ValidatedNarrativeDisplayModel): string {
|
|
422
|
-
const zh: Record<string, string> = { approval: "审批", claims: "主张", relations: "关系", inferred: "未确认", relation: "关系", from: "来自", to: "指向", rationale: "说明", strength: "强度", findingsFile: "研究文件", location: "位置", quote: "引用", caveat: "注意事项", artifacts: "产物", attention: "需关注",
|
|
423
|
-
const ja: Record<string, string> = { approval: "承認", claims: "クレーム", relations: "関係", inferred: "未確認", relation: "関係", from: "起点", to: "終点", rationale: "理由", strength: "強度", findingsFile: "調査ファイル", location: "場所", quote: "引用", caveat: "留意点", artifacts: "成果物", attention: "要確認",
|
|
424
|
-
const en: Record<string, string> = { approval: "approval", claims: "claims", relations: "relations", inferred: "unconfirmed", relation: "relation", from: "from", to: "to", rationale: "rationale", strength: "strength", findingsFile: "findings file", location: "location", quote: "quote", caveat: "caveat", artifacts: "artifacts", attention: "need attention",
|
|
308
|
+
const zh: Record<string, string> = { approval: "审批", claims: "主张", relations: "关系", inferred: "未确认", relation: "关系", from: "来自", to: "指向", rationale: "说明", supportRationale: "支撑逻辑", strength: "强度", findingsFile: "研究文件", location: "位置", quote: "引用", caveat: "注意事项", artifacts: "产物", attention: "需关注", none: display.labels.none }
|
|
309
|
+
const ja: Record<string, string> = { approval: "承認", claims: "クレーム", relations: "関係", inferred: "未確認", relation: "関係", from: "起点", to: "終点", rationale: "理由", supportRationale: "裏付けの論理", strength: "強度", findingsFile: "調査ファイル", location: "場所", quote: "引用", caveat: "留意点", artifacts: "成果物", attention: "要確認", none: display.labels.none }
|
|
310
|
+
const en: Record<string, string> = { approval: "approval", claims: "claims", relations: "relations", inferred: "unconfirmed", relation: "relation", from: "from", to: "to", rationale: "rationale", supportRationale: "why this supports the claim", strength: "strength", findingsFile: "findings file", location: "location", quote: "quote", caveat: "caveat", artifacts: "artifacts", attention: "need attention", none: display.labels.none }
|
|
425
311
|
return (isChineseLanguage(display.language) ? zh : isJapaneseLanguage(display.language) ? ja : en)[term] ?? term
|
|
426
312
|
}
|
|
427
313
|
|