@pghoya2956/livemap 1.1.0 → 1.2.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.
@@ -17,11 +17,11 @@
17
17
  | function | 생성(migration) | name | DB 함수. 읽고 쓰는 테이블, BFF 사용 여부 |
18
18
  | table | 생성(migration) | schema.name | 저장 구조 |
19
19
  | migration | 생성(migration) | file | migration 파일 하나. 만드는 테이블·함수, grant·RLS 수, 마지막 변경 |
20
- | test | 생성(tests/) | file | 검사 파일. 다루는 라우트·API |
21
- | testreport | 생성(JUnit 리포트) | last | 마지막 검사 실행의 건수·실패·건너뜀과 커밋이 최신인지 |
20
+ | test | 생성(tests/, 결과 JSON) | file | 검사 파일. 다루는 라우트·API, 마지막 실행(`lastRun`) |
21
+ | testreport | 생성(결과 JSON, 없으면 JUnit 리포트) | last | 마지막 검사 실행의 건수·실패·건너뜀, 결과가 최신인지와 검사 신호(`test-results.md`) |
22
22
  | commit | 생성(git) | sha | 최근 변경. 건드린 파일 → 영향받는 화면·여정 |
23
- | decision | 생성(위키 index, 작업 문서) | file 또는 번호 | 위키 결정 페이지와 상태(current/proposed/superseded). 작업 문서의 `- DEC-nn`(스펙 결정)과 `- [ ] PN-nn`(계획 항목, 완료 여부)도 이 종류로 둔다 |
24
- | task | 생성(작업 폴더 `tasks.dir`) | 폴더 이름 | 작업 하나. 제목·단계(스펙 초안~검증)·상태·결정 수·계획 항목 완료/미완·열린 질문 |
23
+ | decision | 생성(위키 index, 작업 문서, 판정 파일) | file 또는 번호 | 위키 결정 페이지와 상태(current/proposed/superseded). 작업 문서의 결정 번호와 계획 항목 번호(완료 여부)도 이 종류로 두고, 정의한 작업을 `definers`에 싣는다 |
24
+ | task | 생성(작업 폴더 `tasks.dir`, 판정 파일) | 폴더 이름 | 작업 하나. 제목·단계(스펙 초안~검증)·상태·결정 수·계획 항목 완료/미완·열린 질문 수와 각 값의 읽기 상태. 계획 항목은 계획 파일(대체 규칙 포함)에서만, 결정·잔여 질문은 스펙 final에서만 센다(`semantic-authoring.md` 「작업 문서」) |
25
25
  | ledger | 생성(tasks/index.md) | 행 | 지금 실행 중·대기 중인 작업 |
26
26
  | deploy | 생성(git·배포 매니페스트) | head, 배포 대상 | 브랜치 머리 커밋, 매니페스트 이미지 태그의 sha와 뒤처진 커밋 수 |
27
27
  | milestone | 손(로드맵 `## 제목` 절) | id | 로드맵 항목. 순서·상태·진행 방식·장면·작업·선행·결정 대기·완료 기준·마일스톤, git 이력에서 계산한 완료일(`completedAt`)·결정 대기 시작일(`waitingSince`) |
@@ -32,15 +32,15 @@
32
32
  - journey → step (순서)
33
33
  - step → screen (shows): `screens: [route]`
34
34
  - step → api (uses): 명시(`apis`) 또는 screen을 거쳐 유도
35
- - screen → api (calls): 페이지와 그 로컬 import 닫힘에서 query hook 스캔
35
+ - screen → api (calls): 페이지와 그 import 닫힘의 `/api/` 문자열 리터럴을 모든 어댑터 뒤 연결 단계가 API 노드에 대응(1.2.0). 설정에 `router.hookApi`가 있으면 hook 이름 대응표로 이은 엣지도 더한다(2.0.0에서 폐기)
36
36
  - api → function (invokes): BFF 핸들러 블록의 `rpc/<name>`
37
37
  - function → table (touches): 함수 본문에서 알려진 테이블 이름 스캔
38
38
  - test → screen | api (covers): 검사 파일의 `goto('/…')`·`'/api/…'` 문자열
39
39
  - commit → screen | api | function (touches): 파일 경로 → 노드(페이지 파일·닫힘·server.mjs·migration)
