icoa-cli 2.19.66 → 2.19.67

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.
@@ -1,7 +1,7 @@
1
1
  import type { ExamState } from '../types/index.js';
2
- export declare function getExamState(): ExamState | null;
3
- export declare function getDemoState(): ExamState | null;
4
2
  export declare function getRealExamState(): ExamState | null;
3
+ export declare function getDemoState(): ExamState | null;
4
+ export declare function getExamState(): ExamState | null;
5
5
  export declare function saveExamState(state: ExamState): void;
6
6
  export declare function clearExamState(examId?: string): void;
7
7
  export declare function isExamActive(): boolean;
@@ -12,28 +12,8 @@ function demoStateFile() {
12
12
  function resolveStateFile(examId) {
13
13
  return examId === 'demo-free' ? demoStateFile() : stateFile();
14
14
  }
15
- export function getExamState() {
16
- // Check both files, return the most recently active one
17
- const files = [stateFile(), demoStateFile()];
18
- let latest = null;
19
- let latestTime = 0;
20
- for (const f of files) {
21
- if (!existsSync(f))
22
- continue;
23
- try {
24
- const state = JSON.parse(readFileSync(f, 'utf-8'));
25
- const t = new Date(state.session.confirmedAt || state.session.startedAt).getTime();
26
- if (t > latestTime) {
27
- latest = state;
28
- latestTime = t;
29
- }
30
- }
31
- catch { }
32
- }
33
- return latest;
34
- }
35
- export function getDemoState() {
36
- const f = demoStateFile();
15
+ export function getRealExamState() {
16
+ const f = stateFile();
37
17
  if (!existsSync(f))
38
18
  return null;
39
19
  try {
@@ -43,8 +23,8 @@ export function getDemoState() {
43
23
  return null;
44
24
  }
45
25
  }
46
- export function getRealExamState() {
47
- const f = stateFile();
26
+ export function getDemoState() {
27
+ const f = demoStateFile();
48
28
  if (!existsSync(f))
49
29
  return null;
50
30
  try {
@@ -54,6 +34,18 @@ export function getRealExamState() {
54
34
  return null;
55
35
  }
56
36
  }
37
+ export function getExamState() {
38
+ // Real exam ALWAYS takes priority over demo when both exist.
39
+ // Reasoning: real exam is token-gated, has a timer, and represents a serious
40
+ // commitment. Demo is casual practice. If a real exam is in progress, all
41
+ // shared commands (exam q N, exam answer, ai4ctf, ctf4ai) must target the
42
+ // real exam — never silently fall back to demo questions, even if demo was
43
+ // run more recently. (Demo→Exam→Finals progression principle, exam.md §0)
44
+ const real = getRealExamState();
45
+ if (real)
46
+ return real;
47
+ return getDemoState();
48
+ }
57
49
  export function saveExamState(state) {
58
50
  const f = resolveStateFile(state.session.examId);
59
51
  writeFileSync(f, JSON.stringify(state, null, 2));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "icoa-cli",
3
- "version": "2.19.66",
3
+ "version": "2.19.67",
4
4
  "description": "ICOA CLI — The world's first CLI-native CTF competition terminal",
5
5
  "type": "module",
6
6
  "bin": {