@var-ia/cli 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 (37) hide show
  1. package/dist/src/cli.d.ts +3 -0
  2. package/dist/src/cli.d.ts.map +1 -0
  3. package/dist/src/cli.js +7 -0
  4. package/dist/src/cli.js.map +1 -0
  5. package/dist/src/commands/analyze.d.ts +3 -0
  6. package/dist/src/commands/analyze.d.ts.map +1 -0
  7. package/dist/src/commands/analyze.js +115 -0
  8. package/dist/src/commands/analyze.js.map +1 -0
  9. package/dist/src/commands/cache.d.ts +7 -0
  10. package/dist/src/commands/cache.d.ts.map +1 -0
  11. package/dist/src/commands/cache.js +41 -0
  12. package/dist/src/commands/cache.js.map +1 -0
  13. package/dist/src/commands/claim.d.ts +3 -0
  14. package/dist/src/commands/claim.d.ts.map +1 -0
  15. package/dist/src/commands/claim.js +143 -0
  16. package/dist/src/commands/claim.js.map +1 -0
  17. package/dist/src/commands/export.d.ts +2 -0
  18. package/dist/src/commands/export.d.ts.map +1 -0
  19. package/dist/src/commands/export.js +79 -0
  20. package/dist/src/commands/export.js.map +1 -0
  21. package/dist/src/commands/watch.d.ts +2 -0
  22. package/dist/src/commands/watch.d.ts.map +1 -0
  23. package/dist/src/commands/watch.js +120 -0
  24. package/dist/src/commands/watch.js.map +1 -0
  25. package/dist/src/index.d.ts +3 -0
  26. package/dist/src/index.d.ts.map +1 -0
  27. package/dist/src/index.js +100 -0
  28. package/dist/src/index.js.map +1 -0
  29. package/dist/tsconfig.tsbuildinfo +1 -0
  30. package/package.json +26 -0
  31. package/src/cli.ts +7 -0
  32. package/src/commands/analyze.ts +137 -0
  33. package/src/commands/cache.ts +45 -0
  34. package/src/commands/claim.ts +171 -0
  35. package/src/commands/export.ts +87 -0
  36. package/src/commands/watch.ts +145 -0
  37. package/src/index.ts +111 -0