40
40
  - milestone → task (tracks): 로드맵 항목의 `작업`. 장면·선행은 derive에서 해석하고 없으면 check 오류
41
41
  - release → milestone (contains): 로드맵 항목의 `마일스톤` 키. 없는 마일스톤 id면 엣지 없이 check 오류
42
- - task → decision (defines): 작업 문서의 `- DEC-nn`·`- [ ] PN-nn`
43
- - step → decision (refs): `refs: ["DEC-57", "PN-15", "trust-boundary"]`. derive가 문자열로 해석한다: `DEC-nn`·`PN-nn`은 그 번호를 정의한 작업(defines)으로, 나머지는 위키 결정 slug로 찾는다
42
+ - task → decision (defines): 작업 문서의 결정 목록 줄·계획 파일 체크박스의 머리 번호, 판정 파일 `lines` definition
43
+ - step → decision (refs): `refs: ["DEC-57", "20260914-booking#PN-15", "trust-boundary"]`. derive가 문자열로 해석한다: 번호는 그 번호를 정의한 작업(defines)으로(정의 작업이 여럿이면 `tasks.ambiguous-ref`), `<폴더>#<번호>`는 그 작업의 정의로, 나머지는 위키 결정 slug로 찾는다
44
44
 
45
45
  ## 상태 규칙
46
46
 
@@ -53,7 +53,64 @@
53
53
 
54
54
  ## 문제 기록(issues)
55
55
 
