progmune-runtime 2.0.4 → 2.1.1

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 (110) hide show
  1. package/.mcp.json +11 -0
  2. package/.progmune_allowlist +50 -0
  3. package/.test_report/test_report.md +87 -0
  4. package/Dockerfile +2 -10
  5. package/FAQ.md +167 -0
  6. package/README.md +108 -54
  7. package/demo-project/auth.ts +55 -0
  8. package/demo-project/tsconfig.json +8 -0
  9. package/dist/ab-stats.js +11 -0
  10. package/dist/acl-breakdown.js +13 -0
  11. package/dist/action-runtime.js +1 -0
  12. package/dist/all-sessions.js +11 -0
  13. package/dist/antibody-stats.js +11 -0
  14. package/dist/audit.js +222 -0
  15. package/dist/benchmark-count.js +7 -0
  16. package/dist/benchmark-full.js +17 -0
  17. package/dist/benchmark-pass-rate.js +54 -0
  18. package/dist/benchmark-report.js +67 -0
  19. package/dist/benchmark-save.js +62 -0
  20. package/dist/benchmark-status.js +15 -0
  21. package/dist/branch-ledger.js +393 -0
  22. package/dist/branch-tree-count.js +14 -0
  23. package/dist/check.js +506 -0
  24. package/dist/common-fixpath.js +12 -0
  25. package/dist/constraint-types.js +12 -0
  26. package/dist/deterministic-replay.js +283 -0
  27. package/dist/emitter.js +143 -12
  28. package/dist/exec-metrics.js +11 -0
  29. package/dist/execute.js +251 -0
  30. package/dist/extract-ir.js +592 -5
  31. package/dist/failure-collector.js +163 -0
  32. package/dist/failure-corpus.js +507 -35
  33. package/dist/failure-report.js +11 -0
  34. package/dist/failures.js +11 -0
  35. package/dist/fast-path-hits.js +13 -0
  36. package/dist/feedback.js +10 -3
  37. package/dist/file-lock.js +82 -0
  38. package/dist/find-session.js +10 -0
  39. package/dist/fingerprint-list.js +15 -0
  40. package/dist/gen-history-log.js +13 -0
  41. package/dist/generate.js +2 -1
  42. package/dist/generate_500.js +4 -2
  43. package/dist/genome.js +11 -0
  44. package/dist/heatmap-data.js +11 -0
  45. package/dist/heatmap.js +11 -0
  46. package/dist/immune-reporter.js +34 -22
  47. package/dist/ir-utils.js +18 -0
  48. package/dist/learned.js +11 -0
  49. package/dist/ledger-registry.js +252 -0
  50. package/dist/llm.js +46 -2
  51. package/dist/load-benchmarks.js +47 -0
  52. package/dist/main.js +2 -1
  53. package/dist/mcp-server.mjs +445 -43
  54. package/dist/memory-layer.js +54 -13
  55. package/dist/metrics.js +11 -0
  56. package/dist/obs-web.js +561 -0
  57. package/dist/p0_ssg_demo.js +255 -40
  58. package/dist/planner.js +996 -80
  59. package/dist/protocol-registry.js +112 -0
  60. package/dist/recent-session.js +12 -0
  61. package/dist/repair-proposal.js +363 -0
  62. package/dist/runtime-invariants.js +170 -0
  63. package/dist/runtime-types.js +117 -0
  64. package/dist/runtime.js +1 -0
  65. package/dist/search-planner.js +39 -9
  66. package/dist/semantic-snapshot.js +157 -0
  67. package/dist/semantic-trace.js +1497 -0
  68. package/dist/semantic-validator.js +3 -2
  69. package/dist/semantic_guard_test.js +2 -1
  70. package/dist/session-utils.js +19 -0
  71. package/dist/sessions.js +11 -0
  72. package/dist/ssg-validator.js +666 -20
  73. package/dist/svl-distribution.js +11 -0
  74. package/dist/terminal-status.js +11 -0
  75. package/dist/test_failure_corpus.js +3 -1
  76. package/dist/token-savings.js +11 -0
  77. package/dist/total-repairs.js +12 -0
  78. package/dist/unresolved-count.js +12 -0
  79. package/dist/utils.js +2 -0
  80. package/dist/valid-fingerprints.js +13 -0
  81. package/dist/validator.js +118 -60
  82. package/dist/verify-fps.js +11 -0
  83. package/dist/verify-ledgers.js +11 -0
  84. package/docs/whitepaper-style.css +77 -0
  85. package/docs/whitepaper-v2.1.md +609 -0
  86. package/docs/whitepaper-v2.2.md +1064 -0
  87. package/docs/whitepaper-v2.2.pdf +0 -0
  88. package/fly.toml +1 -1
  89. package/package.json +13 -4
  90. package/protocols.json +131 -11
  91. package/public/dashboard.html +119 -0
  92. package/server/hub.js +84 -12
  93. package/test/replay-golden/sess_1780063202050_mgeld.json +9 -0
  94. package/test/replay-golden/sess_1780064032560_gocld.json +354 -0
  95. package/test/replay-golden/sess_1780064413331_s2709.json +606 -0
  96. package/test/replay-golden/sess_1780064792710_y3avo.json +614 -0
  97. package/test/replay-golden.ts +84 -0
  98. package/test_benchmark.js +165 -0
  99. package/test_comprehensive.mjs +638 -0
  100. package/test_concurrency.js +129 -0
  101. package/test_ir_robustness.js +85 -0
  102. package/test_semantic_contracts.js +269 -0
  103. package/test_ssg_stress.js +156 -0
  104. package/test_svl3.js +58 -0
  105. package/tsconfig.json +1 -1
  106. package/.env.example +0 -3
  107. package/.progmune_memory/episodic.json +0 -186
  108. package/.progmune_memory/fingerprints.json +0 -7
  109. package/.progmune_memory/opt_in.json +0 -4
  110. package/immune_hub_data/2026-05-14.json +0 -50
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.main = main;
4
+ // @progmune-generated session=sess_1780343822181_r90g9 timestamp=2026-06-01T19:57:03.488Z ruleHash=9dec68bc2995e92a
5
+ // Generated with IR constraint: 394 functions, 17 protocol rules
6
+ const failure_corpus_1 = require("./failure-corpus");
7
+ function main() {
8
+ const genome = (0, failure_corpus_1.getFailureGenome)();
9
+ return genome;
10
+ }
11
+ main();
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.main = main;
4
+ // @progmune-generated session=sess_1780207842282_uihp3 timestamp=2026-05-31T06:10:44.349Z ruleHash=9dec68bc2995e92a
5
+ // Generated with IR constraint: 306 functions, 17 protocol rules
6
+ const semantic_trace_1 = require("./src/semantic-trace");
7
+ function main() {
8
+ const summary = (0, semantic_trace_1.formatImmuneResponse)({});
9
+ return summary;
10
+ }
11
+ main();
@@ -9,7 +9,9 @@ const failure_corpus_1 = require("./failure-corpus");
9
9
  constraintType: "protocol",