@@ -0,0 +1,87 @@
1
+ import { runAnalyze } from "./analyze.js";
2
+ import type { EvidenceEvent, Report } from "@var-ia/evidence-graph";
3
+
4
+ export async function runExport(
5
+ pageTitle: string,
6
+ format: string,
7
+ ): Promise<void> {
8
+ const events = await runAnalyze(pageTitle, "detailed");
9
+
10
+ if (events.length === 0) {
11
+ console.log("No events to export.");
12
+ return;
13
+ }
14
+
15
+ if (format === "json") {
16
+ const report = buildReport(pageTitle, events);
17
+ console.log(JSON.stringify(report, null, 2));
18
+ } else if (format === "csv") {
19
+ console.log(toCSV(events));
20
+ } else {
21
+ console.log(JSON.stringify(events, null, 2));
22
+ }
23
+ }
24
+
25
+ function buildReport(pageTitle: string, events: EvidenceEvent[]): Report {
26
+ const sortedRevs = events.map((e) => e.toRevisionId).sort((a, b) => a - b);
27
+ const timestamps = events.map((e) => e.timestamp).sort();
28
+
29
+ return {
30
+ pageTitle,
31
+ pageId: 0,
32
+ analyzedRevisionRange: {
33
+ from: sortedRevs[0] ?? 0,
34
+ to: sortedRevs[sortedRevs.length - 1] ?? 0,
35
+ },
36
+ generatedAt: new Date().toISOString(),
37
+ depth: "detailed",
38
+ layers: [
39
+ { label: "observed", description: "Deterministic", events: events.length, reproducible: true },
40
+ ],
41
+ timeline: {
42
+ totalRevisions: sortedRevs.length,
43
+ analyzedRevisions: sortedRevs.length,
44
+ dateRange: {
45
+ start: timestamps[0] ?? "",
46
+ end: timestamps[timestamps.length - 1] ?? "",
47
+ },
48
+ events: events.map((e) => ({
49
+ revisionId: e.toRevisionId,
50
+ timestamp: e.timestamp,
51
+ eventType: e.eventType,
52
+ summary: e.deterministicFacts.map((f) => f.fact).join("; "),
53
+ layer: e.layer,
54
+ })),
55
+ },
56
+ claims: [],
57
+ sources: [],
58
+ policySignals: [],
59
+ caveats: ["Deterministic analysis only — no model interpretation applied."],
60
+ phase: "Phase 1b",
61
+ };
62
+ }
63
+
64
+ function toCSV(events: EvidenceEvent[]): string {
65
+ const header = "timestamp,eventType,fromRevisionId,toRevisionId,section,before,after,facts";
66
+ const rows = events.map((e) => {
67
+ const facts = e.deterministicFacts.map((f) => f.fact + (f.detail ? `:${f.detail}` : "")).join("; ");
68
+ return [
69
+ e.timestamp,
70
+ e.eventType,
71
+ e.fromRevisionId,
72
+ e.toRevisionId,
73
+ csvEscape(e.section),
74
+ csvEscape(e.before.slice(0, 200)),
75
+ csvEscape(e.after.slice(0, 200)),
76
+ csvEscape(facts),
77
+ ].join(",");
78
+ });
79
+ return [header, ...rows].join("\n");
80
+ }
81
+
82
+ function csvEscape(val: string): string {
83
+ if (val.includes(",") || val.includes('"') || val.includes("\n")) {
84
+ return `"${val.replace(/"/g, '""')}"`;
85
+ }
86
+ return val;
87
+ }
@@ -0,0 +1,145 @@
1
+ import { MediaWikiClient } from "@var-ia/ingestion";
2
+ import { sectionDiffer, citationTracker, templateTracker, revertDetector } from "@var-ia/analyzers";
3
+ import type { EvidenceEvent, EvidenceLayer } from "@var-ia/evidence-graph";
4
+
5
+ const POLL_INTERVAL_MS = 60_000;
6
+
7
+ export async function runWatch(
8
+ pageTitle: string,
9
+ section?: string,
10
+ ): Promise<void> {
11
+ const client = new MediaWikiClient();
12
+ console.log(`Watching "${pageTitle}"${section ? ` section="${section}"` : ""}`);
13
+ console.log(`Polling every ${POLL_INTERVAL_MS / 1000}s. Press Ctrl+C to stop.\n`);
14
+
15
+ let lastSeenRevId = 0;
16
+
17
+ const poll = async () => {
18
+ try {
19
+ const revisions = await client.fetchRevisions(pageTitle, { limit: 5, direction: "newer" });
20
+ if (revisions.length === 0) return;
21
+
22
+ const sortedRevs = [...revisions].sort(
23
+ (a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
24
+ );
25
+
26
+ const newRevs = sortedRevs.filter((r) => r.revId > lastSeenRevId);
27
+ if (newRevs.length === 0) return;
28
+
29
+ if (lastSeenRevId === 0) {
30
+ const latest = sortedRevs[sortedRevs.length - 1];
31
+ console.log(`[${new Date().toISOString()}] Initial watch — latest revision: ${latest.revId} (${latest.timestamp})`);
32
+ lastSeenRevId = latest.revId;
33
+ return;
34
+ }
35
+
36
+ for (const rev of newRevs) {
37
+ const events: EvidenceEvent[] = [];
38
+ const layer: EvidenceLayer = "observed";
39
+
40
+ const prevIdx = sortedRevs.findIndex((r) => r.revId === rev.revId) - 1;
41
+ if (prevIdx >= 0) {
42
+ const before = sortedRevs[prevIdx];
43
+ const citeChanges = citationTracker.diffCitations(
44
+ citationTracker.extractCitations(before.content),
45
+ citationTracker.extractCitations(rev.content),
46
+ );
47
+
48
+ for (const cit of citeChanges) {
49
+ if (cit.type === "unchanged") continue;
50
+ events.push({
51
+ eventType: cit.type === "added" ? "citation_added" : cit.type === "removed" ? "citation_removed" : "citation_replaced",
52
+ fromRevisionId: before.revId,
53
+ toRevisionId: rev.revId,
54
+ section: "body",
55
+ before: cit.before?.raw ?? "",
56
+ after: cit.after?.raw ?? "",
57
+ deterministicFacts: [{ fact: "citation_changed", detail: `type=${cit.type}` }],
58
+ layer,
59
+ timestamp: rev.timestamp,
60
+ });
61
+ }
62
+
63
+ const tplChanges = templateTracker.diffTemplates(
64
+ templateTracker.extractTemplates(before.content),
65
+ templateTracker.extractTemplates(rev.content),
66
+ );
67
+ for (const tpl of tplChanges) {
68
+ if (tpl.type === "unchanged") continue;
69
+ events.push({
70
+ eventType: tpl.type === "added" ? "template_added" : "template_removed",
71
+ fromRevisionId: before.revId,
72
+ toRevisionId: rev.revId,
73
+ section: "body",
74
+ before: "",
75
+ after: tpl.template.name,
76
+ deterministicFacts: [{ fact: "template_changed", detail: `name=${tpl.template.name} type=${tpl.type}` }],
77
+ layer,
78
+ timestamp: rev.timestamp,
79
+ });
80
+ }
81
+
82
+ if (revertDetector.isRevert(rev.comment)) {
83
+ events.push({
84
+ eventType: "revert_detected",
85
+ fromRevisionId: before.revId,
86
+ toRevisionId: rev.revId,
87
+ section: "",
88
+ before: "",
89
+ after: rev.comment,
90
+ deterministicFacts: [{ fact: "revert_detected", detail: rev.comment }],
91
+ layer,
92
+ timestamp: rev.timestamp,
93
+ });
94
+ }
95
+
96
+ const secChanges = sectionDiffer.diffSections(
97
+ sectionDiffer.extractSections(before.content),
98
+ sectionDiffer.extractSections(rev.content),
99
+ );
100
+ for (const sc of secChanges) {
101
+ if (sc.changeType === "unchanged") continue;
102
+ if (section && sc.section !== section) continue;
103
+ events.push({
104
+ eventType: "section_reorganized",
105
+ fromRevisionId: before.revId,
106
+ toRevisionId: rev.revId,
107
+ section: sc.section,
108
+ before: sc.fromContent ?? "",
109
+ after: sc.toContent ?? "",
110
+ deterministicFacts: [{ fact: "section_changed", detail: `change=${sc.changeType}` }],
111
+ layer,
112
+ timestamp: rev.timestamp,
113
+ });
114
+ }
115
+ }
116
+
117
+ console.log(`\n[${rev.timestamp}] NEW REVISION ${rev.revId}`);
118
+ console.log(` Comment: ${rev.comment || "(none)"}`);
119
+ console.log(` Size: ${rev.size} bytes`);
120
+ if (events.length > 0) {
121
+ console.log(` Events: ${events.length}`);
122
+ for (const e of events) {
123
+ console.log(` - ${e.eventType} ${e.deterministicFacts.map((f) => f.detail).join(", ")}`);
124
+ }
125
+ }
126
+ }
127
+
128
+ lastSeenRevId = sortedRevs[sortedRevs.length - 1].revId;
129
+ } catch (err) {
130
+ console.error(`[${new Date().toISOString()}] Watch error:`, (err as Error).message);
131
+ }
132
+ };
133
+
134
+ await poll();
135
+ const interval = setInterval(poll, POLL_INTERVAL_MS);
136
+
137
+ const shutdown = () => {
138
+ clearInterval(interval);
139
+ console.log("\nWatch stopped.");
140
+ process.exit(0);
141
+ };
142
+
143
+ process.on("SIGINT", shutdown);
144
+ process.on("SIGTERM", shutdown);
145
+ }
package/src/index.ts ADDED
@@ -0,0 +1,111 @@
1
+ import { runAnalyze } from "./commands/analyze.js";
2
+ import { runClaim } from "./commands/claim.js";
3
+ import { runExport } from "./commands/export.js";
4
+ import { runWatch } from "./commands/watch.js";
5
+
6
+ const HELP = `
7
+ wikihistory — Wikipedia edit history analysis
8
+
9
+ Usage:
10
+ wikihistory analyze <page> [--depth brief|detailed|forensic] [--from <revId>] [--to <revId>] [--cache]
11
+ wikihistory claim <page> --text "<claim text>" [--cache]
12
+ wikihistory export <page> --format json|csv
13
+ wikihistory watch <page> [--section <name>]
14
+
15
+ Options:
16
+ --depth Analysis depth (default: detailed)
17
+ --text Claim text to track across revisions
18
+ --format Export format (json, csv)
19
+ --section Watch a specific section only
20
+ --from Start revision ID
21
+ --to End revision ID
22
+ --cache Cache revisions in SQLite (~/.wikihistory/varia.db)
23
+ `;
24
+
25
+ export async function cli(args: string[]): Promise<void> {
26
+ const command = args[0];
27
+
28
+ switch (command) {
29
+ case "analyze": {
30
+ const pageTitle = args[1];
31
+ if (!pageTitle) {
32
+ console.error("Usage: wikihistory analyze <page> [--depth brief|detailed|forensic] [--cache]");
33
+ process.exit(1);
34
+ }
35
+ const depth = parseFlag(args, "depth") ?? "detailed";
36
+ const fromRev = parseFlag(args, "from");
37
+ const toRev = parseFlag(args, "to");
38
+ const useCache = args.includes("--cache");
39
+
40
+ const events = await runAnalyze(
41
+ pageTitle,
42
+ depth,
43
+ fromRev ? parseInt(fromRev, 10) : undefined,
44
+ toRev ? parseInt(toRev, 10) : undefined,
45
+ useCache,
46
+ );
47
+
48
+ console.log(`\n=== Analysis Results ===`);
49
+ console.log(`Page: ${pageTitle}`);
50
+ console.log(`Events detected: ${events.length}`);
51
+ console.log();
52
+
53
+ for (const event of events) {
54
+ console.log(`[${event.timestamp}] ${event.eventType} (rev ${event.fromRevisionId}→${event.toRevisionId})`);
55
+ if (event.section) console.log(` Section: ${event.section}`);
56
+ for (const fact of event.deterministicFacts) {
57
+ console.log(` • ${fact.fact}${fact.detail ? `: ${fact.detail}` : ""}`);
58
+ }
59
+ }
60
+ break;
61
+ }
62
+ case "claim": {
63
+ const pageTitle = args[1];
64
+ const claimText = parseFlag(args, "text");
65
+ if (!pageTitle || !claimText) {
66
+ console.error('Usage: wikihistory claim <page> --text "<claim text>" [--cache]');
67
+ process.exit(1);
68
+ }
69
+ const useCache = args.includes("--cache");
70
+ await runClaim(pageTitle, claimText, useCache);
71
+ break;
72
+ }
73
+ case "export": {
74
+ const pageTitle = args[1];
75
+ const format = parseFlag(args, "format") ?? "json";
76
+ if (!pageTitle) {
77
+ console.error("Usage: wikihistory export <page> --format json|csv");
78
+ process.exit(1);
79
+ }
80
+ await runExport(pageTitle, format);
81
+ break;
82
+ }
83
+ case "watch": {
84
+ const pageTitle = args[1];
85
+ if (!pageTitle) {
86
+ console.error("Usage: wikihistory watch <page> [--section <name>]");
87
+ process.exit(1);
88
+ }
89
+ const section = parseFlag(args, "section");
90
+ await runWatch(pageTitle, section);
91
+ break;
92
+ }
93
+ case "--help":
94
+ case "-h":
95
+ default:
96
+ console.log(HELP);
97
+ break;
98
+ }
99
+ }
100
+
101
+ export function parseFlag(args: string[], name: string): string | undefined {
102
+ const idx = args.indexOf(`--${name}`);
103
+ if (idx >= 0 && idx + 1 < args.length) {
104
+ return args[idx + 1];
105
+ }
106
+ const eqIdx = args.findIndex((a) => a.startsWith(`--${name}=`));
107
+ if (eqIdx >= 0) {
108
+ return args[eqIdx].split("=")[1];
109
+ }
110
+ return undefined;
111
+ }