56
- 어댑터가 `g.issue(level, label, message)`로 낸 오류·경고는 노드가 아니라 `graph.json`·`data.json` 최상위 `issues[]`에 `{level, label, message, adapter}`로 남는다(1.1.0부터, `adapter-contract.md`).
56
+ 어댑터가 `g.issue(level, label, message, detail?)`로 낸 오류·경고는 노드가 아니라 `graph.json`·`data.json` 최상위 `issues[]`에 `{level, label, message, adapter}`로 남는다(1.1.0부터, `adapter-contract.md`). 넷째 인자를 준 문제는 `code`·`subject`·`anchors`·`resolutions`(있으면 `judgmentDraft`)를 더 싣는다(1.2.0, 코드 표는 `issue-codes.md`).
57
+
58
+ ## 읽기 상태
59
+
60
+ 1.2.0부터 값마다 어떻게 읽었는지를 함께 싣는다. 노드 `props.reading`은 `{ 필드: 상태 }`, `props.readingNotes`는 `{ 필드: 이유 문장 }`이다.
61
+
62
+ | 상태 | 뜻 | 화면 |
63
+ |---|---|---|
64
+ | `observed` | 생산자의 구조화된 출력(결과 JSON)이나 livemap 소유 형식에서 읽음 | 값 |
65
+ | `rule` | 규칙으로 읽었고 같은 범위의 후보 줄이 모두 읽힘. 적지 않은 필드의 기본값 | 값 |
66
+ | `judged` | 판정 파일이 값을 채우거나 고쳤고 근거 조각이 확인됨 | 값, 작업 상세와 더보기에 "판정" |
67
+ | `partial` | 규칙으로 읽었지만 안 읽힌 후보 줄이나 뜻 확인이 필요한 행이 있음 | 값 뒤 "?"(예: `48/61?`) |
68
+ | `stale` | 판정 근거 조각이 원문에서 사라졌거나 검사 결과 뒤 코드가 바뀜 | "?" |
69
+ | `unknown` | 소스가 없거나 형식 밖 | "?" |
70
+ | `none` | 대상이 없음(체크박스 없는 조사 작업 등) | 빈 칸 |
71
+
72
+ | 필드 | 상태 규칙 |
73
+ |---|---|
74
+ | 작업 `plan` | 계획 파일이나 대체 규칙으로 읽으면 `rule`, 판정 `planFile`이면 `judged`, 계획 파일이 없고 체크박스를 가진 루트 md가 둘 이상이면 `unknown`, 체크박스가 없으면 `none` |
75
+ | 작업 `openQuestions` | 잔여 질문 절을 읽으면 `rule`. 첫 칸이 번호 하나가 아닌 행이 있거나 완료 작업에 닫히지 않은 행이 있으면 `partial`. 스펙 final에 절이 없으면 `unknown`, `spec/initial.md`만 있으면 검토 전이라 `unknown`(이슈 없음). 스펙이 없거나 폐기 작업이면 `none`. 판정이 덮으면 `judged` |
76
+ | 작업 `stage` | 파이프라인 문서로 정하면 `rule`, 없으면 `unknown` |
77
+ | 검사 `count` | 최신 결과가 있으면 `observed`, 제목이 `${`를 가진 템플릿 문자열인 호출이 있으면 `partial`, 나머지 `rule` |
78
+ | 검사 `lastRun` | 최신 결과면 `observed`, 결과가 낡았으면 `stale`, 결과가 없거나 결과에 그 파일이 없으면 `unknown` |
79
+ | 화면 `apis` | 모든 리터럴이 API 노드에 맞고 hookApi로만 붙은 항목이 없으면 `rule`, 아니면 `partial` |
80
+ | 배포 `behind` | 계산하면 `rule`, 매니페스트 sha가 이력에 없거나 git 이력을 못 읽으면 `unknown` |
81
+
82
+ 합계는 구성 요소 중 하나라도 `partial`·`stale`·`unknown`이면 `partial`이다. `none`은 합계에서 빼고, 나머지가 섞이면 `judged`, `rule`, `observed` 순으로 약한 근거를 쓴다. 개요의 "?" 옆에는 이유 분류 문구만 쓰고, 파일·줄이 든 이유 문장은 작업 상세와 더보기에 글자로 보인다.
83
+
84
+ ## 1.2.0에서 더한 생성물 필드
85
+
86
+ 필드를 더하기만 했고 1.1.1 필드의 이름·위치는 그대로다.
87
+
88
+ | 생성물 | 필드 | 뜻 |
89
+ |---|---|---|
90
+ | `graph.json` 노드 `task` | `reading`, `readingNotes` | 작업 `plan`·`openQuestions`·`stage`(대체 규칙이면 `spec`) 읽기 상태와 이유 |
91
+ | | `readLines` | 엔진이 센 줄 `{ 파일: [줄 번호] }` |
92
+ | | `openQuestions`, `openQuestionIds` | 열린 잔여 질문 수(못 읽으면 `null`)와 번호 |
93
+ | | `planFile`, `specFile` | 읽은 계획 파일과 스펙 final(대체 규칙·판정 포함), 없으면 `null` |
94
+ | | `judged`, `judgment` | 판정 파일 경로와 `{ by, note, applied, stale, invalid }`, 판정 파일이 없으면 둘 다 `null` |
95
+ | `graph.json` 노드 `decision` | `definers`, `definedAt` | 그 번호를 정의한 작업 이름 목록, 정의 줄 `{ task, file, line }` 목록 |
96
+ | | `struck` | 취소선 정의만 있는 번호면 `true` |
97
+ | `graph.json` 노드 `test` | `lastRun`의 `failed`·`skipped`·`pending`·`flaky`(Playwright만)·`runner`·`sha`·`at`·`tags` | 결과 JSON의 파일별 값. 1.1.1 `passed`·`tests`·`fresh`는 그대로이고 `passed`는 `failed === 0`이다. `tags`는 파일에 붙은 러너 태그다 |
98
+ | | `runCount`, `reading`, `readingNotes` | 결과의 실행 수, `count`·`lastRun` 읽기 상태와 낡은 이유 |
99
+ | `graph.json` 노드 `screen` | `apiLiterals[]` | `{ path, open?, file, line, matched }`. 관측한 `/api/` 리터럴과 연결 단계가 맞춘 API 노드 id |
100
+ | | `hookApiKeys` | 설정에 `router.hookApi`가 있을 때 그 화면이 쓴 대응표 키 |
101
+ | | `reading`, `readingNotes` | `apis` 읽기 상태와 맞지 않는 리터럴·hookApi로만 붙은 API(3개까지) |
102
+ | `graph.json` 노드 `deploy` | `behindManifestOnly`, `reading`, `readingNotes` | 뒤처짐에서 뺀 매니페스트 전용 커밋 수, `behind` 읽기 상태 |
103
+ | `graph.json` 노드 `testreport:last` | `signal`, `runs` | 검사 신호(`ok`·`fail`·`stale`·`none`)와 실행 목록 |
104
+ | `graph.json`·`data.json` `issues[]` | `code`, `subject`, `anchors`, `resolutions`, `judgmentDraft` | 넷째 인자를 준 문제에만(`adapter-contract.md`, `issue-codes.md`) |
105
+ | `data.json` `tasks[]` | `reading`, `readingNotes`, `openQuestions`, `openQuestionIds`, `planFile`, `specFile`, `judged`, `judgment` | 노드와 같다. `readLines`는 싣지 않는다 |
106
+ | `data.json` `tests[]` | `reading`, `readingNotes` | 노드와 같다 |
107
+ | `data.json` `screens[]` | `reading` | 노드와 같다(이유 문장은 노드에만) |
108
+ | `data.json` | `testRuns[]` | `{ runner, source, sha, at, exit, fresh }`. 결과 JSON의 `dirtyPaths`는 싣지 않는다 |
109
+ | `data.json` | `readings` | `{ values: { 상태: 건수 }, fields: { '<노드 종류>.<필드>': { 상태: 건수 } } }` |
110
+ | `data.json` | `judgments[]` | `{ task, file, by, note, applied, stale, invalid }` |
111
+ | `overview.json` | `counts.openQuestions` | 열린 잔여 질문 합계. 1.1.1 `counts.oq`와 최상위 `openQuestions`는 그대로 남는다 |
112
+ | `overview.json` | `counts.reading` | `{ plans, openQuestions, tests, grades }` 개요 수치의 읽기 상태(경로·파일명 없음). `grades`는 검사 `lastRun`과 화면 `apis`의 합계다 |
113
+ | `overview.json` | `tasks[].openQuestions`, `tasks[].reading` | 작업별 열린 질문 수와 읽기 상태(식별자 없음) |
57
114
 
