dsh-industry-research 0.1.0

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.
Files changed (99) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/LICENSE +201 -0
  3. package/README.es.md +175 -0
  4. package/README.hi.md +175 -0
  5. package/README.md +175 -0
  6. package/README.pt.md +175 -0
  7. package/README.zh.md +175 -0
  8. package/THIRD_PARTY_NOTICES.md +21 -0
  9. package/cordis.patch.yml +44 -0
  10. package/lib/index.js +2080 -0
  11. package/lib/types/chain.d.ts +68 -0
  12. package/lib/types/chain.d.ts.map +1 -0
  13. package/lib/types/chain.js +88 -0
  14. package/lib/types/chain.js.map +1 -0
  15. package/lib/types/company.d.ts +121 -0
  16. package/lib/types/company.d.ts.map +1 -0
  17. package/lib/types/company.js +170 -0
  18. package/lib/types/company.js.map +1 -0
  19. package/lib/types/config.d.ts +72 -0
  20. package/lib/types/config.d.ts.map +1 -0
  21. package/lib/types/config.js +86 -0
  22. package/lib/types/config.js.map +1 -0
  23. package/lib/types/engine-bridge.d.ts +68 -0
  24. package/lib/types/engine-bridge.d.ts.map +1 -0
  25. package/lib/types/engine-bridge.js +18 -0
  26. package/lib/types/engine-bridge.js.map +1 -0
  27. package/lib/types/events.d.ts +79 -0
  28. package/lib/types/events.d.ts.map +1 -0
  29. package/lib/types/events.js +13 -0
  30. package/lib/types/events.js.map +1 -0
  31. package/lib/types/index.d.ts +58 -0
  32. package/lib/types/index.d.ts.map +1 -0
  33. package/lib/types/index.js +111 -0
  34. package/lib/types/index.js.map +1 -0
  35. package/lib/types/paths.d.ts +45 -0
  36. package/lib/types/paths.d.ts.map +1 -0
  37. package/lib/types/paths.js +93 -0
  38. package/lib/types/paths.js.map +1 -0
  39. package/lib/types/report.d.ts +134 -0
  40. package/lib/types/report.d.ts.map +1 -0
  41. package/lib/types/report.js +278 -0
  42. package/lib/types/report.js.map +1 -0
  43. package/lib/types/sources.d.ts +55 -0
  44. package/lib/types/sources.d.ts.map +1 -0
  45. package/lib/types/sources.js +73 -0
  46. package/lib/types/sources.js.map +1 -0
  47. package/lib/types/timeline.d.ts +78 -0
  48. package/lib/types/timeline.d.ts.map +1 -0
  49. package/lib/types/timeline.js +129 -0
  50. package/lib/types/timeline.js.map +1 -0
  51. package/lib/types/toolkit.d.ts +61 -0
  52. package/lib/types/toolkit.d.ts.map +1 -0
  53. package/lib/types/toolkit.js +77 -0
  54. package/lib/types/toolkit.js.map +1 -0
  55. package/lib/types/tools/company.d.ts +36 -0
  56. package/lib/types/tools/company.d.ts.map +1 -0
  57. package/lib/types/tools/company.js +139 -0
  58. package/lib/types/tools/company.js.map +1 -0
  59. package/lib/types/tools/map.d.ts +44 -0
  60. package/lib/types/tools/map.d.ts.map +1 -0
  61. package/lib/types/tools/map.js +211 -0
  62. package/lib/types/tools/map.js.map +1 -0
  63. package/lib/types/tools/report.d.ts +61 -0
  64. package/lib/types/tools/report.d.ts.map +1 -0
  65. package/lib/types/tools/report.js +277 -0
  66. package/lib/types/tools/report.js.map +1 -0
  67. package/lib/types/tools/track.d.ts +46 -0
  68. package/lib/types/tools/track.d.ts.map +1 -0
  69. package/lib/types/tools/track.js +188 -0
  70. package/lib/types/tools/track.js.map +1 -0
  71. package/lib/types/version.d.ts +8 -0
  72. package/lib/types/version.d.ts.map +1 -0
  73. package/lib/types/version.js +8 -0
  74. package/lib/types/version.js.map +1 -0
  75. package/lib/types/web.d.ts +74 -0
  76. package/lib/types/web.d.ts.map +1 -0
  77. package/lib/types/web.js +60 -0
  78. package/lib/types/web.js.map +1 -0
  79. package/package.json +143 -0
  80. package/skills/company-research-method/SKILL.md +50 -0
  81. package/skills/industry-research-method/SKILL.md +57 -0
  82. package/skills/industry-research-method/references/frameworks.md +42 -0
  83. package/src/chain.ts +132 -0
  84. package/src/company.ts +238 -0
  85. package/src/config.ts +154 -0
  86. package/src/engine-bridge.ts +62 -0
  87. package/src/events.ts +82 -0
  88. package/src/index.ts +139 -0
  89. package/src/paths.ts +96 -0
  90. package/src/report.ts +326 -0
  91. package/src/sources.ts +95 -0
  92. package/src/timeline.ts +155 -0
  93. package/src/toolkit.ts +87 -0
  94. package/src/tools/company.ts +157 -0
  95. package/src/tools/map.ts +241 -0
  96. package/src/tools/report.ts +304 -0
  97. package/src/tools/track.ts +223 -0
  98. package/src/version.ts +8 -0
  99. package/src/web.ts +94 -0
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Report assembly for `industry_report`: load nothing itself — it turns the
3
+ * already-loaded artifacts (chain map, timeline, company cards) into the
4
+ * frozen evidence/sections/claims contract, validates model-authored drafts,
5
+ * auto-drafts when the model supplied none, and renders the builtin-fallback
6
+ * Markdown + manifest when no `ctx.researchReport` engine is mounted.
7
+ * @module dsh-industry-research/report
8
+ */
9
+ import type { ChainMap } from './chain.js';
10
+ import type { TimelineEntry } from './timeline.js';
11
+ import type { CompanyCard } from './company.js';
12
+ import type { EvidenceInput, ReportSectionInput } from './engine-bridge.js';
13
+ /** One claim of the report draft (frozen-contract shape). */
14
+ export type ReportClaim = {
15
+ id: string;
16
+ text: string;
17
+ evidenceIds: string[];
18
+ };
19
+ /** A complete report draft: model-authored or auto-built. */
20
+ export interface ReportDraft {
21
+ title: string;
22
+ sections: ReportSectionInput[];
23
+ claims: ReportClaim[];
24
+ }
25
+ /** The artifacts an industry report assembles from. */
26
+ export interface LoadedArtifacts {
27
+ /** The chain map with its raw file content; absent when no map exists. */
28
+ chain?: {
29
+ path: string;
30
+ content: string;
31
+ map: ChainMap;
32
+ };
33
+ /** The timeline with its raw file content; absent when no entries exist. */
34
+ timeline?: {
35
+ path: string;
36
+ content: string;
37
+ entries: TimelineEntry[];
38
+ };
39
+ /** Company cards with their raw `card.json` content. */
40
+ cards: Array<{
41
+ path: string;
42
+ content: string;
43
+ card: CompanyCard;
44
+ }>;
45
+ /** Artifact-level gaps (missing map, empty timeline, unreadable cards, …). */
46
+ gaps: string[];
47
+ }
48
+ /** The standard auto-draft section keys selectable via the `sections` argument. */
49
+ export declare const AUTO_SECTIONS: readonly ["overview", "chain", "timeline", "companies", "gaps"];
50
+ /** One standard auto-draft section key. */
51
+ export type AutoSection = (typeof AUTO_SECTIONS)[number];
52
+ /**
53
+ * Build the frozen-contract evidence list from the loaded artifacts: one
54
+ * entry per artifact file, carrying the verbatim content for byte-level
55
+ * checks.
56
+ * @param artifacts - the loaded artifacts.
57
+ * @param capturedAt - ISO-8601 assembly time recorded on every entry.
58
+ * @returns the evidence list.
59
+ */
60
+ export declare function buildEvidence(artifacts: LoadedArtifacts, capturedAt: string): EvidenceInput[];
61
+ /**
62
+ * Validate a draft against the registered evidence: every claim's
63
+ * `evidenceIds` must reference registered evidence, and every `claimIds`
64
+ * reference in the sections must resolve to a registered claim.
65
+ * @param draft - the candidate draft.
66
+ * @param evidenceIds - registered evidence ids.
67
+ * @returns human-readable problems (empty when valid).
68
+ */
69
+ export declare function validateDraft(draft: ReportDraft, evidenceIds: ReadonlySet<string>): string[];
70
+ /**
71
+ * Build the mechanical draft when the model supplied none: sourced chain
72
+ * metrics and recent timeline entries become claims bound to their artifact
73
+ * evidence; everything unsourced stays a declared gap, never prose.
74
+ * @param industry - the industry display name.
75
+ * @param artifacts - the loaded artifacts.
76
+ * @param sections - which standard sections to include (default all).
77
+ * @returns the auto-built draft.
78
+ */
79
+ export declare function autoDraft(industry: string, artifacts: LoadedArtifacts, sections?: readonly AutoSection[]): ReportDraft;
80
+ /**
81
+ * Render the builtin-fallback Markdown report: sections with claim footnote
82
+ * markers, a source-traceability appendix with SHA-256 per evidence, and the
83
+ * unverified claims appendix. The fallback never claims independent
84
+ * verification.
85
+ * @param industry - the industry display name.
86
+ * @param draft - the validated draft.
87
+ * @param evidence - the registered evidence.
88
+ * @param generatedAt - ISO-8601 generation time.
89
+ * @returns the Markdown text.
90
+ */
91
+ export declare function renderFallbackMarkdown(industry: string, draft: ReportDraft, evidence: readonly EvidenceInput[], generatedAt: string): string;
92
+ /** The fallback manifest shape (`manifest.json`). */
93
+ export interface FallbackManifest {
94
+ engine: 'builtin-fallback';
95
+ industry: string;
96
+ title: string;
97
+ generatedAt: string;
98
+ disclaimer: string;
99
+ evidence: Array<{
100
+ id: string;
101
+ title: string;
102
+ origin: string;
103
+ sha256: string;
104
+ capturedAt: string;
105
+ bytes: number;
106
+ }>;
107
+ claims: Array<ReportClaim & {
108
+ status: 'unverified';
109
+ }>;
110
+ gaps: string[];
111
+ }
112
+ /**
113
+ * Write the fallback report directory: `report.md` + `manifest.json`.
114
+ * @param reportDir - absolute target directory (created).
115
+ * @param industry - the industry display name.
116
+ * @param draft - the validated draft.
117
+ * @param evidence - the registered evidence.
118
+ * @param gaps - artifact-level gaps to record in the manifest.
119
+ * @param generatedAt - ISO-8601 generation time.
120
+ * @returns the written file paths.
121
+ */
122
+ export declare function writeFallbackReport(reportDir: string, industry: string, draft: ReportDraft, evidence: readonly EvidenceInput[], gaps: readonly string[], generatedAt: string): Promise<{
123
+ reportPath: string;
124
+ manifestPath: string;
125
+ }>;
126
+ /**
127
+ * The report directory name for one generation: `<YYYYMMDD-HHmmss>` with a
128
+ * numeric suffix when the same second collides.
129
+ * @param at - the generation time.
130
+ * @param exists - whether a candidate directory already exists.
131
+ * @returns the directory name (not a path).
132
+ */
133
+ export declare function reportDirName(at: Date, exists: (name: string) => boolean): string;
134
+ //# sourceMappingURL=report.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/report.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,278 @@
1
+ /**
2
+ * Report assembly for `industry_report`: load nothing itself — it turns the
3
+ * already-loaded artifacts (chain map, timeline, company cards) into the
4
+ * frozen evidence/sections/claims contract, validates model-authored drafts,
5
+ * auto-drafts when the model supplied none, and renders the builtin-fallback
6
+ * Markdown + manifest when no `ctx.researchReport` engine is mounted.
7
+ * @module dsh-industry-research/report
8
+ */
9
+ import { writeFile, mkdir } from 'node:fs/promises';
10
+ import { join } from 'node:path';
11
+ import { DISCLAIMER } from "./company.js";
12
+ import { sha256Of } from "./sources.js";
13
+ /** The standard auto-draft section keys selectable via the `sections` argument. */
14
+ export const AUTO_SECTIONS = ['overview', 'chain', 'timeline', 'companies', 'gaps'];
15
+ /** How many timeline entries the auto-draft cites with claims. */
16
+ const AUTO_TIMELINE_CLAIMS = 10;
17
+ /**
18
+ * Build the frozen-contract evidence list from the loaded artifacts: one
19
+ * entry per artifact file, carrying the verbatim content for byte-level
20
+ * checks.
21
+ * @param artifacts - the loaded artifacts.
22
+ * @param capturedAt - ISO-8601 assembly time recorded on every entry.
23
+ * @returns the evidence list.
24
+ */
25
+ export function buildEvidence(artifacts, capturedAt) {
26
+ const evidence = [];
27
+ if (artifacts.chain !== undefined) {
28
+ evidence.push({ id: 'E-chain', title: '产业链结构图 chain.json', origin: artifacts.chain.path, content: artifacts.chain.content, capturedAt });
29
+ }
30
+ if (artifacts.timeline !== undefined) {
31
+ evidence.push({ id: 'E-timeline', title: '政策与动态时间线 timeline.jsonl', origin: artifacts.timeline.path, content: artifacts.timeline.content, capturedAt });
32
+ }
33
+ for (const { path, content, card } of artifacts.cards) {
34
+ evidence.push({ id: `E-company-${card.slug}`, title: `公司速览卡 ${card.name}`, origin: path, content, capturedAt });
35
+ }
36
+ return evidence;
37
+ }
38
+ /**
39
+ * Validate a draft against the registered evidence: every claim's
40
+ * `evidenceIds` must reference registered evidence, and every `claimIds`
41
+ * reference in the sections must resolve to a registered claim.
42
+ * @param draft - the candidate draft.
43
+ * @param evidenceIds - registered evidence ids.
44
+ * @returns human-readable problems (empty when valid).
45
+ */
46
+ export function validateDraft(draft, evidenceIds) {
47
+ const problems = [];
48
+ if (typeof draft.title !== 'string' || draft.title.trim().length === 0)
49
+ problems.push('draft.title must be a non-empty string');
50
+ if (draft.sections.length === 0)
51
+ problems.push('draft.sections must not be empty');
52
+ const claimIds = new Set();
53
+ for (const claim of draft.claims) {
54
+ if (typeof claim.id !== 'string' || claim.id.trim().length === 0) {
55
+ problems.push('a claim has an empty id');
56
+ continue;
57
+ }
58
+ if (claimIds.has(claim.id))
59
+ problems.push(`duplicate claim id "${claim.id}"`);
60
+ claimIds.add(claim.id);
61
+ for (const evidenceId of claim.evidenceIds) {
62
+ if (!evidenceIds.has(evidenceId))
63
+ problems.push(`claim "${claim.id}" references unknown evidence "${evidenceId}"`);
64
+ }
65
+ }
66
+ for (const section of draft.sections) {
67
+ for (const paragraph of section.paragraphs) {
68
+ for (const claimId of paragraph.claimIds ?? []) {
69
+ if (!claimIds.has(claimId))
70
+ problems.push(`section "${section.heading}" references unregistered claim "${claimId}"`);
71
+ }
72
+ }
73
+ }
74
+ return problems;
75
+ }
76
+ /**
77
+ * Build the mechanical draft when the model supplied none: sourced chain
78
+ * metrics and recent timeline entries become claims bound to their artifact
79
+ * evidence; everything unsourced stays a declared gap, never prose.
80
+ * @param industry - the industry display name.
81
+ * @param artifacts - the loaded artifacts.
82
+ * @param sections - which standard sections to include (default all).
83
+ * @returns the auto-built draft.
84
+ */
85
+ export function autoDraft(industry, artifacts, sections = [...AUTO_SECTIONS]) {
86
+ const wanted = new Set(sections);
87
+ const claims = [];
88
+ const draftSections = [];
89
+ if (wanted.has('overview')) {
90
+ const presence = [
91
+ artifacts.chain !== undefined ? `产业链结构图:${artifacts.chain.map.nodes.length} 节点 / ${artifacts.chain.map.edges.length} 边` : '产业链结构图:缺失',
92
+ artifacts.timeline !== undefined ? `政策与动态:${artifacts.timeline.entries.length} 条` : '政策与动态:无',
93
+ `公司速览卡:${artifacts.cards.length} 张`,
94
+ ];
95
+ draftSections.push({
96
+ heading: '概览',
97
+ paragraphs: [{ text: `本报告汇总「${industry}」行业研究工作区内的已有材料:${presence.join(';')}。所有数值均以来源回溯表中的证据为准,缺口见文末清单。` }],
98
+ });
99
+ }
100
+ if (wanted.has('chain')) {
101
+ const paragraphs = [];
102
+ if (artifacts.chain !== undefined) {
103
+ const map = artifacts.chain.map;
104
+ for (const tier of ['upstream', 'midstream', 'downstream']) {
105
+ const names = map.nodes.filter(node => node.tier === tier).map(node => node.name);
106
+ paragraphs.push({ text: `${tier}:${names.length > 0 ? names.join('、') : '(无节点)'}` });
107
+ }
108
+ const edgeText = map.edges.map(edge => {
109
+ const from = map.nodes.find(node => node.id === edge.from)?.name ?? edge.from;
110
+ const to = map.nodes.find(node => node.id === edge.to)?.name ?? edge.to;
111
+ return `${from} → ${to}${edge.note !== undefined ? `(${edge.note})` : ''}`;
112
+ });
113
+ if (edgeText.length > 0)
114
+ paragraphs.push({ text: `链上关系:${edgeText.join(';')}` });
115
+ const metricClaims = [];
116
+ for (const node of map.nodes) {
117
+ node.metrics.forEach((metric, index) => {
118
+ if (metric.value === undefined || metric.sourceRef === undefined)
119
+ return;
120
+ const id = `C-chain-${node.id}-${index}`;
121
+ claims.push({
122
+ id,
123
+ text: `${node.name} 的 ${metric.key} 为 ${metric.value}${metric.unit ?? ''}${metric.asOf !== undefined ? `(截至 ${metric.asOf},来源 ${metric.sourceRef})` : `(来源 ${metric.sourceRef})`}`,
124
+ evidenceIds: ['E-chain'],
125
+ });
126
+ metricClaims.push(id);
127
+ });
128
+ }
129
+ if (metricClaims.length > 0) {
130
+ paragraphs.push({ text: '链上关键指标见 claims 清单(逐条绑定来源证据)。', claimIds: metricClaims });
131
+ }
132
+ else {
133
+ paragraphs.push({ text: '链上暂无有来源的指标数值(均为待补槽位)。' });
134
+ }
135
+ }
136
+ else {
137
+ paragraphs.push({ text: '待补:尚无产业链结构图(先运行 industry_map)。' });
138
+ }
139
+ draftSections.push({ heading: '产业链结构', paragraphs });
140
+ }
141
+ if (wanted.has('timeline')) {
142
+ const paragraphs = [];
143
+ if (artifacts.timeline !== undefined && artifacts.timeline.entries.length > 0) {
144
+ const recent = artifacts.timeline.entries.slice(-AUTO_TIMELINE_CLAIMS);
145
+ for (const [index, entry] of recent.entries()) {
146
+ const id = `C-timeline-${index}`;
147
+ claims.push({ id, text: `${entry.date ?? '日期未知'}:${entry.title}(来源 ${entry.url})`, evidenceIds: ['E-timeline'] });
148
+ paragraphs.push({ text: `${entry.date ?? '日期未知'} — ${entry.title}`, claimIds: [id] });
149
+ }
150
+ if (artifacts.timeline.entries.length > recent.length) {
151
+ paragraphs.push({ text: `(时间线共 ${artifacts.timeline.entries.length} 条,此处仅列最近 ${recent.length} 条。)` });
152
+ }
153
+ }
154
+ else {
155
+ paragraphs.push({ text: '待补:尚无政策与动态条目(先运行 industry_track)。' });
156
+ }
157
+ draftSections.push({ heading: '政策与动态', paragraphs });
158
+ }
159
+ if (wanted.has('companies')) {
160
+ const paragraphs = [];
161
+ if (artifacts.cards.length > 0) {
162
+ for (const { card } of artifacts.cards) {
163
+ paragraphs.push({ text: `「${card.name}」(asOf ${card.asOf}):数据文件 ${card.sources.length} 份,数字候选行 ${card.figureCandidates.length} 行,缺口 ${card.gaps.length} 项;详见该卡 card.md。` });
164
+ }
165
+ }
166
+ else {
167
+ paragraphs.push({ text: '待补:尚无公司速览卡(先运行 company_scan)。' });
168
+ }
169
+ draftSections.push({ heading: '公司速览', paragraphs });
170
+ }
171
+ if (wanted.has('gaps')) {
172
+ draftSections.push({
173
+ heading: '缺口与待补',
174
+ paragraphs: artifacts.gaps.length > 0
175
+ ? artifacts.gaps.map(gap => ({ text: gap }))
176
+ : [{ text: '本次组装未发现材料级缺口(指标级待补见产业链结构一节)。' }],
177
+ });
178
+ }
179
+ const title = `${industry} 行业研究报告`;
180
+ return { title, sections: draftSections, claims };
181
+ }
182
+ /**
183
+ * Render the builtin-fallback Markdown report: sections with claim footnote
184
+ * markers, a source-traceability appendix with SHA-256 per evidence, and the
185
+ * unverified claims appendix. The fallback never claims independent
186
+ * verification.
187
+ * @param industry - the industry display name.
188
+ * @param draft - the validated draft.
189
+ * @param evidence - the registered evidence.
190
+ * @param generatedAt - ISO-8601 generation time.
191
+ * @returns the Markdown text.
192
+ */
193
+ export function renderFallbackMarkdown(industry, draft, evidence, generatedAt) {
194
+ const lines = [
195
+ `# ${draft.title}`,
196
+ '',
197
+ `> ${DISCLAIMER}。`,
198
+ `> 行业:${industry};生成时间:${generatedAt};引擎:builtin-fallback(未经过独立核查引擎,claims 未做逐条核查)。`,
199
+ '',
200
+ ];
201
+ for (const section of draft.sections) {
202
+ lines.push(`## ${section.heading}`, '');
203
+ for (const paragraph of section.paragraphs) {
204
+ const markers = (paragraph.claimIds ?? []).map(id => `[${id}]`).join('');
205
+ lines.push(`${paragraph.text}${markers}`, '');
206
+ }
207
+ }
208
+ lines.push('## 附录:来源回溯表', '', '| 证据 | 来源 | SHA-256 | 抓取时间 |', '|---|---|---|---|');
209
+ for (const item of evidence) {
210
+ lines.push(`| ${item.id} | ${item.title}(\`${item.origin}\`) | \`${sha256Of(item.content)}\` | ${item.capturedAt} |`);
211
+ }
212
+ lines.push('', '## 附录:claims 清单(builtin-fallback,未核查)', '');
213
+ if (draft.claims.length > 0) {
214
+ lines.push('| claim | 内容 | 证据 | 状态 |', '|---|---|---|---|');
215
+ for (const claim of draft.claims) {
216
+ lines.push(`| ${claim.id} | ${claim.text} | ${claim.evidenceIds.join(', ')} | unverified |`);
217
+ }
218
+ }
219
+ else {
220
+ lines.push('无 claims。');
221
+ }
222
+ lines.push('');
223
+ return lines.join('\n');
224
+ }
225
+ /**
226
+ * Write the fallback report directory: `report.md` + `manifest.json`.
227
+ * @param reportDir - absolute target directory (created).
228
+ * @param industry - the industry display name.
229
+ * @param draft - the validated draft.
230
+ * @param evidence - the registered evidence.
231
+ * @param gaps - artifact-level gaps to record in the manifest.
232
+ * @param generatedAt - ISO-8601 generation time.
233
+ * @returns the written file paths.
234
+ */
235
+ export async function writeFallbackReport(reportDir, industry, draft, evidence, gaps, generatedAt) {
236
+ await mkdir(reportDir, { recursive: true });
237
+ const reportPath = join(reportDir, 'report.md');
238
+ const manifestPath = join(reportDir, 'manifest.json');
239
+ const manifest = {
240
+ engine: 'builtin-fallback',
241
+ industry,
242
+ title: draft.title,
243
+ generatedAt,
244
+ disclaimer: DISCLAIMER,
245
+ evidence: evidence.map(item => ({
246
+ id: item.id,
247
+ title: item.title,
248
+ origin: item.origin,
249
+ sha256: sha256Of(item.content),
250
+ capturedAt: item.capturedAt,
251
+ bytes: Buffer.byteLength(item.content, 'utf8'),
252
+ })),
253
+ claims: draft.claims.map(claim => ({ ...claim, status: 'unverified' })),
254
+ gaps: [...gaps],
255
+ };
256
+ await writeFile(reportPath, renderFallbackMarkdown(industry, draft, evidence, generatedAt), 'utf8');
257
+ await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
258
+ return { reportPath, manifestPath };
259
+ }
260
+ /**
261
+ * The report directory name for one generation: `<YYYYMMDD-HHmmss>` with a
262
+ * numeric suffix when the same second collides.
263
+ * @param at - the generation time.
264
+ * @param exists - whether a candidate directory already exists.
265
+ * @returns the directory name (not a path).
266
+ */
267
+ export function reportDirName(at, exists) {
268
+ const pad = (value) => String(value).padStart(2, '0');
269
+ const base = `${at.getFullYear()}${pad(at.getMonth() + 1)}${pad(at.getDate())}-${pad(at.getHours())}${pad(at.getMinutes())}${pad(at.getSeconds())}`;
270
+ if (!exists(base))
271
+ return base;
272
+ for (let suffix = 2;; suffix += 1) {
273
+ const candidate = `${base}-${suffix}`;
274
+ if (!exists(candidate))
275
+ return candidate;
276
+ }
277
+ }
278
+ //# sourceMappingURL=report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.js","sourceRoot":"","sources":["../../src/report.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * The per-industry source registry (`sources.json`). Every artifact a metric
3
+ * or claim may cite — a user note, a supplied data file, a fetched page — is
4
+ * registered here with a stable ref (`S1`, `S2`, …), its origin (workspace
5
+ * path or URL), and a SHA-256 content hash, so reports can render a
6
+ * source-traceability appendix and byte-level checks can replay.
7
+ * @module dsh-industry-research/sources
8
+ */
9
+ /** One registered source. */
10
+ export type SourceEntry = {
11
+ /** Stable citation ref (e.g. `S1`). */
12
+ ref: string;
13
+ /** Where the content came from: an absolute workspace path or a URL. */
14
+ origin: string;
15
+ /** SHA-256 of the registered content snapshot. */
16
+ sha256: string;
17
+ /** ISO-8601 time the snapshot was registered. */
18
+ capturedAt: string;
19
+ /** Optional human note (e.g. the page title). */
20
+ note?: string;
21
+ };
22
+ /** The on-disk registry shape. */
23
+ export type SourceRegistry = {
24
+ /** Next ref number (refs are never reused). */
25
+ next: number;
26
+ items: SourceEntry[];
27
+ };
28
+ /** SHA-256 hex digest of a UTF-8 string. */
29
+ export declare function sha256Of(content: string): string;
30
+ /**
31
+ * Load a registry from disk, tolerating a missing file (empty registry).
32
+ * A corrupt file fails loud — durable data must not be silently dropped.
33
+ * @param path - absolute path of the registry JSON.
34
+ * @returns the parsed registry, or a fresh one when the file does not exist.
35
+ */
36
+ export declare function loadSources(path: string): Promise<SourceRegistry>;
37
+ /**
38
+ * Persist a registry, creating the parent directory.
39
+ * @param path - absolute path of the registry JSON.
40
+ * @param registry - the registry to write.
41
+ */
42
+ export declare function saveSources(path: string, registry: SourceRegistry): Promise<void>;
43
+ /**
44
+ * Register one source and return its ref. Re-registering the same origin
45
+ * refreshes the hash/timestamp of the existing entry instead of duplicating
46
+ * it, so refs stay stable across re-runs.
47
+ * @param registry - the registry to mutate.
48
+ * @param origin - workspace path or URL the content came from.
49
+ * @param content - the verbatim content snapshot (hashed).
50
+ * @param capturedAt - ISO-8601 registration time.
51
+ * @param note - optional human note (e.g. page title).
52
+ * @returns the stable ref of the entry.
53
+ */
54
+ export declare function registerSource(registry: SourceRegistry, origin: string, content: string, capturedAt: string, note?: string): string;
55
+ //# sourceMappingURL=sources.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sources.d.ts","sourceRoot":"","sources":["../../src/sources.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,6BAA6B;AAC7B,MAAM,MAAM,WAAW,GAAG;IACxB,uCAAuC;IACvC,GAAG,EAAE,MAAM,CAAA;IACX,wEAAwE;IACxE,MAAM,EAAE,MAAM,CAAA;IACd,kDAAkD;IAClD,MAAM,EAAE,MAAM,CAAA;IACd,iDAAiD;IACjD,UAAU,EAAE,MAAM,CAAA;IAClB,iDAAiD;IACjD,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAED,kCAAkC;AAClC,MAAM,MAAM,cAAc,GAAG;IAC3B,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,WAAW,EAAE,CAAA;CACrB,CAAA;AAED,4CAA4C;AAC5C,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAavE;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAGvF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAanI"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * The per-industry source registry (`sources.json`). Every artifact a metric
3
+ * or claim may cite — a user note, a supplied data file, a fetched page — is
4
+ * registered here with a stable ref (`S1`, `S2`, …), its origin (workspace
5
+ * path or URL), and a SHA-256 content hash, so reports can render a
6
+ * source-traceability appendix and byte-level checks can replay.
7
+ * @module dsh-industry-research/sources
8
+ */
9
+ import { createHash } from 'node:crypto';
10
+ import { readFile, writeFile, mkdir } from 'node:fs/promises';
11
+ import { dirname } from 'node:path';
12
+ /** SHA-256 hex digest of a UTF-8 string. */
13
+ export function sha256Of(content) {
14
+ return createHash('sha256').update(content, 'utf8').digest('hex');
15
+ }
16
+ /**
17
+ * Load a registry from disk, tolerating a missing file (empty registry).
18
+ * A corrupt file fails loud — durable data must not be silently dropped.
19
+ * @param path - absolute path of the registry JSON.
20
+ * @returns the parsed registry, or a fresh one when the file does not exist.
21
+ */
22
+ export async function loadSources(path) {
23
+ let text;
24
+ try {
25
+ text = await readFile(path, 'utf8');
26
+ }
27
+ catch (error) {
28
+ if (error.code === 'ENOENT')
29
+ return { next: 1, items: [] };
30
+ throw error;
31
+ }
32
+ const parsed = JSON.parse(text);
33
+ if (typeof parsed.next !== 'number' || !Array.isArray(parsed.items)) {
34
+ throw new Error(`sources registry at ${path} is malformed (expected { next, items[] })`);
35
+ }
36
+ return parsed;
37
+ }
38
+ /**
39
+ * Persist a registry, creating the parent directory.
40
+ * @param path - absolute path of the registry JSON.
41
+ * @param registry - the registry to write.
42
+ */
43
+ export async function saveSources(path, registry) {
44
+ await mkdir(dirname(path), { recursive: true });
45
+ await writeFile(path, `${JSON.stringify(registry, null, 2)}\n`, 'utf8');
46
+ }
47
+ /**
48
+ * Register one source and return its ref. Re-registering the same origin
49
+ * refreshes the hash/timestamp of the existing entry instead of duplicating
50
+ * it, so refs stay stable across re-runs.
51
+ * @param registry - the registry to mutate.
52
+ * @param origin - workspace path or URL the content came from.
53
+ * @param content - the verbatim content snapshot (hashed).
54
+ * @param capturedAt - ISO-8601 registration time.
55
+ * @param note - optional human note (e.g. page title).
56
+ * @returns the stable ref of the entry.
57
+ */
58
+ export function registerSource(registry, origin, content, capturedAt, note) {
59
+ const sha256 = sha256Of(content);
60
+ const existing = registry.items.find(item => item.origin === origin);
61
+ if (existing !== undefined) {
62
+ existing.sha256 = sha256;
63
+ existing.capturedAt = capturedAt;
64
+ if (note !== undefined)
65
+ existing.note = note;
66
+ return existing.ref;
67
+ }
68
+ const ref = `S${registry.next}`;
69
+ registry.next += 1;
70
+ registry.items.push({ ref, origin, sha256, capturedAt, ...(note !== undefined ? { note } : {}) });
71
+ return ref;
72
+ }
73
+ //# sourceMappingURL=sources.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sources.js","sourceRoot":"","sources":["../../src/sources.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAA;AAC7D,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAuBnC,4CAA4C;AAC5C,MAAM,UAAU,QAAQ,CAAC,OAAe;IACtC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AACnE,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAY;IAC5C,IAAI,IAAY,CAAA;IAChB,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IACrC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAA;QACrF,MAAM,KAAK,CAAA;IACb,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAmB,CAAA;IACjD,IAAI,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QACpE,MAAM,IAAI,KAAK,CAAC,uBAAuB,IAAI,4CAA4C,CAAC,CAAA;IAC1F,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAY,EAAE,QAAwB;IACtE,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAC/C,MAAM,SAAS,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;AACzE,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,cAAc,CAAC,QAAwB,EAAE,MAAc,EAAE,OAAe,EAAE,UAAkB,EAAE,IAAa;IACzH,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAA;IAChC,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,KAAK,MAAM,CAAC,CAAA;IACpE,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,QAAQ,CAAC,MAAM,GAAG,MAAM,CAAA;QACxB,QAAQ,CAAC,UAAU,GAAG,UAAU,CAAA;QAChC,IAAI,IAAI,KAAK,SAAS;YAAE,QAAQ,CAAC,IAAI,GAAG,IAAI,CAAA;QAC5C,OAAO,QAAQ,CAAC,GAAG,CAAA;IACrB,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAA;IAC/B,QAAQ,CAAC,IAAI,IAAI,CAAC,CAAA;IAClB,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAA;IACjG,OAAO,GAAG,CAAA;AACZ,CAAC"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * The per-industry timeline store (`timeline.jsonl`): one JSON entry per line,
3
+ * appended by `industry_track`, deduplicated by normalized URL, and capped by
4
+ * `timelineMaxEntries` (oldest entries dropped first). Pure file store — the
5
+ * web retrieval policy lives in `tools/track.ts`.
6
+ * @module dsh-industry-research/timeline
7
+ */
8
+ /** One timeline entry: a dated, sourced industry event. */
9
+ export type TimelineEntry = {
10
+ /** Event date (ISO-8601) from the source's publication time; null when the provider gave none. */
11
+ date: string | null;
12
+ /** Headline. */
13
+ title: string;
14
+ /** Source URL (citation). */
15
+ url: string;
16
+ /** Short summary (search snippet); null when unavailable. */
17
+ summary: string | null;
18
+ /** SHA-256 of the fetched page snapshot; null when the snapshot fetch failed or was outside the fetch budget. */
19
+ snapshotHash: string | null;
20
+ /** ISO-8601 time this entry was captured by `industry_track`. */
21
+ capturedAt: string;
22
+ /** Search topics that surfaced this entry. */
23
+ topics: string[];
24
+ /** Optional caveat (e.g. why the snapshot hash is missing). */
25
+ note?: string;
26
+ };
27
+ /** The outcome of merging a batch into the store. */
28
+ export interface TimelineMerge {
29
+ /** Entries actually appended (new URLs). */
30
+ added: TimelineEntry[];
31
+ /** Batch entries dropped because their URL was already tracked. */
32
+ duplicates: number;
33
+ /** Total entries in the store after the merge. */
34
+ total: number;
35
+ /** Whether the cap dropped older entries during this merge. */
36
+ truncated: boolean;
37
+ }
38
+ /**
39
+ * Normalize a URL for dedupe: lowercase scheme and host, strip default ports,
40
+ * strip a lone trailing slash on the path. Unparseable URLs are returned
41
+ * verbatim (dedupe then falls back to exact string equality).
42
+ * @param url - the raw URL.
43
+ * @returns the normalized dedupe key.
44
+ */
45
+ export declare function normalizeUrl(url: string): string;
46
+ /**
47
+ * Decide whether a source URL passes the host allow/block lists. Entries are
48
+ * host suffixes (`gov.cn` matches `www.gov.cn`) or URL prefixes
49
+ * (`https://www.gov.cn/zhengce/`). The blocklist wins. An empty allowlist
50
+ * allows every host.
51
+ * @param url - the candidate URL.
52
+ * @param allowlist - configured allow entries.
53
+ * @param blocklist - configured block entries.
54
+ * @returns whether the URL may be tracked.
55
+ */
56
+ export declare function sourceAllowed(url: string, allowlist: readonly string[], blocklist: readonly string[]): boolean;
57
+ /**
58
+ * Read a timeline store, tolerating a missing file. Corrupt lines are skipped
59
+ * and counted (never silently: the caller surfaces the count), because a
60
+ * single torn line must not drop the rest of a durable log.
61
+ * @param path - absolute path of the JSONL file.
62
+ * @returns the parsed entries plus the number of skipped corrupt lines.
63
+ */
64
+ export declare function readTimeline(path: string): Promise<{
65
+ entries: TimelineEntry[];
66
+ corrupt: number;
67
+ }>;
68
+ /**
69
+ * Merge a batch into the store and persist: dedupe by normalized URL (both
70
+ * against the store and within the batch), append the survivors, and rewrite
71
+ * the file keeping only the newest `maxEntries` when the cap is exceeded.
72
+ * @param path - absolute path of the JSONL file.
73
+ * @param batch - candidate entries, in arrival order.
74
+ * @param maxEntries - retention cap (oldest dropped first).
75
+ * @returns the merge outcome.
76
+ */
77
+ export declare function mergeTimeline(path: string, batch: readonly TimelineEntry[], maxEntries: number): Promise<TimelineMerge>;
78
+ //# sourceMappingURL=timeline.d.ts.map