10
10
  actionSequence: [{ kind: "call", function: "generate_jwt" }],
11
11
  errorDetail: "非法调用:generate_jwt 要求前置状态 [AUTHENTICATED],当前状态为 [UNAUTHENTICATED]",
12
- ssgState: "UNAUTHENTICATED",
12
+ ssgState: ["UNAUTHENTICATED"],
13
+ plannerAttempt: 1,
14
+ plannerRetryTotal: 3,
13
15
  });
14
16
  // 打印当前统计
15
17
  console.log("当前失败案例总数:", (0, failure_corpus_1.getAllFailures)().length);
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.main = main;
4
+ // @progmune-generated session=sess_1780343649325_e8j9k timestamp=2026-06-01T19:54:10.484Z ruleHash=9dec68bc2995e92a
5
+ // Generated with IR constraint: 390 functions, 17 protocol rules
6
+ const failure_corpus_1 = require("./failure-corpus");
7
+ function main() {
8
+ const stats = (0, failure_corpus_1.getAntibodyStats)();
9
+ return stats;
10
+ }
11
+ main();
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.main = main;
4
+ // @progmune-generated session=sess_1780343904555_v2jom timestamp=2026-06-01T19:58:26.164Z ruleHash=9dec68bc2995e92a
5
+ // Generated with IR constraint: 397 functions, 17 protocol rules
6
+ const execute_1 = require("./execute");
7
+ function main() {
8
+ const metrics = (0, execute_1.getExecutionMetrics)();
9
+ const totalRepairCount = findIndex();
10
+ return totalRepairCount;
11
+ }
12
+ main();
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.main = main;
4
+ // @progmune-generated session=sess_1780343799715_z6pcw timestamp=2026-06-01T19:56:41.675Z ruleHash=9dec68bc2995e92a
5
+ // Generated with IR constraint: 393 functions, 17 protocol rules
6
+ const failure_corpus_1 = require("./failure-corpus");
7
+ function main() {
8
+ const sessions = (0, failure_corpus_1.getAllSessions)();
9
+ const failures = (0, failure_corpus_1.getAllFailures)();
10
+ return failures;
11
+ }
12
+ main();
package/dist/utils.js CHANGED
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.jaccardSimilarity = jaccardSimilarity;
4
4
  exports.extractKeywords = extractKeywords;