58
115
  ## 다른 프로젝트에 옮길 때 바꾸는 것
59
116
 
@@ -0,0 +1,189 @@
1
+ # 검사 결과
2
+
3
+ 상황판의 검사 신호, 검사 파일별 마지막 실행, 기능 단계 등급 A는 검사 결과 JSON에서 읽는다(1.2.0). 러너가 파일마다 낸 실행 수·통과·실패를 그대로 받고, 결과를 만든 커밋 뒤에 코드가 바뀌었는지로 결과가 최신인지 정한다. 엔진은 검사를 해석하거나 다시 돌리지 않는다.
4
+
5
+ ## 결과 JSON
6
+
7
+ 경로는 설정 `tests.report`와 같은 폴더의 `test-results.json`이다(기본 `map/.out/test-results.json`). livemap이 소유하는 형식이고 필드 이름은 CTRF를 따른다.
8
+
9
+ ```json
10
+ {
11
+ "schema": 1,
12
+ "runs": [
13
+ {
14
+ "runner": "node",
15
+ "source": "livemap test-report",
16
+ "sha": "4f1c2d0e9b8a7c6d5e4f3a2b1c0d9e8f7a6b5c4d",
17
+ "dirtyPaths": [],
18
+ "at": "2026-01-01T01:02:03.000Z",
19
+ "exit": 1,
20
+ "outsideRoot": 0,
21
+ "files": [
22
+ { "filePath": "tests/booking.test.mjs", "tests": 36, "passed": 35, "failed": 1, "skipped": 0, "pending": 0, "tags": [] }
23
+ ]
24
+ },
25
+ {
26
+ "runner": "playwright",
27
+ "source": "map/.out/playwright.json",
28
+ "stamped": "import",
29
+ "sha": "4f1c2d0e9b8a7c6d5e4f3a2b1c0d9e8f7a6b5c4d",
30
+ "dirtyPaths": [],
31
+ "at": "2026-01-01T01:10:00.000Z",
32
+ "exit": null,
33
+ "outsideRoot": 0,
34
+ "files": [
35
+ { "filePath": "tests/search.spec.mjs", "tests": 10, "passed": 10, "failed": 0, "skipped": 0, "pending": 0, "flaky": 0, "tags": ["diver/search"] }
36
+ ]
37
+ }
38
+ ]
39
+ }
40
+ ```
41
+
42
+ | 필드 | 뜻 |
43
+ |---|---|
44
+ | `runner` | `node`, `playwright`, `junit` |
45
+ | `source` | 실행 출처. `livemap test-report`, 가져온 파일의 루트 기준 경로 |
46
+ | `stamped` | `import`면 `sha`·`dirtyPaths`를 러너가 아니라 가져올 때 적었다 |
47
+ | `sha` | 실행한 커밋(`git rev-parse HEAD`). git이 없으면 `null` |
48
+ | `dirtyPaths` | 실행 때 커밋되지 않은 변경 경로(`git status --porcelain`, 루트 기준 정렬) |
49
+ | `at` | 실행 시각(UTC ISO). Playwright는 `stats.startTime`, JUnit은 첫 `testsuite`의 `timestamp` |
50
+ | `exit` | 러너 종료 코드. 모르면 `null` |
51
+ | `outsideRoot` | 프로젝트 루트 밖이라 뺀 파일 수 |
52
+ | `files[]` | 파일별 `tests`·`passed`·`failed`·`skipped`·`pending`, Playwright만 `flaky`, 러너 태그 `tags` |
53
+ | `totals` | JUnit 실행만. 파일 속성이 없어 `files`가 비어도 실행 전체 합계가 남는다 |
54
+
55
+ - 같은 러너·같은 출처의 실행은 바꾸고, 새 실행은 뒤에 붙인다. 파일은 임시 파일에 쓴 뒤 이름을 바꿔 넣어, 두 실행이 동시에 써도 깨지지 않고 나중 실행이 남는다.
56
+ - `filePath`는 검사 노드 id(검사 파일 경로)와 글자 그대로 맞춘다. `tests/booking.test.mjs`의 결과는 `tests/booking-journey.test.mjs`에 붙지 않는다.
57
+
58
+ ## 결과 만들기
59
+
60
+ ### Node 검사: livemap test-report
61
+
62
+ ```bash
63
+ npx --no livemap test-report
64
+ ```
65
+
66
+ `tests.dir`의 `*.test.mjs`를 `node --test`로 한 번 돌리면서 JUnit 리포터(1.x 호환 `tests.report`와 옆 `.json` 메타)와 livemap 리포터를 함께 붙인다. 결과 JSON의 `node`·`livemap test-report` 실행을 바꾸고, 종료 코드는 러너 종료 코드다. 이 명령을 `node --test` 안에서 불러도(검사 래퍼) 자식 러너가 파일을 건너뛰지 않는다.
67
+
68
+ ### Node 검사: 리포터를 기존 명령에 붙이기
69
+
70
+ 검사 명령이 따로 있으면(파일 목록·환경 변수·제외 파일) 그 명령에 livemap 리포터를 더하고 출력 파일을 가져온다.
71
+
72
+ ```bash
73
+ node --test \
74
+ --test-reporter=spec --test-reporter-destination=stdout \
75
+ --test-reporter=@pghoya2956/livemap/src/reporters/node-results.mjs \
76
+ --test-reporter-destination=map/.out/node-results.json \
77
+ tests/*.test.mjs
78
+ npx --no livemap test-report --import map/.out/node-results.json
79
+ ```
80
+
81
+ - 리포터 경로 `@pghoya2956/livemap/src/reporters/node-results.mjs`는 공개 경로다. 옮기면 major다.
82
+ - 리포터는 `test:pass`·`test:fail` 이벤트에서 `details.type`이 `suite`인 것을 빼고 파일별로 센다. `skip`이면 `skipped`, `todo`면 `pending`이다. 불러오기에 실패한 파일은 실패 1로 센다. Node v22.0.0 문서에 있는 이벤트 필드만 쓰고 `test:summary`는 쓰지 않는다.
83
+ - 리포터는 러너의 작업 폴더를 프로젝트 루트로 본다. 프로젝트 루트에서 러너를 부른다.
84
+ - 리포터는 종료 코드를 모르므로 출력의 `exit`는 `null`이다. 실패 신호는 파일별 `failed`로 난다.
85
+
86
+ ### Playwright
87
+
88
+ ```bash
89
+ PLAYWRIGHT_JSON_OUTPUT_NAME=map/.out/playwright.json npx --no playwright test --reporter=list,json
90
+ npx --no livemap test-report --import map/.out/playwright.json
91
+ ```
92
+
93
+ - 파일은 `config.rootDir`와 `suites[].file`(중첩 suite 포함)을 합친 경로다. 결과는 `tests[].status`로 센다: `expected` 통과, `unexpected` 실패, `flaky` 통과이며 `flaky` 수에 더함, `skipped` 건너뜀. 태그는 `specs[].tags`의 합집합이다(`@`를 뗀 값, Playwright 1.63.0 실측).
94
+ - Playwright `outputDir`(기본 `test-results/`)가 작업트리 안에 생기고 git이 무시하지 않으면 가져올 때 `dirtyPaths`에 들어가 결과가 낡는다. `.gitignore`에 넣거나 `map/.out/` 아래로 둔다.
95
+
96
+ ### JUnit
97
+
98
+ ```bash
99
+ npx --no livemap test-report --import reports/junit.xml --sha "$(git rev-parse HEAD)"
100
+ ```
101
+
102
+ 최상위·중첩 `<testcase>`를 모두 센다. `file` 속성(없으면 가장 가까운 `testsuite`의 속성)이 있으면 파일별로 나누고, 없으면 실행 합계(`totals`)만 남는다.
103
+
104
+ ### --import 규칙
105
+
106
+ - 형식은 내용으로 가린다. `schema 1`과 `runs` 배열이면 livemap 결과(리포터 출력 포함), `config` 객체와 `suites` 배열이면 Playwright JSON, `<testsuites`·`<testsuite`·`<testcase`가 있으면 JUnit이다. 가릴 수 없거나 파일이 없으면 종료 코드 2이고 결과 JSON을 쓰지 않는다.
107
+ - livemap 결과는 파일에 적힌 `sha`·`dirtyPaths`·`at`을 지킨다. `source`가 `null`인 실행(리포터 출력)만 가져온 파일 경로를 채운다.
108
+ - Playwright·JUnit 출력에는 커밋이 없어 `--sha`(없으면 가져올 때의 HEAD)와 가져올 때의 `dirtyPaths`를 적고 `stamped: "import"`를 남긴다. 러너를 돌린 커밋과 가져오는 커밋이 다르면 `--sha`로 실행 커밋을 준다. 오래된 결과를 지금 HEAD로 가져오면 최신으로 보이므로 CI에서는 러너 바로 뒤에 가져온다.
109
+
110
+ ## 최신 판정
111
+
112
+ testreport 어댑터는 결과 JSON이 있으면 그것만 읽는다. 실행마다 다음 셋이 모두 맞으면 최신이다.
113
+
114
+ 1. 실행 `sha`가 git 어댑터의 HEAD(`git.branch` → origin → HEAD)와 같거나 그 조상이다.
115
+ 2. `sha` 뒤 HEAD까지 문서 경로 밖을 바꾼 커밋이 0이다(`git rev-list --count <sha>..<HEAD> -- . ':(exclude)<문서 경로>'…`).
116
+ 3. `dirtyPaths`에 문서 경로 밖 경로가 없다. `dirtyPaths`가 없는 실행(JUnit 메타)은 이 조건을 건너뛴다.
117
+
118
+ 문서 경로는 새 설정 키 없이 기존 설정에서 정한다: `tasks.dir`, `wiki.index`의 폴더(루트 파일이면 그 파일), `semantic`, `roadmap.file`, `captures.site`, `deploy.manifest`, `map/judgments`. 설정에 없는 키는 뺀다. 코드·CI·로컬 스택·설정 파일 변경은 모두 결과를 낡게 한다. 그래서 작업 문서·판정 파일·여정만 바꾼 커밋 뒤에도 결과는 최신으로 남고, 코드를 바꾼 커밋 뒤에는 다시 돌려야 최신이 된다.
119
+
120
+ 결과가 최신이 아니면 검사 노드 `readingNotes.lastRun`에 이유가 남는다.
121
+
122
+ | 이유 문장 | 뜻 |
123
+ |---|---|
124
+ | 결과 커밋이 이력에 없음 | git이 없거나 얕은 클론이라 `sha`를 찾지 못함 |
125
+ | 결과 커밋이 HEAD의 조상이 아님 | 다른 브랜치에서 낸 결과 |
126
+ | 결과 커밋 뒤 문서 경로 밖 변경 커밋 n | 조건 2 |
127
+ | 실행 때 문서 경로 밖 변경 n(경로 3개까지) | 조건 3 |
128
+ | 결과에 이 검사 파일이 없음 | 결과 JSON에 그 파일이 없음(`lastRun` 읽기 상태 `unknown`) |
129
+ | JUnit에 파일(file 속성)이 없어 파일별 결과 없음 | JUnit 대체 읽기에서 파일을 나눌 수 없음 |
130
+
131
+ ## 상황판에 들어가는 값
132
+
133
+ - 검사 노드 `props.lastRun`: `passed`(`failed === 0`), `tests`, `failed`, `skipped`, `pending`, `flaky`(Playwright만), `runner`, `sha`, `at`, `fresh`, `tags`(러너 태그). 같은 파일이 여러 실행에 있으면 최신 실행을 먼저, 같으면 나중 `at`을 쓴다.
134
+ - 검사 개수: 최신 결과가 있는 파일은 실행 수로 바꾸고 `count` 읽기 상태가 `observed`다. 낡은 결과는 개수를 바꾸지 않고 `runCount`에만 싣는다.
135
+ - 개요 `signals.tests`: 최신 실행 중 실패가 있거나 `exit`가 0이 아니면 `fail`, 실행이 모두 최신이고 실패 0이면 `ok`, 최신이 아닌 실행이 있으면 `stale`, 실행이 없으면 `none`.
136
+ - 등급 A: 단계의 화면·API·함수를 덮는 검사 파일 중 하나 이상이 최신 실행에서 `tests` 1 이상, `failed` 0이다.
137
+ - `data.json` `testRuns[]`: `{ runner, source, sha, at, exit, fresh }`. `dirtyPaths`는 싣지 않는다(`data.json`은 서빙된다).
138
+
139
+ 결과 JSON이 없으면 1.x처럼 `tests.report`(JUnit)와 옆 메타(`sha`·`exit`)를 같은 해석기로 읽는다. 메타 `exit`가 0이 아니면 실패다. 둘 다 없으면 어댑터 상태가 partial "검사 리포트 없음(npm run test:report 미실행)"이고 검사 `lastRun` 읽기 상태는 `unknown`이다.
140
+
141
+ ## CI 배선 예
142
+
143
+ 검사 잡과 상황판 이미지·정적 사이트 빌드 잡이 따로 도는 파이프라인의 예다. 검사 잡이 결과 JSON을 아티팩트로 올리고, 빌드 잡이 `livemap build` 전에 받는다.
144
+
145
+ ```yaml
146
+ jobs:
147
+ tests:
148
+ runs-on: ubuntu-latest
149
+ steps:
150
+ - uses: actions/checkout@v4
151
+ with: { fetch-depth: 0 }
152
+ - uses: actions/setup-node@v4
153
+ with: { node-version: 22 }
154
+ - run: npm ci
155
+ - run: >
156
+ node --test
157
+ --test-reporter=spec --test-reporter-destination=stdout
158
+ --test-reporter=@pghoya2956/livemap/src/reporters/node-results.mjs
159
+ --test-reporter-destination=map/.out/node-results.json
160
+ tests/*.test.mjs
161
+ - run: npx --no playwright test --reporter=list,json
162
+ env: { PLAYWRIGHT_JSON_OUTPUT_NAME: map/.out/playwright.json }
163
+ - if: always()
164
+ run: |
165
+ npx --no livemap test-report --import map/.out/node-results.json
166
+ npx --no livemap test-report --import map/.out/playwright.json
167
+ - if: always()
168
+ uses: actions/upload-artifact@v4
169
+ with: { name: test-results, path: map/.out/test-results.json }
170
+
171
+ board:
172
+ needs: [tests]
173
+ if: always()
174
+ runs-on: ubuntu-latest
175
+ steps:
176
+ - uses: actions/checkout@v4
177
+ with: { fetch-depth: 0 }
178
+ - uses: actions/setup-node@v4
179
+ with: { node-version: 22 }
180
+ - run: npm ci
181
+ - uses: actions/download-artifact@v4
182
+ with: { name: test-results, path: map/.out }
183
+ continue-on-error: true
184
+ - run: npm run map && npm run map:export
185
+ ```
186
+
187
+ - `fetch-depth: 0`이 없으면 얕은 클론이라 결과 커밋을 찾지 못해 검사 신호가 "?"다.
188
+ - 빌드 잡이 검사 잡을 기다리지 않는 파이프라인(검사가 제품 경로가 바뀐 push에서만 돌거나 이미지와 동시에 도는 경우)은 가장 최근에 결과 아티팩트를 올린 실행에서 받는다(`gh run list`로 실행을 찾고 `gh run download`, 잡 권한에 `actions: read`). 문서만 바꾼 push의 상황판은 그 결과를 최신으로 싣고, 코드를 바꾼 push는 그 커밋 검사가 끝나기 전이라 검사 신호가 "?"(stale)다.
189
+ - 로컬에서는 검사를 돌린 세션이 `npm run test:report`와 브라우저 검사 `--import`로 결과 JSON을 갱신한다. `map:serve`는 코드가 그대로인 동안 그 결과를 최신으로 본다.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pghoya2956/livemap",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Project status board engine: scans a repository into a graph and serves a one-screen map of journeys, screens, APIs, tests and work.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/site/map.css CHANGED
@@ -339,6 +339,24 @@ a.row:hover .tt,button.row:hover .tt{color:#fff}
339
339
  [data-screen="tasks"] .tk-num{font:12px var(--mono);white-space:nowrap}
340
340
  [data-screen="tasks"] .t-폐기{color:var(--red);background:var(--red-soft)}
341
341
  [data-screen="tasks"] .t-기록{color:var(--muted);background:transparent}
342
+ [data-screen="tasks"] .tk-cols{margin-top:10px}
343
+ [data-screen="tasks"] .tk-src{margin-top:8px}
344
+ [data-screen="tasks"] .tk-reading{margin-top:12px;padding-top:8px;border-top:1px solid var(--line)}
345
+ [data-screen="tasks"] .tk-reading h4{margin:0 0 6px}
346
+
347
+ /* 읽기 상태 표기(작업·더보기): "?"·이유 분류·판정 */
348
+ .rd-q{color:var(--amber);font-weight:700}
349
+ .rd-why{margin-left:6px;font:10.5px var(--sans);color:var(--amber);white-space:nowrap}
350
+ .rd-list{list-style:none;margin:0 0 8px;padding:0}
351
+ .rd-list li{padding:5px 0;border-bottom:1px solid #121820;font-size:12px;color:var(--ink)}
352
+ .rd-list li.warn{color:var(--amber)}
353
+ .rd-list li.error{color:var(--red)}
354
+ .rd-note{margin-top:2px;font-size:11.5px;color:var(--muted);white-space:normal;overflow-wrap:anywhere}
355
+ .rd-code{font:10.5px var(--mono);color:var(--muted)}
356
+ .rd-excerpt{color:var(--dim)}
357
+ .rd-judged{font-size:12px}
358
+ .rd-ok{margin:0;font-size:12px}
359
+ .t-judged{color:var(--cyan);background:var(--cyan-soft)}
342
360
 
343
361
  /* routes: more */
344
362
  [data-screen="more"] .tabs{display:flex;gap:6px;flex-wrap:wrap;margin:0 0 14px}
@@ -377,6 +395,13 @@ a.row:hover .tt,button.row:hover .tt{color:#fff}
377
395
  [data-screen="more"] .mm-issue{padding:6px 0;border-bottom:1px solid #121820;font-size:12.5px}
378
396
  [data-screen="more"] .mm-issue.warn{color:var(--amber)}
379
397
  [data-screen="more"] .mm-issue.error{color:var(--red)}
398
+ [data-screen="more"] .mm-issue-group{padding:4px 0 8px}
399
+ [data-screen="more"] .mm-issue-hd{padding:4px 0;font-size:12.5px;color:var(--ink)}
400
+ [data-screen="more"] .mm-issue-hd.warn b{color:var(--amber)}
401
+ [data-screen="more"] .mm-issue-hd.error b{color:var(--red)}
402
+ [data-screen="more"] .mm-rd-values{display:flex;flex-wrap:wrap;gap:6px 14px;margin:8px 0 10px;font-size:12px;color:var(--muted)}
403
+ [data-screen="more"] .mm-rd-v b{color:var(--ink)}
404
+ [data-screen="more"] .mm-rd-v.warn b,[data-screen="more"] .mm-warn{color:var(--amber)}
380
405
  [data-screen="more"] .mm-prose h3{margin:14px 0 6px;font-size:13px}
381
406
  [data-screen="more"] .mm-prose h3:first-child{margin-top:0}
382
407
  [data-screen="more"] .mm-prose p{margin:0 0 8px;font-size:12.5px;color:#C9D2DB;line-height:1.55}