5
5
  // 计算两个字符串的简单 Jaccard 相似度(基于字符二元组)
6
+ /** @requires STRING_A @produces SIMILARITY_SCORE */
6
7
  function jaccardSimilarity(a, b) {
7
8
  const bigrams = (s) => {
8
9
  const bgs = new Set();
@@ -17,6 +18,7 @@ function jaccardSimilarity(a, b) {
17
18
  return intersection.size / (union.size || 1);
18
19
  }
19
20
  // 从意图中提取关键词
21
+ /** @requires TEXT @produces KEYWORDS */
20
22
  function extractKeywords(intent) {
21
23
  return intent.split(/[\s,。!?,]+/).filter(w => w.length > 1).map(w => w.toLowerCase());
22
24
  }
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.main = main;
4
+ // @progmune-generated session=sess_1780343878763_2tpv3 timestamp=2026-06-01T19:58:00.794Z ruleHash=9dec68bc2995e92a
5
+ // Generated with IR constraint: 396 functions, 17 protocol rules
6
+ const immune_reporter_1 = require("./immune-reporter");
7
+ const ledger_registry_1 = require("./ledger-registry");
8
+ function main() {
9
+ const fps = (0, immune_reporter_1.extractFingerprints)({});
10
+ const summary = (0, ledger_registry_1.verifyAllFingerprints)("defaultStr");
11
+ return summary;
12
+ }
13
+ main();
package/dist/validator.js CHANGED
@@ -41,7 +41,8 @@ function loadIR() {
41
41
  const irPath = path.resolve(__dirname, "../ir.json");
42
42
  if (!fs.existsSync(irPath))
43
43
  return [];
44
- return JSON.parse(fs.readFileSync(irPath, "utf-8"));
44
+ const raw = JSON.parse(fs.readFileSync(irPath, "utf-8"));
45
+ return Array.isArray(raw) ? raw : (raw.functions || []);
45
46
  }
46
47
  const BUILTIN_WHITELIST = new Set([
47
48
  "console.log", "setTimeout", "setInterval", "clearTimeout",
@@ -69,128 +70,185 @@ function normalizeType(type) {
69
70
  return "any";
70
71
  return t;
71
72
  }
73
+ // ========== 重写的变量流向分析:基于声明追踪的确定性检查 ==========
72
74
  function checkVariableFlow(actions) {
73
75
  const errors = [];
74
76
  const declared = new Map();
75
- for (let i = 0; i < actions.length; i++) {
76
- const action = actions[i];
77
+ const isLiteral = (val) => {
78
+ if (typeof val !== 'string')
79
+ return true;
80
+ if (/^["'`]/.test(val) || /["'`]$/.test(val))
81
+ return true;
82
+ if (/^\d+$/.test(val))
83
+ return true;
84
+ if (val === 'true' || val === 'false' || val === 'null' || val === 'undefined')
85
+ return true;
86
+ if (/\s/.test(val) || /[^\w]/.test(val))
87
+ return true;
88
+ return false;
89
+ };
90
+ const processAction = (action) => {
77
91
  if (action.kind === "call") {
78
- for (const arg of (action.args || [])) {
79
- if (typeof arg.value === "string" && /^[a-zA-Z_]\w*$/.test(arg.value)) {
80
- if (declared.has(arg.value))
81
- continue;
82
- if (arg.value.length < 15 && arg.value[0] === arg.value[0].toLowerCase()) {
83
- continue;
84
- }
85
- }
86
- }
92
+ // call 动作的参数值来自结构化 {name, type, value},都是字面量,不做变量引用检查
87
93
  if (action.assignTo) {
88
- if (action.args?.some(a => a.value === action.assignTo)) {
89
- errors.push(`变量 '${action.assignTo}' 在动作${i}中引用自身`);
90
- }
91
94
  declared.set(action.assignTo, "any");
92
95
  }
93
96
  }
94
97
  else if (action.kind === "assign") {
95
- if (action.target) {
96
- if (typeof action.value === "string" && /^[a-zA-Z_]\w*$/.test(action.value)) {
97
- if (!declared.has(action.value) && action.value.length < 15)
98
- continue;
99
- if (!declared.has(action.value))
100
- errors.push(`赋值时引用未定义变量 '${action.value}'`);
98
+ if (typeof action.value === "string") {
99
+ const val = action.value;
100
+ if (!isLiteral(val) && /^[a-zA-Z_]\w*$/.test(val)) {
101
+ if (!declared.has(val)) {
102
+ errors.push(`变量 '${val}' 在赋值前未声明`);
103
+ }
101
104
  }
105
+ }
106
+ if (action.target) {
102
107
  declared.set(action.target, "any");
103
108
  }
104
109
  }
105
110
  else if (action.kind === "return") {
106
- if (typeof action.value === "string" && /^[a-zA-Z_]\w*$/.test(action.value)) {
107
- if (!declared.has(action.value) && !/^["']/.test(action.value) && action.value.length < 15)
108
- continue;
109
- if (!declared.has(action.value))
110
- errors.push(`返回未定义变量 '${action.value}'`);
111
+ if (typeof action.value === "string") {
112
+ const val = action.value;
113
+ if (!isLiteral(val) && /^[a-zA-Z_]\w*$/.test(val)) {
114
+ if (!declared.has(val)) {
115
+ errors.push(`返回语句引用了未声明的变量 '${val}'`);
116
+ }
117
+ }
111
118
  }
112
119
  }
113
120
  else if (action.kind === "if") {
114
- if (typeof action.condition === "string" && declared.has(action.condition))
115
- continue;
116
- if (typeof action.condition === "string" && /^(true|false)$/.test(action.condition))
117
- continue;
118
- if (typeof action.condition === "string" && action.condition.length < 10) {
119
- // 可能是表达式,放行
121
+ if (typeof action.condition === "string") {
122
+ const cond = action.condition;
123
+ if (!isLiteral(cond) && /^[a-zA-Z_]\w*$/.test(cond)) {
124
+ if (!declared.has(cond) && cond !== 'true' && cond !== 'false') {
125
+ errors.push(`条件中引用了未声明的变量 '${cond}'`);
126
+ }
127
+ }
128
+ }
129
+ for (const a of (action.thenActions || [])) {
130
+ processAction(a);
120
131
  }
121
- else {
122
- errors.push(`条件中引用了未定义的变量 '${action.condition}'`);
132
+ for (const a of (action.elseActions || [])) {
133
+ processAction(a);
123
134
  }
124
- errors.push(...checkVariableFlow(action.thenActions || []));
125
- errors.push(...checkVariableFlow(action.elseActions || []));
126
135
  }
127
136
  else if (action.kind === "for") {
128
- errors.push(...checkVariableFlow(action.bodyActions || []));
137
+ if (action.variable)
138
+ declared.set(action.variable, "any");
139
+ for (const a of (action.bodyActions || [])) {
140
+ processAction(a);
141
+ }
129
142
  }
143
+ };
144
+ for (const action of actions) {
145
+ processAction(action);
130
146
  }
131
147
  return errors;
132
148
  }
133
- function validateAction(action) {
149
+ /**
150
+ * 校验单个动作的合法性(函数存在、类型匹配、参数数量)。
151
+ * @protocol namespace=dev_pipeline pre_states=["IR_EXTRACTED"] post_states=["ACTION_VALIDATED"]
152
+ */
153
+ /** @requires ACTION @produces VALIDATION_RESULT */
154
+ function validateAction(action, actionIndex) {
134
155
  const functions = loadIR();
135
156
  const errors = [];
157
+ const violations = [];
158
+ const idx = actionIndex ?? 0;
136
159
  if (!action || !["call", "if", "for", "assign", "return"].includes(action.kind)) {
137
- errors.push(`无效动作类型: '${action?.kind}'`);
138
- return { valid: false, errors };
160
+ const msg = `无效动作类型: '${action?.kind}'`;
161
+ errors.push(msg);
162
+ violations.push({ svl: 1, violatedConstraint: "symbol_existence", actionIndex: idx, description: msg });
163
+ return { valid: false, errors, violations };
139
164
  }
140
165
  if (action.kind === "call") {
141
166
  const fn = functions.find((f) => f.name === action.function);
142
167
  if (!fn) {
143
168
  if (action.function && BUILTIN_WHITELIST.has(action.function))
144
- return { valid: true, errors: [] };
145
- errors.push(`函数 '${action.function}' 不存在`);
146
- return { valid: false, errors };
169
+ return { valid: true, errors: [], violations: [] };
170
+ const msg = `函数 '${action.function}' 不存在`;
171
+ errors.push(msg);
172
+ violations.push({ svl: 1, violatedConstraint: "symbol_existence", actionIndex: idx, missingStates: [action.function], description: msg });
173
+ return { valid: false, errors, violations };
147
174
  }
148
175
  if (!action.args) {
149
- errors.push(`函数 '${action.function}' 缺少参数列表`);
150
- return { valid: false, errors };
176
+ const msg = `函数 '${action.function}' 缺少参数列表`;
177
+ errors.push(msg);
178
+ violations.push({ svl: 2, violatedConstraint: "type_mismatch", actionIndex: idx, description: msg });
179
+ return { valid: false, errors, violations };
151
180
  }
152
181
  if (action.args.length !== fn.params.length) {
153
- errors.push(`参数数量不匹配: 期望 ${fn.params.length}, 实际 ${action.args.length}`);
182
+ const msg = `参数数量不匹配: 期望 ${fn.params.length}, 实际 ${action.args.length}`;
183
+ errors.push(msg);
184
+ violations.push({ svl: 2, violatedConstraint: "type_mismatch", actionIndex: idx, description: msg });
154
185
  }
155
186
  action.args.forEach((arg, i) => {
156
187
  if (!arg) {
157
- errors.push(`函数 '${action.function}' 的第${i}个参数为空`);
188
+ const msg = `函数 '${action.function}' 的第${i}个参数为空`;
189
+ errors.push(msg);
190
+ violations.push({ svl: 2, violatedConstraint: "type_mismatch", actionIndex: idx, description: msg });
158
191
  return;
159
192
  }
160
193
  const expected = normalizeType(fn.params[i]?.type);
161
194
  const actual = normalizeType(arg.type);
162
195
  if (actual !== "any" && expected !== "any" && actual !== expected) {
163
- errors.push(`类型不匹配: 参数 '${fn.params[i].name}' 期望 ${expected}, 实际 ${actual}`);
196
+ const msg = `类型不匹配: 参数 '${fn.params[i].name}' 期望 ${expected}, 实际 ${actual}`;
197
+ errors.push(msg);
198
+ violations.push({ svl: 2, violatedConstraint: "type_mismatch", actionIndex: idx, description: msg });
164
199
  }
165
200
  });
166
201
  }
167
202
  else if (action.kind === "if") {
168
203
  if (action.thenActions) {
169
- for (const a of action.thenActions)
170
- errors.push(...validateAction(a).errors);
204
+ for (const a of action.thenActions) {
205
+ const r = validateAction(a, idx);
206
+ errors.push(...r.errors);
207
+ violations.push(...r.violations);
208
+ }
171
209
  }
172
210
  if (action.elseActions) {
173
- for (const a of action.elseActions)
174
- errors.push(...validateAction(a).errors);
211
+ for (const a of action.elseActions) {
212
+ const r = validateAction(a, idx);
213
+ errors.push(...r.errors);
214
+ violations.push(...r.violations);
215
+ }
175
216
  }
176
217
  }
177
218
  else if (action.kind === "for") {
178
219
  if (action.bodyActions) {
179
- for (const a of action.bodyActions)
180
- errors.push(...validateAction(a).errors);
220
+ for (const a of action.bodyActions) {
221
+ const r = validateAction(a, idx);
222
+ errors.push(...r.errors);
223
+ violations.push(...r.violations);
224
+ }
181
225
  }
182
226
  }
183
- return { valid: errors.length === 0, errors };
227
+ return { valid: errors.length === 0, errors, violations };
184
228
  }
229
+ /**
230
+ * 批量校验动作序列 + 变量流向分析。
231
+ * @protocol namespace=dev_pipeline pre_states=["ACTION_VALIDATED"] post_states=["SEQUENCE_VALIDATED"] invalidate=["ACTION_VALIDATED"]
232
+ */
233
+ /** @requires ACTIONS @produces VALIDATION_RESULT */
185
234
  function validateActionSequence(actions) {
186
235
  const errors = [];
187
- for (const action of actions) {
188
- const result = validateAction(action);
189
- if (!result.valid)
236
+ const violations = [];
237
+ for (let i = 0; i < actions.length; i++) {
238
+ const result = validateAction(actions[i], i);
239
+ if (!result.valid) {
190
240
  errors.push(...result.errors);
241
+ violations.push(...result.violations);
242
+ }
191
243
  }
192
244
  if (errors.length === 0) {
193
- errors.push(...checkVariableFlow(actions));
245
+ const flowErrors = checkVariableFlow(actions);
246
+ if (flowErrors.length > 0) {
247
+ errors.push(...flowErrors);
248
+ for (const msg of flowErrors) {
249
+ violations.push({ svl: 3, violatedConstraint: "dataflow", actionIndex: 0, description: msg });
250
+ }
251
+ }
194
252
  }
195
- return { valid: errors.length === 0, errors };
253
+ return { valid: errors.length === 0, errors, violations };
196
254
  }
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.main = main;
4
+ // @progmune-generated session=sess_1780345747502_y4u91 timestamp=2026-06-01T20:29:08.854Z ruleHash=9dec68bc2995e92a
5
+ // Generated with IR constraint: 388 functions, 17 protocol rules
6
+ const ledger_registry_1 = require("./ledger-registry");
7
+ function main() {
8
+ const result = (0, ledger_registry_1.verifyAllFingerprints)("");
9
+ return result;
10
+ }
11
+ main();
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.main = main;
4
+ // @progmune-generated session=sess_1780343460763_6iyf4 timestamp=2026-06-01T19:51:02.054Z ruleHash=9dec68bc2995e92a
5
+ // Generated with IR constraint: 386 functions, 17 protocol rules
6
+ const ledger_registry_1 = require("./ledger-registry");
7
+ function main() {
8
+ const summary = (0, ledger_registry_1.verifyAllFingerprints)("defaultStr");
9
+ return summary;
10
+ }
11
+ main();
@@ -0,0 +1,77 @@
1
+ /* Progmune Runtime v2.2 Whitepaper — Clean Academic Style */
2
+ body {
3
+ font-family: 'Times New Roman', Times, 'Songti SC', 'SimSun', serif;
4
+ font-size: 12pt;
5
+ line-height: 1.8;
6
+ color: #000;
7
+ max-width: 780px;
8
+ margin: 0 auto;
9
+ padding: 2.5rem 2rem;
10
+ }
11
+
12
+ /* ── Headings ── */
13
+ h1 { font-size: 1.8rem; text-align: center; margin: 0 0 0.3rem; font-weight: bold; }
14
+ h2 { font-size: 1.3rem; margin: 2rem 0 0.8rem; border-bottom: 1px solid #000; padding-bottom: 0.2rem; }
15
+ h3 { font-size: 1.1rem; margin: 1.5rem 0 0.5rem; }
16
+ h4 { font-size: 1rem; margin: 1.2rem 0 0.4rem; }
17
+
18
+ /* ── Code ── */
19
+ code {
20
+ font-family: 'Courier New', Courier, monospace;
21
+ font-size: 0.9em;
22
+ background: #f5f5f5;
23
+ padding: 0.1em 0.3em;
24
+ }
25
+ pre {
26
+ font-family: 'Courier New', Courier, monospace;
27
+ font-size: 0.85em;
28
+ background: #f8f8f8;
29
+ border: 1px solid #ddd;
30
+ padding: 0.8rem 1rem;
31
+ overflow-x: auto;
32
+ line-height: 1.5;
33
+ }
34
+ pre code { background: none; padding: 0; border: none; }
35
+
36
+ /* ── Tables ── */
37
+ table {
38
+ width: 100%;
39
+ border-collapse: collapse;
40
+ margin: 1rem 0 1.5rem;
41
+ font-size: 0.95em;
42
+ }
43
+ thead { background: #333; color: #fff; }
44
+ th, td {
45
+ padding: 0.4rem 0.6rem;
46
+ text-align: left;
47
+ border: 1px solid #999;
48
+ }
49
+
50
+ /* ── Blockquote ── */
51
+ blockquote {
52
+ border-left: 3px solid #666;
53
+ margin: 0.8rem 0;
54
+ padding: 0.3rem 0.8rem;
55
+ background: #f9f9f9;
56
+ font-style: italic;
57
+ }
58
+
59
+ /* ── Horizontal rule ── */
60
+ hr { border: none; border-top: 1px solid #999; margin: 1.5rem 0; }
61
+
62
+ /* ── Lists ── */
63
+ ul, ol { padding-left: 1.5rem; }
64
+ li { margin: 0.2rem 0; }
65
+
66
+ /* ── Strong ── */
67
+ strong { color: #000; }
68
+
69
+ /* ── Print ── */
70
+ @media print {
71
+ body { font-size: 10.5pt; max-width: none; padding: 0.8rem; }
72
+ h1 { font-size: 1.5rem; }
73
+ h2 { font-size: 1.15rem; }
74
+ pre { font-size: 0.8em; white-space: pre-wrap; }
75
+ table { font-size: 0.85em; }
76
+ @page { margin: 2cm; }
77
+ }