Compare commits

...
4 Commits
Author SHA1 Message Date
ewderna a80f5ea215 agent: add AGENTS.md for codex (simply redirect) 2026-09-22 19:57:13 +09:00
ewdernaandClaude Opus 5 5b671976d5 docs: 保留課題をbacklogへ分離し、運用ルール5を追加
未確認事項が「現在の仕様が答えに依存するもの」と「答えが出なくても進められる
もの」を混ぜて抱えていた。後者を backlog/README.md へ移し、仕様書の未確認事項を
前者だけに絞る。移す際は保留中の対応範囲・暫定仕様を仕様書本文に明記し、
backlogへの移動が仮説の採用を意味しないようにする。

PHY・ELC・IDXの10件を移し、3ファイルとも未確認事項が空になった。
ルール4では、事実確認の「格上げ条件」と意思決定の「完了条件」を区別する。
仕様書側の保留課題はbacklogの区分へのリンク箇条書きだけに固定し、
末尾は保留課題 → 検討済み・非採用 → 未確認事項の順に置く。

doc-lintをルール5に対応させ、backlogを検査対象に加えた。回帰テストを追加。

Journal: journal/2026-09-22-document-backlog.md

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DKW58HEg8zAQoLrkWByPfD
2026-09-22 19:52:39 +09:00
ewdernaandClaude Opus 5 66a594a378 elc: 走査方向の決定と追加指定をFirmware Generatorに置く
ELCは配線を保存し、どちら側のピン群を駆動してどちら側から読み取るかは
出力器が選ぶ。追加指定が必要な場合は出力設定で受け取る。
複数の適切な方法から出力器が選べるなら一意性は要らないので、配線だけから
全方式の走査手順が一意に決まることをELCの成立条件にしない。

これは責任分担の決定であり、各方式の実配線を検証した結果ではない。
P-3は方式ごとの走査設定の確認として残す。

Journal: journal/2026-09-22-elc-scan-settings.md

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DKW58HEg8zAQoLrkWByPfD
2026-09-22 19:52:14 +09:00
ewdernaandClaude Opus 5 76c349fa0c idx: 全バリアントを1つのorderに並べ、出力時に取捨して番号を詰める
排他キーも別々の要素として並べる。出力はバリアントを選択した構成を単位とし、
対象外のキーを除いた並びの位置をそのままキー番号にする。同じ配線の排他キーは
選択後に同時には残らないため、番号を共有させる必要がない。
保存したorderは選択で書き換えず、番号の互換性は出力レイアウトごとに扱う。

6d0eba9 で「IDXは番号を共有できる」と書いたが、共有をやめる形に変える。
コミュニティレイアウトの並びを取捨で得られるかは、この方式では未検証のため
IDXのP-1として残す。

Journal: journal/2026-09-22-idx-variant-output.md

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DKW58HEg8zAQoLrkWByPfD
2026-09-22 18:36:09 +09:00
15 changed files with 692 additions and 164 deletions
+26 -6
View File
@@ -1,6 +1,6 @@
--- ---
name: doc-lint name: doc-lint
description: ドキュメントがCLAUDE.mdの運用ルール1〜4に沿っているか検査する。推測タグと未確認事項の対応漏れ、journalへのリンク、比較表の混入などを検出する。 description: ドキュメントがCLAUDE.mdの運用ルール1〜5に沿っているか検査する。推測タグと未確認事項の対応漏れ、保留課題の書式・参照・必須欄、journalへのリンク、比較表の混入などを検出する。
argument-hint: [ファイル...] argument-hint: [ファイル...]
--- ---
@@ -15,15 +15,22 @@ argument-hint: [ファイル...]
python .claude/skills/doc-lint/doc-lint.py [ファイル...] python .claude/skills/doc-lint/doc-lint.py [ファイル...]
``` ```
引数なしで直下の `*.md` と `journal/*.md` を全部見る。ERROR があれば終了コード 1。 Python 3.9以上で実行する。引数なしでリポジトリ直下の `*.md`、`journal/*.md`、`backlog/**/*.md` を全部見る。
実行場所にかかわらずリポジトリを基準に分類し、明示したパスは実行ディレクトリから解決する。
ERROR があれば終了コード 1。存在しない引数のファイルもERRORにする。
検出する項目: 検出する項目:
| 種別 | 内容 | | 種別 | 内容 |
|---|---| |---|---|
| ERROR | 本文の `[推測:P-n]` に対応する `## 未確認事項` の項目がない | | ERROR | 本文の `[推測:P-n]` に対応する `## 未確認事項` の項目がない |
| ERROR | `## 未確認事項` の項目に `- 格上げ条件:` の行がない | | ERROR | 本文の推測タグに対応する項目に `- 格上げ条件:` の内容がない |
| ERROR | タグのない未確認事項に `- 格上げ条件:` または `- 完了条件:` の内容がない |
| ERROR | `## 未確認事項` のIDが重複している | | ERROR | `## 未確認事項` のIDが重複している |
| ERROR | 仕様書の `## 保留課題` がリンク箇条書き以外を含む、空である、参照先ファイル・見出しがない |
| ERROR | 末尾の管理セクションが重複する、保留課題 → 検討済み・非採用 → 未確認事項の順でない |
| ERROR | backlogの項目IDが不正・検査対象内で重複する、必須5欄がない・空である |
| ERROR | backlogに推測タグがある、参照先ファイルが存在しない |
| ERROR | journal へのリンク / journal から他ファイルへのリンク | | ERROR | journal へのリンク / journal から他ファイルへのリンク |
| ERROR | `## 検討済み・非採用` の項目が journal ファイル名を参照していない | | ERROR | `## 検討済み・非採用` の項目が journal ファイル名を参照していない |
| ERROR | journal のファイル名が `YYYY-MM-DD-<topic>.md` 形式でない | | ERROR | journal のファイル名が `YYYY-MM-DD-<topic>.md` 形式でない |
@@ -31,7 +38,12 @@ python .claude/skills/doc-lint/doc-lint.py [ファイル...]
| WARN | journal の冒頭20行に結論が見当たらない | | WARN | journal の冒頭20行に結論が見当たらない |
| WARN | メインドキュメントに比較表・優先度評価・却下案の記述らしきもの | | WARN | メインドキュメントに比較表・優先度評価・却下案の記述らしきもの |
**本文タグのない `## 未確認事項` の項目はエラーにしない。** 「まだ本文に書けない未解決の問い」として正当。 **本文タグのない `## 未確認事項` の項目はエラーにしない。** 実装前に判断が必要な未解決の問いとして正当。
設計判断は「完了条件」でよい。タグなしの項目が事実確認なのか設計判断なのかは、目視で確認する。
backlogでは `### PHY-P-14 題名` のように区分付きIDを使い、関連仕様・未決事項・保留できる理由・再開条件・完了条件を必須とする。
仕様書側の書式は `- [PHYの保留課題](backlog/README.md#phy)`。
backlogは短い候補・制約を保持できるため、メインドキュメント向けの散文ヒューリスティックは適用しない。
WARN の散文検出は当たりが粗い。取りこぼしも誤検出もあるので、出た/出ないを結論にしない。 WARN の散文検出は当たりが粗い。取りこぼしも誤検出もあるので、出た/出ないを結論にしない。
@@ -43,10 +55,12 @@ WARN の散文検出は当たりが粗い。取りこぼしも誤検出もある
却下理由ではなく、現在効いている制約として書かれているか。 却下理由ではなく、現在効いている制約として書かれているか。
- ✗ `階層構造はKLEが非対応なので諦めた` - ✗ `階層構造はKLEが非対応なので諦めた`
- ○ `階層クラスターはKLE JSONに出力しない(KLE互換性の制約)` - ○ `階層クラスターはKLE JSONに出力しない(KLE互換性の制約)`
- **裏取りしていない記述にタグが付いていないか。** 検査項目の中で最も重要で、 - **本文が前提にする未確認の記述にタグが付いているか。** 検査項目の中で最も重要で、
機械では判定できない。「〜できるはず」「〜と考えられる」「実現可能」で実際に試していない 機械では判定できない。「〜できるはず」「〜と考えられる」「実現可能」で実際に試していない
記述を探し、`[推測:P-n]` が付いているか確認する。 記述を探し、`[推測:P-n]` が付いているか確認する。
- **同じ事実が複数ドキュメントに複製されていないか。** 片方だけ古くなる。 - **同じ事実が複数ドキュメントに複製されていないか。** 片方だけ古くなる。
- **保留中の対応範囲が仕様本文にあるか。** 現在の実装が答えに依存する前提をbacklogへ隠していないか。
- **再開条件・完了条件が具体的か。** 仕様書の保留課題リンクは該当区分に向き、解決済みの課題が残っていないか。
- **journal の冒頭20行で、結論と選択肢が分かるか。** 見出しの有無ではなく中身を読む。 - **journal の冒頭20行で、結論と選択肢が分かるか。** 見出しの有無ではなく中身を読む。
- **コミット規約**(必要なら `git log --oneline` を見る) - **コミット規約**(必要なら `git log --oneline` を見る)
- 1コミット1決定になっているか - 1コミット1決定になっているか
@@ -59,6 +73,12 @@ ERROR / WARN / 目視で見つけたもの を分けて出す。
## 注意 ## 注意
- 運用ルール策定前に書かれた既存ドキュメントは、まだルール1〜4が適用されていない。 - 既存ドキュメントへのルール適用は段階的に進めている。
WARN が大量に出るのは想定内で、再編(`/restructure-doc`)のタイミングで解消する。 WARN が大量に出るのは想定内で、再編(`/restructure-doc`)のタイミングで解消する。
再編前のファイルの WARN を個別に直そうとしない。 再編前のファイルの WARN を個別に直そうとしない。
スクリプトを変更した場合の回帰検査:
```
python -B -m unittest discover -s .claude/skills/doc-lint -p test_doc_lint.py
```
+135 -21
View File
@@ -1,16 +1,17 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""CLAUDE.md の運用ルール1〜4のうち、機械的に判定できる項目を検査する。 """CLAUDE.md の運用ルール1〜5のうち、機械的に判定できる項目を検査する。
使い方: 使い方:
python .claude/skills/doc-lint/doc-lint.py [ファイル...] python .claude/skills/doc-lint/doc-lint.py [ファイル...]
引数なしなら リポジトリ直下の *.md と journal/*.md を対象にする。 引数なしならリポジトリ直下の *.md、journal/*.md、backlog/**/*.md が対象。
明示したパスは実行ディレクトリから解決する。
ERROR が1件でもあれば終了コード 1。 ERROR が1件でもあれば終了コード 1。
""" """
import sys, re, os, io, glob import re
import sys
if hasattr(sys.stdout, "reconfigure"): from pathlib import Path
sys.stdout.reconfigure(encoding="utf-8") from urllib.parse import unquote, urlsplit
TAG = re.compile(r"\[推測:(P-\d+)\]") TAG = re.compile(r"\[推測:(P-\d+)\]")
ENTRY = re.compile(r"^###\s+(P-\d+)\b") ENTRY = re.compile(r"^###\s+(P-\d+)\b")
@@ -18,6 +19,12 @@ H2 = re.compile(r"^##\s+(.*?)\s*$")
MDLINK = re.compile(r"\]\(([^)]+\.md[^)]*)\)") MDLINK = re.compile(r"\]\(([^)]+\.md[^)]*)\)")
JOURNALFN= re.compile(r"^\d{4}-\d{2}-\d{2}-.+\.md$") JOURNALFN= re.compile(r"^\d{4}-\d{2}-\d{2}-.+\.md$")
PROMOTE = re.compile(r"^\s*[-*]\s*格上げ条件\s*[::]\s*\S") PROMOTE = re.compile(r"^\s*[-*]\s*格上げ条件\s*[::]\s*\S")
COMPLETE = re.compile(r"^\s*[-*]\s*完了条件\s*[::]\s*\S")
BACKLOG_ENTRY = re.compile(r"^###\s+([A-Za-z][A-Za-z0-9_-]*-P-\d+)\s+\S")
BACKLOG_LINK = re.compile(r"^- \[[^\]]+の保留課題\]\(([^)]+)\)$")
BACKLOG_FIELDS = ("関連仕様", "未決事項", "保留できる理由", "再開条件", "完了条件")
MANAGEMENT_SECTIONS = ("保留課題", "検討済み・非採用", "未確認事項")
ROOT = Path(__file__).resolve().parents[3]
# ルールブック自身は、アンチパターンを例として引用するため散文ヒューリスティックの対象外 # ルールブック自身は、アンチパターンを例として引用するため散文ヒューリスティックの対象外
PROSE_EXEMPT = {"CLAUDE.md"} PROSE_EXEMPT = {"CLAUDE.md"}
@@ -28,7 +35,7 @@ def add(sev, path, line, msg):
def load(path): def load(path):
"""コードフェンス内を空行に潰した (行番号, 本文) の列を返す。""" """コードフェンス内を空行に潰した (行番号, 本文) の列を返す。"""
raw = io.open(path, encoding="utf-8").read().splitlines() raw = Path(path).read_text(encoding="utf-8").splitlines()
out, fence = [], False out, fence = [], False
for i, l in enumerate(raw, 1): for i, l in enumerate(raw, 1):
if l.lstrip().startswith("```"): if l.lstrip().startswith("```"):
@@ -55,15 +62,109 @@ def section(lines, title):
body.append((n, l)) body.append((n, l))
return body return body
def check(path): def local_target(path, target):
rel = path.replace(os.sep, "/") """相対リンクをリンク元基準で解決する。外部URLは対象外。"""
base = os.path.basename(rel) parsed = urlsplit(target)
is_journal = rel.startswith("journal/") if parsed.scheme or parsed.netloc:
return None, ""
linked = (path.parent / unquote(parsed.path)).resolve() if parsed.path else path
return linked, unquote(parsed.fragment)
def check_backlog(path, rel, lines, seen_ids):
blocks = []
current = None
for n, line in lines:
if H2.match(line):
current = None
elif re.match(r"^###\s", line):
match = BACKLOG_ENTRY.match(line)
current = None
if not match:
add("ERROR", rel, n, "保留課題の見出しは ### <区分>-P-<番号> <題名> とする")
continue
pid = match.group(1)
if pid in seen_ids:
add("ERROR", rel, n, "保留課題のID %s が重複している(初出: %s)" % (pid, seen_ids[pid]))
seen_ids[pid] = "%s:%d" % (rel, n)
current = (pid, n, [])
blocks.append(current)
elif current is not None:
current[2].append(line)
for pid, n, body in blocks:
for field in BACKLOG_FIELDS:
pattern = re.compile(r"^[-*]\s+" + re.escape(field) + r"\s*[::]\s*\S")
if not any(pattern.match(line) for line in body):
add("ERROR", rel, n, "%s に「- %s:」の内容がない" % (pid, field))
for n, line in lines:
if TAG.search(line):
add("ERROR", rel, n, "本文の前提となる推測タグはbacklogではなく仕様書に置く")
for target in MDLINK.findall(line):
linked, _ = local_target(path, target)
if linked is not None and not linked.is_file():
add("ERROR", rel, n, "backlogの参照先ファイルがない: %s" % target)
def check_backlog_section(path, rel, lines):
positions = []
for title in MANAGEMENT_SECTIONS:
headings = [n for n, line in lines if H2.match(line) and H2.match(line).group(1) == title]
if len(headings) > 1:
add("ERROR", rel, headings[1], "## %s が重複している" % title)
if headings:
positions.append(headings[0])
if positions != sorted(positions):
add("ERROR", rel, min(positions), "末尾の管理セクションは保留課題 → 検討済み・非採用 → 未確認事項の順に置く")
deferred = section(lines, "保留課題")
if deferred is None:
return
contents = [(n, line) for n, line in deferred if line.strip()]
if not contents:
add("ERROR", rel, 1, "空の ## 保留課題 はセクションごと削除する")
for n, line in contents:
match = BACKLOG_LINK.fullmatch(line)
if not match:
add("ERROR", rel, n, "保留課題には - [<区分>の保留課題](backlogのファイル#見出し) だけを書く")
continue
target = match.group(1)
linked, anchor = local_target(path, target)
if (linked is None or not linked.is_relative_to(ROOT / "backlog")
or linked.suffix != ".md" or not anchor):
add("ERROR", rel, n, "保留課題はbacklog内のMarkdownファイルの見出しへリンクする: %s" % target)
continue
if not linked.is_file():
add("ERROR", rel, n, "保留課題の参照先ファイルがない: %s" % target)
continue
headings = set()
for _, linked_line in load(linked):
heading = re.match(r"^#{1,6}\s+(.+?)\s*#*\s*$", linked_line)
if heading:
slug = re.sub(r"[^\w\s-]", "", heading.group(1)).lower().replace(" ", "-")
headings.add(slug)
if anchor not in headings:
add("ERROR", rel, n, "保留課題の参照先見出しがない: %s" % target)
def check(path, seen_backlog_ids=None):
path = Path(path).resolve()
rel = path.relative_to(ROOT).as_posix() if path.is_relative_to(ROOT) else path.as_posix()
base = path.name
is_journal = path.is_relative_to(ROOT / "journal")
is_backlog = path.is_relative_to(ROOT / "backlog")
lines = load(path) lines = load(path)
if is_backlog:
check_backlog(path, rel, lines, seen_backlog_ids if seen_backlog_ids is not None else {})
elif not is_journal:
check_backlog_section(path, rel, lines)
unconf = section(lines, "未確認事項") unconf = section(lines, "未確認事項")
rejected = section(lines, "検討済み・非採用") rejected = section(lines, "検討済み・非採用")
unconf_span = set(n for n, _ in unconf) if unconf else set() unconf_span = set(n for n, _ in unconf) if unconf else set()
tagged = {pid for n, line in lines if n not in unconf_span for pid in TAG.findall(line)}
# --- ルール4: 推測タグと未確認事項の対応 --- # --- ルール4: 推測タグと未確認事項の対応 ---
entries = set() entries = set()
@@ -80,9 +181,14 @@ def check(path):
elif blocks: elif blocks:
blocks[-1][2].append(l) blocks[-1][2].append(l)
for pid, ln, body in blocks: for pid, ln, body in blocks:
if not any(PROMOTE.match(x) for x in body): has_promote = any(PROMOTE.match(x) for x in body)
has_complete = any(COMPLETE.match(x) for x in body)
if pid in tagged and not has_promote:
add("ERROR", rel, ln, add("ERROR", rel, ln,
"%s に「- 格上げ条件:」の行がない(何を確認すれば事実に格上げできるか)" % pid) "%s に「- 格上げ条件:」の行がない(何を確認すれば事実に格上げできるか)" % pid)
elif not has_promote and not has_complete:
add("ERROR", rel, ln,
"%s に「- 格上げ条件:」または「- 完了条件:」の内容がない" % pid)
for n, l in lines: for n, l in lines:
if n in unconf_span: if n in unconf_span:
@@ -96,9 +202,10 @@ def check(path):
# --- ルール3: journal への/からのリンク禁止 --- # --- ルール3: journal への/からのリンク禁止 ---
for n, l in lines: for n, l in lines:
for target in MDLINK.findall(l): for target in MDLINK.findall(l):
if target.lstrip("./").startswith("journal/"): linked, _ = local_target(path, target)
if linked is not None and linked.is_relative_to(ROOT / "journal"):
add("ERROR", rel, n, "journal へのリンクが張られている: %s" % target) add("ERROR", rel, n, "journal へのリンクが張られている: %s" % target)
elif is_journal: elif is_journal and linked is not None:
add("ERROR", rel, n, "journal から他ファイルへのリンクが張られている: %s" % target) add("ERROR", rel, n, "journal から他ファイルへのリンクが張られている: %s" % target)
# --- ルール1: 検討済み・非採用 --- # --- ルール1: 検討済み・非採用 ---
@@ -121,7 +228,7 @@ def check(path):
add("WARN", rel, 1, "冒頭20行に結論が見当たらない(結論を先頭に置く)") add("WARN", rel, 1, "冒頭20行に結論が見当たらない(結論を先頭に置く)")
# --- ルール1: 調査・比較の混入(要目視確認) --- # --- ルール1: 調査・比較の混入(要目視確認) ---
if not is_journal and base not in PROSE_EXEMPT: if not is_journal and not is_backlog and base not in PROSE_EXEMPT:
# 検討済み・非採用 は却下案を列挙する場所なので対象外 # 検討済み・非採用 は却下案を列挙する場所なので対象外
skip = set(n for n, _ in rejected) if rejected else set() skip = set(n for n, _ in rejected) if rejected else set()
for n, l in lines: for n, l in lines:
@@ -135,16 +242,21 @@ def check(path):
add("WARN", rel, n, "優先度評価は journal 行きでは?") add("WARN", rel, n, "優先度評価は journal 行きでは?")
def main(argv): def main(argv):
targets = argv[1:] issues.clear()
targets = [Path(arg).resolve() for arg in argv[1:]]
if not targets: if not targets:
targets = sorted(glob.glob("*.md")) + sorted(glob.glob("journal/*.md")) targets = (sorted(ROOT.glob("*.md")) + sorted((ROOT / "journal").glob("*.md"))
+ sorted((ROOT / "backlog").rglob("*.md")))
targets = list(dict.fromkeys(targets))
if not targets: if not targets:
print("対象ファイルなし"); return 0 print("対象ファイルなし")
return 0
seen_backlog_ids = {}
for t in targets: for t in targets:
if os.path.isfile(t): if t.is_file():
check(t) check(t, seen_backlog_ids)
else: else:
print("見つからない: %s" % t) add("ERROR", t.as_posix(), 1, "対象ファイルが見つからない")
errs = [i for i in issues if i[0] == "ERROR"] errs = [i for i in issues if i[0] == "ERROR"]
warns = [i for i in issues if i[0] == "WARN"] warns = [i for i in issues if i[0] == "WARN"]
@@ -154,4 +266,6 @@ def main(argv):
return 1 if errs else 0 return 1 if errs else 0
if __name__ == "__main__": if __name__ == "__main__":
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(encoding="utf-8")
sys.exit(main(sys.argv)) sys.exit(main(sys.argv))
+150
View File
@@ -0,0 +1,150 @@
"""文書の分類・参照・完了条件の回帰検査。標準ライブラリだけで実行する。"""
import contextlib
import importlib.util
import io
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
spec = importlib.util.spec_from_file_location("doc_lint", Path(__file__).with_name("doc-lint.py"))
lint = importlib.util.module_from_spec(spec)
spec.loader.exec_module(lint)
def backlog_item(pid="PHY-P-14"):
return (
f"### {pid} 原点の吸着\n\n"
"- 関連仕様: [PHY](../PHY.md)\n"
"- 未決事項: 吸着が必要か\n"
"- 保留できる理由: 数値入力で編集できる\n"
"- 再開条件: 原点移動を試作するとき\n"
"- 完了条件: 吸着の要否を決める\n"
)
class DocLintTests(unittest.TestCase):
def setUp(self):
temporary = tempfile.TemporaryDirectory()
self.addCleanup(temporary.cleanup)
self.root = Path(temporary.name).resolve()
root_patch = patch.object(lint, "ROOT", self.root)
root_patch.start()
self.addCleanup(root_patch.stop)
self.write("PHY.md", "# PHY\n")
def write(self, relative, content):
path = self.root / relative
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content, encoding="utf-8")
return path
def run_lint(self, *paths):
output = io.StringIO()
with contextlib.redirect_stdout(output):
status = lint.main(["doc-lint.py", *(str(path) for path in paths)])
return status, output.getvalue()
def test_default_scan_includes_backlog_and_accepts_fixed_links(self):
self.write("PHY.md", "# PHY\n\n## 保留課題\n\n- [PHYの保留課題](backlog/README.md#phy)\n")
self.write("backlog/README.md", "# 保留課題\n\n## PHY\n\n" + backlog_item())
self.write("journal/2026-09-22-decision.md", "# 決定\n\n## 結論\n\n採用した。\n")
status, output = self.run_lint()
self.assertEqual(status, 0, output)
self.assertIn("3 files / ERROR 0", output)
def test_backlog_fields_must_have_values(self):
for field in lint.BACKLOG_FIELDS:
with self.subTest(field=field):
text = backlog_item()
text = "\n".join(f"- {field}: " if line.startswith(f"- {field}:") else line
for line in text.splitlines())
path = self.write("backlog/README.md", "## PHY\n" + text)
status, output = self.run_lint(path)
self.assertEqual(status, 1)
self.assertIn(f"- {field}:", output)
def test_duplicate_backlog_ids_across_nested_files(self):
self.write("backlog/one.md", "## PHY\n" + backlog_item())
self.write("backlog/sub/two.md", "## PHY\n" + backlog_item().replace("../PHY.md", "../../PHY.md"))
status, output = self.run_lint()
self.assertEqual(status, 1)
self.assertIn("保留課題のID PHY-P-14 が重複", output)
def test_incomplete_last_item_cannot_borrow_fields_from_next_section(self):
self.write("backlog/README.md", "## PHY\n### PHY-P-14 吸着\n\n## ELC\n" + backlog_item("ELC-P-1"))
status, output = self.run_lint()
self.assertEqual(status, 1)
self.assertIn("PHY-P-14 に「- 完了条件:」", output)
def test_invalid_backlog_heading_and_missing_reference(self):
path = self.write("backlog/README.md", "## PHY\n### P-14 吸着\n" + backlog_item().replace("../PHY.md", "../missing.md"))
status, output = self.run_lint(path)
self.assertEqual(status, 1)
self.assertIn("保留課題の見出し", output)
self.assertIn("参照先ファイルがない", output)
def test_spec_backlog_section_rejects_prose_empty_and_broken_targets(self):
self.write("backlog/README.md", "## PHY\n" + backlog_item())
cases = {
"内容をここにも要約する。": "だけを書く",
"": "空の ## 保留課題",
"- [PHYの保留課題](backlog/missing.md#phy)": "参照先ファイルがない",
"- [PHYの保留課題](backlog/README.md#missing)": "参照先見出しがない",
"- [PHYの保留課題](backlog/README.md)": "見出しへリンク",
"- [PHYの保留課題](PHY.md#phy)": "backlog内",
}
for content, expected in cases.items():
with self.subTest(content=content):
path = self.write("PHY.md", "# PHY\n\n## 保留課題\n\n" + content + "\n")
status, output = self.run_lint(path)
self.assertEqual(status, 1)
self.assertIn(expected, output)
def test_management_sections_have_one_order(self):
path = self.write("PHY.md", "## 未確認事項\n\n(なし)\n\n## 検討済み・非採用\n\n## 未確認事項\n")
status, output = self.run_lint(path)
self.assertEqual(status, 1)
self.assertIn("順に置く", output)
self.assertIn("## 未確認事項 が重複", output)
def test_decisions_accept_completion_but_tagged_assumptions_require_evidence(self):
text = "## 未確認事項\n\n### P-1 操作方式\n- 完了条件: 操作方式を決める\n"
path = self.write("PHY.md", text)
self.assertEqual(self.run_lint(path)[0], 0)
path.write_text("本文 [推測:P-1]\n\n" + text, encoding="utf-8")
status, output = self.run_lint(path)
self.assertEqual(status, 1)
self.assertIn("格上げ条件", output)
path.write_text("本文 [推測:P-1]\n\n" + text.replace("完了条件", "格上げ条件"), encoding="utf-8")
self.assertEqual(self.run_lint(path)[0], 0)
def test_absolute_journal_paths_keep_journal_checks(self):
path = self.write("journal/bad.md", "# 調査\n\n[仕様](../PHY.md)\n")
status, output = self.run_lint(path)
self.assertEqual(status, 1)
self.assertIn("journal のファイル名", output)
self.assertIn("journal から他ファイルへのリンク", output)
def test_backlog_is_not_prose_linted_but_cannot_link_to_journal(self):
self.write("journal/2026-09-22-decision.md", "## 結論\n")
path = self.write("backlog/README.md", "## PHY\n" + backlog_item() + "\n候補はA案。\n[履歴](../journal/2026-09-22-decision.md)\n")
status, output = self.run_lint(path)
self.assertEqual(status, 1)
self.assertIn("journal へのリンク", output)
self.assertIn("WARN 0", output)
def test_code_examples_do_not_create_real_sections_or_tags(self):
path = self.write("PHY.md", "```markdown\n## 保留課題\n[推測:P-9]\n```\n")
status, output = self.run_lint(path)
self.assertEqual(status, 0, output)
def test_missing_explicit_file_is_an_error(self):
status, output = self.run_lint(self.root / "missing.md")
self.assertEqual(status, 1)
self.assertIn("対象ファイルが見つからない", output)
if __name__ == "__main__":
unittest.main()
+1
View File
@@ -0,0 +1 @@
REDIRECT: CLAUDE.mdに準じる
+42 -7
View File
@@ -17,16 +17,19 @@ PHY(物理配置) / ELC(電気配線) / IDX(キー識別) / MAP(キーマップ)
| `phase1-json-schema.md` | 各層のJSON Schema | | `phase1-json-schema.md` | 各層のJSON Schema |
| `keyboard-making-workflow.md` | 既存の自作キーボード制作フローとの対応 | | `keyboard-making-workflow.md` | 既存の自作キーボード制作フローとの対応 |
| `implementation-plan.md` | 実装方針の決定事項・検証用サンプル | | `implementation-plan.md` | 実装方針の決定事項・検証用サンプル |
| [`backlog/README.md`](backlog/README.md) | 後回しにできる問い・検証・改善候補 |
**既存ドキュメントは下記ルールの策定前に書かれたもので、ルール1〜4は未適用。** 既存ドキュメントには下記ルールを段階的に適用する。PHY・ELC・IDXの保留課題はbacklogへ分離済み。
全体の再編は別タスクとして行う。それまで各ファイルをメイン/journalのどちらに分類するかは未確定なので、 全体の再編は別タスクとして行い、指示なく他の内容を移動・分割・書き直ししない。
指示なく移動・分割・書き直しをしない。
初期コミット以前の経緯は追跡できない。現在のドキュメント群を「初期検討段階」のベースラインとして扱い、 初期コミット以前の経緯は追跡できない。現在のドキュメント群を「初期検討段階」のベースラインとして扱い、
それ以前の決定理由を掘り起こそうとしない。 それ以前の決定理由を掘り起こそうとしない。
## ドキュメント運用ルール ## ドキュメント運用ルール
現在の仕様・将来取り組む課題・過去の検討を分け、仕様を読む際に判断状況が混ざらないようにする。
未確認の前提は確定事項と区別し、保留課題は再開するまで見つけられる状態に保つ。
### 1. メインドキュメント(リポジトリ直下の `*.md`) ### 1. メインドキュメント(リポジトリ直下の `*.md`)
書くもの: **採用した案 / 現在の設定 / 設計に影響する制約**。 書くもの: **採用した案 / 現在の設定 / 設計に影響する制約**。
@@ -62,10 +65,12 @@ PHY(物理配置) / ELC(電気配線) / IDX(キー識別) / MAP(キーマップ)
- **裏取りの済んだ事実**は、ドキュメント内のどこに書いてもよい。文中に自然に混ぜてよい。 - **裏取りの済んだ事実**は、ドキュメント内のどこに書いてもよい。文中に自然に混ぜてよい。
ただし同じ事実を複数ドキュメントに複製しない。必要ならリンクする。 ただし同じ事実を複数ドキュメントに複製しない。必要ならリンクする。
- **未確認の推測**だけを末尾に隔離する。2種類ある: - **現在の仕様・実装が答えに依存する未確認事項**は、仕様書の末尾に残す。2種類ある:
- 本文が前提にしている推測 → 本文に `[推測:P-n]` を付け、末尾の `## 未確認事項` に対応項目を置く - 本文が前提にしている推測 → 本文に `[推測:P-n]` を付け、末尾の `## 未確認事項` に対応項目を置く
- まだ本文に書けない未解決の問い → 本文タグなしで `## 未確認事項` に直接置く - 実装前に判断が必要な未解決の問い → 本文タグなしで `## 未確認事項` に直接置く
- `## 未確認事項` の各項目には、**何を確認できれば事実に格上げできるか**を必ず書く。 - 未確認の推測には、**何を確認できれば事実に格上げできるか**を「格上げ条件」として書く。
設計判断には、**何を決めれば解決するか**を「完了条件」として書く。意思決定を事実確認と混同しない。
- 答えに依存せず進められる課題は `backlog/` に置く(ルール5)。保留中の対応範囲・暫定仕様は仕様書に残す。
- 検証できたらタグを外し、該当項目を削除する。 - 検証できたらタグを外し、該当項目を削除する。
- 「今回検討して捨てた仮説」は `## 未確認事項` に書かない。journal に残して消す。 - 「今回検討して捨てた仮説」は `## 未確認事項` に書かない。journal に残して消す。
- 推測に基づいて実装しない。先に検証するか、ユーザーに確認する。 - 推測に基づいて実装しない。先に検証するか、ユーザーに確認する。
@@ -80,9 +85,39 @@ PHY(物理配置) / ELC(電気配線) / IDX(キー識別) / MAP(キーマップ)
- 格上げ条件: KLE本家でインポート→エクスポートし、labels[9]が残ることを確認する - 格上げ条件: KLE本家でインポート→エクスポートし、labels[9]が残ることを確認する
``` ```
### 5. 保留課題(`backlog/`)
書くもの: **今は解決しなくても進められる問い・検証・改善候補**。
- 当面は `backlog/README.md` にまとめる。増えて読みにくくなったらレイヤー別・トピック別に分ける。
- 項目には、関連仕様・未決事項・保留できる理由・再開条件・完了条件を書く。
再開条件は「いつ考え直すか」、完了条件は「何を確認または決定すれば終わるか」を示す。
- 仕様書から移す項目は、元の番号にレイヤー名などを付けて識別する(例: `PHY-P-14`)。
判断に必要な候補や制約は短く残す。長い比較・調査ログは `journal/` に置く。
- 問いの本文は二重管理しない。仕様書には保留中の対応範囲・暫定仕様を残し、必要に応じてbacklogへリンクする。
- 仕様書から保留課題を分離した場合、末尾の管理セクションを `## 保留課題` → `## 検討済み・非採用` →
`## 未確認事項` の順に置く(存在するものだけ)。`## 保留課題` には下記のリンク箇条書きだけを書く。
項目名の列挙・問いの要約・保留理由・対応範囲は書かない。対応範囲は仕様本文、問いの詳細はbacklogに置く。
- リンクは `- [<区分>の保留課題](backlog/README.md#<区分の見出し>)` の形とする。
分割後は該当するbacklogファイルへ向ける。複数区分を参照する場合は1リンク1行とし、参照先の見出しまで指定する。
該当課題がなくなったらリンクを削除し、空になった `## 保留課題` はセクションごと削除する。
- 現在の仕様が依存する未確認の前提は、backlogへ移して隠さない。仕様書の `## 未確認事項` に残す。
- backlogへの移動は仮説の採用や実装の許可を意味しない。実装は採用済みの仕様・対応範囲に従う。
- 解決したら必要な結論を仕様書へ反映し、項目と不要になった参照を削除する。取り下げた項目も削除し、完了一覧は蓄積しない。
調査・判断の経緯はルール2・3に従って履歴に残す。
- 未解決の課題は作業ツリーに残す。結論反映後に削除できるjournalとは寿命が異なる。
仕様書側の書式:
```markdown
## 保留課題
- [PHYの保留課題](backlog/README.md#phy)
```
## Claude への作業指示 ## Claude への作業指示
- ドキュメントを更新するときは、上のルール1〜4のどこに属する情報かを判断してから書く。分類に迷うものは書く前に確認する。 - ドキュメントを更新するときは、上のルール1〜5のどこに属する情報かを判断してから書く。分類に迷うものは書く前に確認する。
- 調査タスクの結果は、結論だけをメインドキュメントに書き、調査過程は `journal/` に落とす。 - 調査タスクの結果は、結論だけをメインドキュメントに書き、調査過程は `journal/` に落とす。
- 既存ドキュメントの記述と食い違う事実を見つけたら、勝手に上書きせず指摘する。 - 既存ドキュメントの記述と食い違う事実を見つけたら、勝手に上書きせず指摘する。
+19 -20
View File
@@ -25,6 +25,9 @@ ELCが扱う「ピン」は物理的なマイコンピンに限らない。**ピ
現時点で具体形を定めるのは、マイコンとアナログマルチプレクサの2種別。 現時点で具体形を定めるのは、マイコンとアナログマルチプレクサの2種別。
デジタル側でピンを増やす部品も提供者として扱うが、種別ごとの記述は必要になった時点で定める。 デジタル側でピンを増やす部品も提供者として扱うが、種別ごとの記述は必要になった時点で定める。
同じ `PinRef` が複数の提供者の `select` に現れることは一律には禁止しない。
複数mux構成の走査出力は、実配線と制御方法を確認した構成から対応する。
### キーの端子 ### キーの端子
キーは検出方式(detect)を持ち、検出方式ごとに端子の構成が決まる。各端子は接続先の論理ピンを持つ。 キーは検出方式(detect)を持ち、検出方式ごとに端子の構成が決まる。各端子は接続先の論理ピンを持つ。
@@ -32,7 +35,7 @@ ELCが扱う「ピン」は物理的なマイコンピンに限らない。**ピ
- 接点(contact): a, b の2端子。端子は無極性で、a と b に順序の意味はない - 接点(contact): a, b の2端子。端子は無極性で、a と b に順序の意味はない
- アナログ(analog): out 1端子。加えて電源の vcc, gnd。ホールセンサがこれにあたる - アナログ(analog): out 1端子。加えて電源の vcc, gnd。ホールセンサがこれにあたる
光学スイッチの端子構成は未定 [推測:P-1]。 光学スイッチは当面未対応とする。端子構成を確認するまで、既存の検出方式に仮置きしない。
### 配線トポロジを区分として持たない ### 配線トポロジを区分として持たない
@@ -46,6 +49,12 @@ ELCが扱う「ピン」は物理的なマイコンピンに限らない。**ピ
row / col はELCの正本に含めない。charlieplexing で便宜的に行と列の形で書くことはあるが、 row / col はELCの正本に含めない。charlieplexing で便宜的に行と列の形で書くことはあるが、
それは表記の都合であって配線の事実ではない。QMKなどが要求する row / col は、出力時に割り当てる派生情報とする。 それは表記の都合であって配線の事実ではない。QMKなどが要求する row / col は、出力時に割り当てる派生情報とする。
走査方向の決定と追加指定はFirmware Generatorが受け持ち、ELCには保存しない。
接点マトリクスでは、どちら側のピン群を駆動し、どちら側から読み取るかを指す。
配線・ダイオードの向きと整合する方向を出力器が選び、追加指定が必要な場合は出力設定で受け取る。
配線だけから全方式の走査手順が一意に決まることは前提にしない。
方式ごとの必要な設定は、その方式の出力に対応する際に確認し、未対応の構成は出力しない。
### ダイオード ### ダイオード
接点キーはダイオードの向きを持つ。向きは端子名で表す(`a-to-b` はアノードがa側)。省略時はダイオードなし。 接点キーはダイオードの向きを持つ。向きは端子名で表す(`a-to-b` はアノードがa側)。省略時はダイオードなし。
@@ -70,9 +79,13 @@ PHYがレイアウトバリアントを持つ場合も、ELCは選択に関係
設計エラーとする。接点マトリクスなら、どちらが押されたかを区別できないため。 設計エラーとする。接点マトリクスなら、どちらが押されたかを区別できないため。
排他かどうかはPHYの木から判る。 排他かどうかはPHYの木から判る。
ファームウェア向けのキー番号と接続先の対応表は、バリアント選択後のIDXの並びから生成する。
同じ接続先を共有する排他キーも、選択後に同時には残らない。ELCの保存データは選択によって書き換えない。
LEDチェーンもバリアントごとに分けない。排他なキー位置の両方にLEDが載る基板も、 LEDチェーンもバリアントごとに分けない。排他なキー位置の両方にLEDが載る基板も、
バリアント固有のキーにLEDが載らない基板もあるが、どちらもチェーンは1本で、選択によって並びが変わらない。 バリアント固有のキーにLEDが載らない基板もあるが、どちらもチェーンは1本で、選択によって並びが変わらない。
載らないキーは、PHYがキーごとに持つper-key LEDの有無でそのまま表せる。 載らないキーは、PHYがキーごとに持つper-key LEDの有無でそのまま表せる。
キー配列の出力で行うバリアントの取捨・番号の詰め直しは、LEDチェーンには適用しない。
### 他レイヤーへの参照 ### 他レイヤーへの参照
@@ -130,6 +143,10 @@ type ELC = {
} }
``` ```
## 保留課題
- [ELCの保留課題](backlog/README.md#elc)
## 検討済み・非採用 ## 検討済み・非採用
- `matrix.type` による配線方式の列挙 (journal/2026-09-20-elc-pin-provider.md) - `matrix.type` による配線方式の列挙 (journal/2026-09-20-elc-pin-provider.md)
@@ -138,22 +155,4 @@ type ELC = {
## 未確認事項 ## 未確認事項
### P-1 光学スイッチの端子構成 (なし)
- 位置づけ: 本文「キーの端子」が扱えていない残り
- 現状の認識: 発光側と受光側が独立した端子を持つのか、接点と同じ2端子として扱えるのかが分からない
- 格上げ条件: 光学スイッチを使った実在のPCBの回路図を読み、端子数と配線の形を確認する
### P-2 複数マルチプレクサ構成の作り
- 位置づけ: 本文「ピンとピン提供者」が前提にしている。`select` を提供者ごとに持たせてよいかに関わる
- 現状の認識: 選択ピンを複数のmuxで共有して共通ピンだけ分ける形と、イネーブルピンで切り替える形が考えられるが、
どちらが一般的か確認していない。前者なら同じ `PinRef` が複数の提供者の `select` に現れる
- 格上げ条件: 複数muxを使った実在のキーボードの配線を確認し、共有の形を確かめる
### P-3 駆動側と検出側の役割をELCが明示的に持つ必要があるか
- 位置づけ: 本文「配線トポロジを区分として持たない」の残り
- 現状の認識: 接点マトリクスではダイオードの向きから、directでは電源レールの参照から、
どちらを駆動しどちらを検出するかが決まりそうに見えるが、全方式で導出できるか確かめていない
- 格上げ条件: 各方式について、端子の接続先とダイオードの向きだけから走査手順が一意に決まるかを確認する
+56 -39
View File
@@ -2,20 +2,24 @@
## 概要 ## 概要
IDXレイヤーは、PHYのキーに「キー番号」を与える。キー番号は、キーマップがキーを指すための識別子である。 IDXレイヤーは、全バリアントにわたるPHYのキーの並び順を持つ。
番号を使う形式への出力時に、選択したバリアントで使うキーだけを残し、0から連続するキー番号を与える。
保存した並びの位置と、選択後の出力でのキー番号は区別する。
キー番号は他のどの情報からも導出できない。設計者が決める並び順そのものであり、 並び順は他のどの情報からも導出できない。設計者が決める情報なので、IDXが正本として持つ。
そのためIDXが正本として持つ。ELCのrow/colが「配線から決まるので出力時に導出する」のと、 ELCのrow/colが「配線から決まるので出力時に導出する」のと、理由が裏返しの関係にある。
理由が裏返しの関係にある。
IDXが持つのは、番号を振る対象のキーと、その並び順だけとする。 IDXが持つのは、番号を振る対象のキーと、その並び順だけとする。バリアントの選択は保存しない。
## キー番号は公開インターフェース ## キー番号は公開インターフェース
基板を公開すると、他人が書いたキーマップがそのキー番号に依存する。 基板を公開すると、他人が書いたキーマップが、出力したレイアウトのキー番号に依存する。
番号を振り直すと、既存のキーマップが別のキーを指す。 同じレイアウトで番号を振り直すと、既存のキーマップが別のキーを指す。
そのためキー番号は、出力のたびに生成し直す派生情報として扱わず、固定した正本として保存する。 そのため並び順は正本として保存し、出力のたびに幾何や配線から生成し直さない。
同じ並びと同じバリアント選択からは、同じ出力番号を得る。
番号の互換性は出力レイアウトごとに扱い、異なるバリアントの同じ番号が同じキーを指すとは限らない。
並びへの挿入・削除・並べ替えでは、影響する出力レイアウトの番号が変わることを考慮する。
## 幾何から導出しない ## 幾何から導出しない
@@ -51,35 +55,50 @@ row / col 自体がELCの正本ではなく出力時の派生情報である。
## レイアウトバリアント ## レイアウトバリアント
PHYがレイアウトバリアントを持つ場合も、キー番号の空間は1つとする。バリアントごとにIDXを分けない。 IDXは、番号を振る対象のキーを全バリアントにわたって1つの `order` に持つ。
キー番号は公開インターフェースであり、どの空間のものかを断らずに解釈できなければならない。 各PHYキーIDを別々の要素として並べ、同じIDは重複させない。同じ接続先に配線された排他キーも
特定のレイアウト向けの並びは、選んだバリアントに属さないキーを落として得る。 別々の要素にする。物理キーごとに、出力先が要求する並びの中の位置を与えられるようにするため。
ただしこれだけでは足りない場合がある。QMKのコミュニティレイアウトでは、同じマトリクス位置が
レイアウトによって別の物理キーになり(ANSIのバックスラッシュとISOの `#~`)、残ったキーの読み順が変わる。
ISO系で引数順が一致しない。
排他なキー同士は、同じキー番号を共有できる。`order` の要素にキーIDの配列を書くと、 出力時は、PHYのVariantGroupごとにバリアントを選び、次の手順で出力用の並びを得る。
その位置の番号を配列内のキーで共有する。
番号を共有できるのは、ELCで同じ接続先の組に配線されたキーに限る。 1. 選択したバリアントの配下と、バリアントに属さない共通のキーを対象にする。
3つの処理系が要求するのはいずれも「番号の位置 → マトリクス位置」の1対1の対応表であり、 入れ子の場合は、祖先のバリアントも選択されているキーだけを対象にする
1つの番号に対応するマトリクス位置は1つしかないため。 2. `order` から対象外のキーを除く。残ったキー同士の順序は変えない
逆に、同じ接続先の組に配線された排他キーは番号を共有しなければならない。 3. 残った並びの位置を、0から連続する出力用のキー番号とする
別々の番号を与えると、その対応表に同じマトリクス位置が2回現れ、1回のキー押下で両方の番号が反応する。
選択によって番号が詰まることはない。あるバリアントでしか実装されないキーの番号は、 保存した `order` は書き換えない。レイアウトごとに独立したIDXや並び順も保存しない。
別のバリアントを選んだときには使われない番号として残る。 同じ接続先の排他キーは選択後に同時には残らないため、別々の要素でも出力の対応表に重複しない。
番号を使う形式への出力は、バリアントを選択した構成を単位とする。
例えば、ANSIのバックスラッシュとISOの `#` が同じ接続先を使う場合も、次のように並べられる。
QとAは共通キー、バックスラッシュと `#` は排他キーとする。
| キー | 保存したorderでの位置 | ANSI出力の番号 | ISO出力の番号 |
| --- | --- | --- | --- |
| Q | 0 | 0 | 0 |
| バックスラッシュ | 1 | 1 | — |
| A | 2 | 2 | 1 |
| # | 3 | — | 2 |
全体の並びは `Q → バックスラッシュ → A → #`、ANSI出力は `Q → バックスラッシュ → A`、
ISO出力は `Q → A → #` になる。
既存コミュニティレイアウトとの互換出力は、要求順を再現できることを確認したものを対象とする。
単一の `order` で既存レイアウト全般を再現できることは、現行仕様の前提にしない。
## 他レイヤーとの関係 ## 他レイヤーとの関係
IDXはPHYのキーIDだけを参照する。ELCを参照しない。 IDXはPHYのキーIDだけを参照する。ELCを参照しない。
MAPはIDXを参照しない。MAPもキーの参照にPHYのキーIDを使う。 MAPはIDXを参照しない。MAPもキーの参照にPHYのキーIDを使う。
キーマップを配列として出力するとき、IDXの並び順で並べ替える。 キーマップを配列として出力するとき、バリアント選択後のIDXの並び順で並べ替える。
これにより、IDXにキーを挿入して番号がずれても、MAPは書き換えずに済む。 ELCから作るキー番号と接続先の対応表にも、同じ選択後の並びを使う。
これにより、IDXの編集やバリアント選択で出力番号が変わっても、MAP内のキーIDへの割り当ては書き換えずに済む。
PHYのIDで参照するELC・MAPの保存データ自体には、番号を詰める処理はない。
MAPが番号を持たないキーにキーコードを割り当てていた場合、そのキーは出力に現れない。 MAPが番号を持たないキーにキーコードを割り当てていた場合、そのキーは出力に現れない。
IDXの `order` と突き合わせて検出できる。 IDXの `order` と突き合わせて検出できる。`order` に含まれていても、選ばれなかったバリアントの
キーへの割り当ては、その出力には含めない。
## 持たないもの ## 持たないもの
@@ -92,30 +111,28 @@ IDXの `order` と突き合わせて検出できる。
```ts ```ts
type IDX = { type IDX = {
order: (string | string[])[] // PHYのキーID。並びの位置がキー番号。 order: string[] // 全バリアントの対象キーのPHY ID。各IDは1回だけ現れる。
// 配列は、その位置の番号を共有する排他バリアントのキー // 出力時に対象外のキーを除き、残った並びの位置をキー番号とする
} }
``` ```
並びの位置を番号とする形は、ELCの `LEDChain.order` と同じ形である。 番号は個別のフィールドとして持たず、出力時の並びの位置で表すため、出力番号に欠番や飛びは生じない。
欠番・重複・番号の飛びが構造として起こらない。 キーIDの重複は許さない。
`order` に挙げたキーだけが番号を持つ。PHYの部品ノードと `decal` のキーは対象にならない。 出力番号の対象は `order` に挙げたキーに限る。PHYの部品ノードと `decal` のキーは対象にならない。
## 保留課題
- [IDXの保留課題](backlog/README.md#idx)
## 検討済み・非採用 ## 検討済み・非採用
- PHYのツリー順をキー番号とする (journal/2026-09-21-idx-necessity.md) - PHYのツリー順をキー番号とする (journal/2026-09-21-idx-necessity.md)
- ELCのマトリクス順からキー番号を導出する (journal/2026-09-21-idx-necessity.md) - ELCのマトリクス順からキー番号を導出する (journal/2026-09-21-idx-necessity.md)
- `keyId` と `index` の対を並べる形 (journal/2026-09-21-idx-necessity.md) - 排他キーで番号を共有し、選択後も番号を詰めない (journal/2026-09-22-idx-variant-output.md)
- MAPがキー番号を参照する (journal/2026-09-21-idx-necessity.md) - MAPがキー番号を参照する (journal/2026-09-21-idx-necessity.md)
- レイアウトごとに別のIDXを持つ (journal/2026-09-21-multi-layout.md) - レイアウトごとに別のIDXを持つ (journal/2026-09-21-multi-layout.md)
## 未確認事項 ## 未確認事項
### P-1 一致しないコミュニティレイアウトの並びをどう与えるか (なし)
- 位置づけ: 本文「レイアウトバリアント」の残余。一致しない場合があることは確認済み
- 現状の認識: QMKの実データで、コミュニティレイアウトとの173組のうち30組(すべてISO系)が
`order` からの取捨では得られない。並べ替えの規則を与えるか、レイアウトごとの並びを持つかが未定。
後者は「レイアウトごとに別のIDXを持つ」として一度落としている
- 格上げ条件: 埋め方をユーザーが決める
+21 -38
View File
@@ -101,6 +101,8 @@ LED・OLED・ロータリーエンコーダ・ネジ穴のように、設計者
フットプリントの選択には使わない。予約名以外および省略時は既定の形で描く。 フットプリントの選択には使わない。予約名以外および省略時は既定の形で描く。
ユーザーが付ける名前は `@` で始めない限り予約と衝突しない。 ユーザーが付ける名前は `@` で始めない限り予約と衝突しない。
当面は表裏の描き分けを行わない。描画用の予約グループ名を、PCB上の実装面の指定としては使わない。
部品ノードはクラスターの子になれる。クラスターを動かせば配下の部品も追随する。 部品ノードはクラスターの子になれる。クラスターを動かせば配下の部品も追随する。
LEDの配線はPHYが持たない。LEDの識別番号もPHYは持たない。番号は配線で決まるため、ELCが持つ。 LEDの配線はPHYが持たない。LEDの識別番号もPHYは持たない。番号は配線で決まるため、ELCが持つ。
@@ -113,6 +115,7 @@ IDXはLEDの番号を持たない。RGB効果が必要とする位置と区分
### ラベル ### ラベル
キーはラベルを参考情報として持つ。KLE JSONの取り込みでは照合に使う。 キーはラベルを参考情報として持つ。KLE JSONの取り込みでは照合に使う。
当面はラベルからMAPへの自動適用は行わない。
### グルーピング ### グルーピング
@@ -170,7 +173,8 @@ VariantGroupごとにバリアントを1つ選んでから階層を畳んで生
Variantをまたぐノードの移動は、クラスター間の移動と同じreparentとして扱う。絶対配置は変わらない。 Variantをまたぐノードの移動は、クラスター間の移動と同じreparentとして扱う。絶対配置は変わらない。
組み替えの対象を同一の親に限る規則がそのまま効くため、複数のVariantにまたがる選択は組み替えできない。 組み替えの対象を同一の親に限る規則がそのまま効くため、複数のVariantにまたがる選択は組み替えできない。
選択はPHYを畳むときにだけ使う。他レイヤーはPHYのノードIDを参照するだけで、選択を見ない。 ELC・IDX・MAPの保存データは全バリアントのPHYノードIDを参照し、選択によって書き換えない。
選択は、PHYを単一の配置に畳むときや、IDX・MAPから選択した構成のキー配列を生成するときに使う。
### クラスターの組み替え ### クラスターの組み替え
@@ -197,6 +201,9 @@ Transform は回転と平行移動だけを持つ剛体変換であり、合成
ただし選択したノード全部が同じ回転角度を持つ場合はその角度を基準角度に採り、境界矩形もその角度の座標系で取る ただし選択したノード全部が同じ回転角度を持つ場合はその角度を基準角度に採り、境界矩形もその角度の座標系で取る
- 組み替えで生じた非グリッド値はそのまま保持する。クラスターを解除して親に戻ったノードも同じ - 組み替えで生じた非グリッド値はそのまま保持する。クラスターを解除して親に戻ったノードも同じ
当面、reframeは通常の移動・回転と別の明示的な操作とし、原点の指定は数値入力で行う。
原点の吸着やフォーカス状態・ハンドルに連動した操作は、試作後に検討する。
## 保存形式 ## 保存形式
各ノードは親から見た変換を持つ。キー・クラスター・部品ノード・バリアントノードを混在させた木構造として保持する。 各ノードは親から見た変換を持つ。キー・クラスター・部品ノード・バリアントノードを混在させた木構造として保持する。
@@ -341,6 +348,10 @@ const kleData = (ConfigObject | KeyLabel)[][]
### KLE JSONの取り込み ### KLE JSONの取り込み
当面は新規PHYとしての読み込みを対象とする。既存PHYへの照合・更新は、照合規則を検証するまで未対応とする。
以下は照合・更新に対応する際の制約であり、曖昧な対応を自動確定しない。
誤照合で既存IDを別のキーに割り当てると、他レイヤーの参照にも影響するため。
既存のPHYデータを畳んだ配置に対して、KLE JSONの各キーを形状(位置・サイズ・回転)とラベルで照合する。 既存のPHYデータを畳んだ配置に対して、KLE JSONの各キーを形状(位置・サイズ・回転)とラベルで照合する。
- 照合できたキーは元のノードとして扱う。KLE側で配置が変わっていれば、元の親から見た相対座標に戻して反映する - 照合できたキーは元のノードとして扱う。KLE側で配置が変わっていれば、元の親から見た相対座標に戻して反映する
@@ -355,10 +366,17 @@ KLE本家はこの位置の r, rx, ry をエラーにするが、VIAとRemapは
KLEの rx, ry, r はキーの絶対配置 (x, y, rotation) に変換して取り込む。元の rx, ry は保持しない。 KLEの rx, ry, r はキーの絶対配置 (x, y, rotation) に変換して取り込む。元の rx, ry は保持しない。
見た目の属性(c, t, p, a, f, f2, fa, l, n)は、KLE JSONへ書き戻せるように保持する。どの層で持つかは決まっていない。 見た目の属性(c, t, p, a, f, f2, fa, l, n)は、KLE JSONへ書き戻せるように保持する。
当面はキー単位の `kleCompatibility` に不透明なデータとして退避し、見た目の編集機能は持たない。
KLEの sm, sb, st(ファイル単位では switchMount, switchBrand, switchType)も同様に、不透明なまま保持して書き戻す。 KLEの sm, sb, st(ファイル単位では switchMount, switchBrand, switchType)も同様に、不透明なまま保持して書き戻す。
Beyond KLEはスイッチの品種を持たないため、対応づける先がない。 Beyond KLEはスイッチの品種を持たないため、対応づける先がない。
ファイル単位の属性(switchMount, switchBrand, switchType, backcolor, radii, background, css)は、
ファイル単位の `kleCompatibility` に退避する。正式な所属層を決めるまでは、この退避先で保持する。
## 保留課題
- [PHYの保留課題](backlog/README.md#phy)
## 検討済み・非採用 ## 検討済み・非採用
@@ -370,39 +388,4 @@ Beyond KLEはスイッチの品種を持たないため、対応づける先が
## 未確認事項 ## 未確認事項
### P-2 KLE JSON取り込み時の照合基準 (なし)
- 位置づけ: 本文「KLE JSONの取り込み」の具体化に必要な未解決の問い
- 現状の認識: 形状の許容誤差、ラベルの扱い、複数候補がある場合の決め方が未定
- 格上げ条件: 照合規則を決めたうえで、KLE本家でキーの移動・追加・削除をしたJSONを取り込み、意図どおりに対応づくことを確認する
### P-6 キーキャップの見た目情報を持つ層
- 位置づけ: 本文「見た目の属性は保持する。どの層で持つかは決まっていない」の未解決部分
- 現状の認識: c, t, p, a, f系に加え、l, n もキーキャップの見た目属性であり、PHYとは別の層が要りそう。退避だけして書き戻す sm, sb, st も同じ置き場の問題を持つ。そもそもキーキャップは設計時に決める必要が薄く、カスタムキーボードの文脈に属するため、Beyond KLEの扱う範囲かどうかも未定。ファイル単位の backcolor, radii, background, css も同じ問いに含まれる
- 格上げ条件: キーキャップの情報をBeyond KLEの範囲に含めるかをユーザーが決め、保持する層を決める
### P-8 ラベルをMAPに適用するか
- 位置づけ: 未解決の問い。本文ではラベルを参考情報として持つことだけを決めている
- 現状の認識: どのラベルがMAPに適用できるかは、おそらく機械的には判断できない。自動で適用してユーザーが不要なものを落とす方式も考えられる
- 格上げ条件: 適用するか、する場合は自動適用とユーザーによる取捨の流れをユーザーが決める
### P-12 部品の表裏を描画でどう扱うか
- 位置づけ: 本文「部品ノードは表裏を持たない」の残余
- 現状の認識: どちらの面に載るかはPCB設計ツールが決める情報なのでPHYには持たない。ただしエディタ上で裏面の部品を描き分けたくなる可能性があり、`@` 始まりの予約グループ名だけで表現できるかは未検証
- 格上げ条件: 予約グループ名による描画を実装し、表裏の描き分けが必要になるか、必要なら予約名で足りるかを確認する
### P-13 reframeと通常の移動・回転をエディタ上でどう区別するか
- 位置づけ: 本文「クラスターの組み替え」の残余。データモデル側は決まっており、操作系だけが未定
- 現状の認識: クラスターへのフォーカス状態に紐づけ(中に入っている間は配下の絶対配置が変わらない)、
そのうえで原点ハンドルと通常の移動・回転ハンドルを分ける案が有力。専用モードを設ける案もある
- 格上げ条件: 操作系をユーザーが決める
### P-14 クラスター原点の吸着先
- 位置づけ: 未解決の問い。数値入力以外に、原点をどこへ吸着させるか
- 現状の認識: キーのローカル原点(左上角)・キー中心・境界矩形の角と中心が候補。相対グリッドとは別の機構になる
- 格上げ条件: 原点移動を実装し、吸着なしで足りるか、足りない場合どの候補が要るかを確認する
+100
View File
@@ -0,0 +1,100 @@
# 保留課題
現在の実装が答えに依存せず進められる問い・検証・改善候補をまとめる。
保留中の対応範囲は各仕様書を正とし、運用は[ドキュメント運用ルール](../CLAUDE.md)に従う。
項目の識別子は移動元のレイヤー名と番号を引き継ぐ。
## PHY
### PHY-P-2 KLE JSON取り込み時の照合基準
- 関連仕様: [PHYレイヤー](../PHYレイヤー.md)「KLE JSONの取り込み」
- 未決事項: 形状の許容誤差、ラベルの扱い、複数候補がある場合の決め方。
- 保留できる理由: 新規PHYとしての読み込みは、既存ノードとの照合なしで実装できる。
- 再開条件: 既存PHYへのKLE JSONの照合・更新に着手するとき。
- 完了条件: 照合規則と曖昧な候補の扱いを決め、KLE本家でキーの移動・追加・削除をしたJSONを取り込み、
意図したIDに対応づき、他レイヤーの参照を誤って付け替えないことを確認する。
### PHY-P-6 キーキャップの見た目情報を持つ層
- 関連仕様: [PHYレイヤー](../PHYレイヤー.md)「KLE JSONの取り込み」、[JSON Schema](../phase1-json-schema.md)の `kleCompatibility`
- 未決事項: キーキャップの見た目編集をBeyond KLEの範囲に含めるか、正式にはどの層で情報を持つか。
対象はキー単位の c, t, p, a, f系, l, n と、退避する sm, sb, st、ファイル単位の
switchMount, switchBrand, switchType, backcolor, radii, background, css。
- 保留できる理由: 現行Schemaの `kleCompatibility` で、正式な所属層や編集機能を決めずに保持・書き戻しができる。
- 再開条件: 見た目の編集機能、または退避先から正式な所属層への移行を検討するとき。
- 完了条件: 扱う範囲と所属層をユーザーが決め、既存の退避情報を失わない保持・移行方法を定める。
### PHY-P-8 ラベルをMAPに適用するか
- 関連仕様: [PHYレイヤー](../PHYレイヤー.md)「ラベル」
- 未決事項: ラベルをMAPへ適用するか。機械的に適用先を判断できるかは未確認で、候補を自動適用してユーザーが不要なものを落とす案もある。
- 保留できる理由: ラベルは参考情報として保持でき、MAPへの自動適用なしで各レイヤーを扱える。
- 再開条件: MAPのキーコード割り当てを補助する機能を検討するとき。
- 完了条件: 適用の要否と、適用する場合の候補提示・ユーザーによる取捨の流れをユーザーが決める。
### PHY-P-12 部品の表裏を描画でどう扱うか
- 関連仕様: [PHYレイヤー](../PHYレイヤー.md)「部品ノード」
- 未決事項: エディタ上で表裏の描き分けが必要か。必要なら `@` 始まりの予約グループ名だけで足りるか。
- 保留できる理由: 表裏を描き分けなくても配置は編集でき、実装面はPCB設計ツール側で決められる。
- 再開条件: 部品ノードと予約グループ名による描画を試作し、表示を評価するとき。
- 完了条件: 描き分けの要否を確認し、必要なら実装面の指定と混同しない表示方法を決める。
### PHY-P-13 reframeと通常の移動・回転をエディタ上でどう区別するか
- 関連仕様: [PHYレイヤー](../PHYレイヤー.md)「クラスターの組み替え」
- 未決事項: reframeの操作系。クラスターへのフォーカス状態に紐づけて原点ハンドルと通常のハンドルを分ける案と、専用モードを設ける案がある。
- 保留できる理由: データ変換は定義済みで、通常の移動・回転とは別の明示的な操作として試作できる。
- 再開条件: クラスター編集を試作し、操作の分かりやすさを評価するとき。
- 完了条件: 配下が追随する操作と絶対配置を保つ操作を区別できる操作系をユーザーが決める。
### PHY-P-14 クラスター原点の吸着先
- 関連仕様: [PHYレイヤー](../PHYレイヤー.md)「クラスターの組み替え」
- 未決事項: 吸着が必要か。候補はキーのローカル原点(左上角)・キー中心・境界矩形の角と中心で、相対グリッドとは別の機構になる。
- 保留できる理由: 数値入力だけで原点移動は成立する。
- 再開条件: 原点移動を試作し、操作性を評価するとき。
- 完了条件: 吸着の要否と、必要な場合の候補を決める。
## ELC
### ELC-P-1 光学スイッチの端子構成
- 関連仕様: [ELCレイヤー](../ELCレイヤー.md)「キーの端子」
- 未決事項: 発光側と受光側が独立した端子を持つのか、接点と同じ2端子として扱えるのか。
- 保留できる理由: 光学スイッチを対応範囲から外したまま、接点・アナログの配線を扱える。
- 再開条件: 光学スイッチの配線に対応するとき。
- 完了条件: 実在のPCBの回路図で端子数と配線の形を確認し、必要な端子構成を定める。
### ELC-P-2 複数マルチプレクサ構成の作り
- 関連仕様: [ELCレイヤー](../ELCレイヤー.md)「ピンとピン提供者」
- 未決事項: 選択ピンを共有して共通ピンを分ける構成と、enableで切り替える構成の実配線・制御方法。
どちらが一般的かは未確認。前者は複数providerの `select` に同じ `PinRef` を書く形で表現できる。
- 保留できる理由: providerごとの参照を保存する形は維持でき、複数muxの走査出力を検証済みの構成に限定できる。
- 再開条件: 複数muxを使う構成のファームウェア出力に対応するとき。
- 完了条件: 実在のキーボードの配線でピン共有とenableの使い方を確認し、現行モデルでの表現と必要な制御を確かめる。
### ELC-P-3 方式ごとの走査設定の確認
- 関連仕様: [ELCレイヤー](../ELCレイヤー.md)「配線トポロジを区分として持たない」
- 未決事項: 各方式で出力器が決められる走査方法と、ユーザーの追加指定が必要になる範囲。
接点マトリクスではダイオードの向き、directでは電源レールの参照から役割を判断できる範囲を確認する。
- 保留できる理由: 走査方向の決定・追加指定の責任は出力器側と決まり、ELCの配線保存はそのまま進められる。
- 再開条件: 各方式のファームウェア出力に着手するとき。
- 完了条件: 対象方式の配線と整合する走査方法を確認し、必要な出力設定と対応範囲を定める。
全方式で走査手順が一意に決まることの証明は求めない。
## IDX
### IDX-P-1 単一のorderから既存コミュニティレイアウトの並びを得られる範囲
- 関連仕様: [IDXレイヤー](../IDXレイヤー.md)「レイアウトバリアント」
- 未決事項: 採用済みの方式で既存コミュニティレイアウトの要求順を再現できる範囲の実測。
同じPHYキーIDを使う共通キー同士の要求順がレイアウト間で逆転する場合は、単一の `order` からの取捨では表せないが、その実例の有無は未確認。
- 保留できる理由: 全バリアントのキーを別々の要素として並べ、選択後に番号を詰める仕様は決定済み。
既存レイアウト全般との互換性を前提にせず実装できる。
- 再開条件: 既存コミュニティレイアウトとの互換出力を検証するとき。
- 完了条件: 実データのキーを共通キーとバリアント別のPHYキーに対応づけ、全体の `order` から各レイアウトの要求順を取捨で再現できるか確認する。
再現できないものが残る場合は、対応範囲と扱いを決める。
+44
View File
@@ -0,0 +1,44 @@
# 保留課題をbacklogへ分離する
## 結論
文書を、現行仕様・保留課題・検討履歴の3種類に分ける。
後回しにできる問い・検証・改善候補はbacklog/README.mdにまとめる。
現在の仕様が依存する未確認の前提は、引き続き仕様書の末尾に残す。
## 案の比較
| 置き場 | 判断 |
| --- | --- |
| 各仕様書の未確認事項にすべて残す | 現行実装の前提と、将来考えればよい課題が混ざる |
| journalへ移す | 結論反映後に削除できる履歴と、再開まで残す課題では寿命が異なる |
| 直下にbacklog.mdを置く | 直下へ追加したくないというユーザーの意向で採らない |
| backlog/README.mdへまとめる | 採用。役割をフォルダで区別し、必要になれば分割できる |
## 運用の意図
既存ルールの狙いは、採用した仕様に過去の検討を混ぜず、未確認の前提を事実と誤認して実装しないこと。
保留課題は現在の仕様でも解決済みの履歴でもないため、第3の文書種別として扱う。
移動の基準は未確認かどうかではなく、現在の実装がその答えに依存するかどうか。
保留中の対応範囲・暫定仕様は仕様書に明記し、その範囲で進められる問いだけを移す。
backlogへの移動自体は、仮説の採用や実装の許可を意味しない。
各項目は元の番号にレイヤー名を付け、関連仕様・未決事項・保留理由・再開条件・完了条件を持つ。
再開条件で「いつ考え直すか」を、完了条件で「何を確認または決定すれば終わるか」を残す。
事実確認と設計上の意思決定をともに「格上げ」と呼ぶことはやめる。
解決または取り下げた項目は削除し、完了済み課題の一覧は作らない。
仕様書側の「保留課題」は、対応するbacklogの区分へのリンク箇条書きだけに固定した。
問いの要約や項目名の列挙を重ねると片方だけ古くなるため、詳細はbacklogで一元管理する。
末尾は保留課題・検討済み非採用・未確認事項の順とし、空の保留課題セクションは残さない。
## 適用範囲
PHY・ELC・IDXの10件を整理した。ELC-P-3は責任分担の決定後に残る方式別の確認として扱い、
IDX-P-1は別途決定済みの仕様を維持して既存レイアウトとの互換性の実測だけを残した。
実機確認や既存コミュニティレイアウトの再調査は行っていない。
他の既存文書の全体再編は、この変更には含めない。
doc-lintもルール5に対応させる。backlogを検査対象に加え、項目の必須欄、
仕様書側の固定書式と参照先、事実確認と設計判断の完了条件を検査する。
+29
View File
@@ -0,0 +1,29 @@
# ELCの走査設定を出力器に置く
## 結論
ELCは配線を保存し、走査方向の決定と追加指定はFirmware Generatorの出力設定に置く。
接点マトリクスでいう走査方向は、どちら側のピン群を駆動して、どちら側から読み取るか。
全方式で走査手順が配線から一意に決まることは、ELCの成立条件にしない。
## 案の比較
| 案 | 判断 |
| --- | --- |
| 全方式で配線から走査手順を一意に導出できることを先に検証する | 配線保存の実装まで止める条件としては強すぎる |
| ELCに走査方向の指定を持たせる | 配線と出力時の選択の責任が混ざるため採らない |
| 出力器が走査方向を選び、必要なら出力設定で指定を受ける | 採用。ELCの責任を配線に保てる |
## 判断の根拠と残件
旧ELC P-3は「端子の接続先とダイオードの向きだけから走査手順が一意に決まるか」を格上げ条件にしていた。
しかし、複数の適切な方法から出力器が選べるなら、一意性は必要ない。
先に決める必要があったのは、配線だけでは決まらない場合の指定をどこが受け持つかである。
この変更は責任分担の決定であり、各方式の実配線や走査方法を新たに検証した結果ではない。
出力器は配線・ダイオードの向きと整合する方法を選び、必要な追加指定は出力設定から受け取る。
未対応の構成は出力しない。
方式ごとの導出可能な範囲と必要な追加設定は、その方式の出力に着手するときに確認する。
残件はbacklogのELC-P-3「方式ごとの走査設定の確認」に引き継いだ。
PHYの編集やELCの配線保存は、この確認に依存せず進められる。
+38
View File
@@ -0,0 +1,38 @@
# IDXの全体の並びとバリアント選択後の出力番号
## 結論
IDXは全バリアントの対象キーを `order: string[]` に持つ。同じ配線を使う排他キーも別々に並べる。
出力時に選択した構成のキーだけを残し、残った並びの位置を0から連続する出力番号とする。
保存したorderは変更しない。公開番号の互換性は出力レイアウトごとに扱う。
ユーザーがこの方針を採用した。
## 案の比較
| 案 | 扱い |
| --- | --- |
| 1つのorderに全キーを置き、出力時に取捨して詰める | 採用 |
| 同じ配線の排他キーは番号を共有し、選択後も番号を詰めない | 今回置き換えた旧仕様 |
| レイアウトごとに独立したIDXを保存する | 採用しない |
## 決定理由と例
ユーザーは、ANSIのバックスラッシュとISOの # が同じ配線を使っても、IDXでは別々の位置を持つことを希望した。
QとAが共通キーなら、全体のorderを `Q, backslash, A, hash` と置くことで、
ANSIでは `Q, backslash, A`、ISOでは `Q, A, hash` を取捨だけで得られる。
ISO出力の番号は0, 1, 2であり、保存したorderでの位置0, 2, 3とは区別する。
旧仕様は、全バリアントを含む番号と配線の対応表に同じ接続先が重複することを理由に、番号共有を必須としていた。
今回の出力単位はバリアント選択済みの構成であり、排他キーは同時に残らないため、その理由は当てはまらない。
全バリアントを一度に含むキー番号の対応表は、今回の出力方式には含めない。
MAPはPHYキーIDへの割り当てを保持する。出力時に配線の対応表とMAPの配列へ同じ選択後の並びを適用する。
ELCの保存データは全バリアントの配線を保持し、LEDチェーンも選択によって取捨・詰め直しをしない。
## 未検証の範囲
例での解消は、既存コミュニティレイアウト全般に対応できることの検証ではない。
従来の実データ調査は、既存のLAYOUT_all等の並びから他の並びを取捨できるかを調べたものだった。
今回の、別々のPHYキーIDを使って全体のorderを構成する方式については改めて確認が必要。
共通キー同士の要求順がレイアウト間で逆転する場合には単一のorderで表せない。
その実例の有無と互換出力の対応範囲を、IDX P-1の残余として残す。
+9 -7
View File
@@ -38,14 +38,14 @@ ELCはIDXを参照せず、IDXもELCを参照しない。MAPもIDXを参照し
### 3. IDXレイヤー (キー識別) ### 3. IDXレイヤー (キー識別)
- **役割**: PHYのキーに"キー番号"を与える - **役割**: 全バリアントにわたるPHYのキーの並び順を保存し、選択した構成の出力用キー番号を決める
- **入力**: PHYのキーID - **入力**: PHYのキーID
- **対応ツール**: QMKの `LAYOUT` マクロ、ZMKの matrix transform、KMKの `coord_mapping` - **対応ツール**: QMKの `LAYOUT` マクロ、ZMKの matrix transform、KMKの `coord_mapping`
- **出力**: 番号を振るキーの並び。並びの位置がキー番号 - **出力**: 全バリアントの対象キーIDを重複なく並べた `order`。出力時に対象外のキーを除き、0から連続する番号を与える
- **備考**: キー番号はPHYの幾何からもELCの配線からも導出しない(導出すると、幾何の編集や再配線でキーマップがずれる) - **備考**: キー番号はPHYの幾何からもELCの配線からも導出しない(導出すると、幾何の編集や再配線でキーマップがずれる)
- **備考**: キー番号は公開インターフェースであり、出力のたびに生成し直さず固定する - **備考**: 並び順を正本として保存する。出力番号は公開インターフェースであり、互換性は出力レイアウトごとに扱う
- **備考**: マトリックス座標とLEDの識別番号はIDXが持たない(どちらも配線で決まるためELCが持つ) - **備考**: マトリックス座標とLEDの識別番号はIDXが持たない(どちらも配線で決まるためELCが持つ)
- **備考**: レイアウトバリアントがあってもキー番号の空間は1つ。同じマトリクス位置に配線された排他キーは同じ番号を共有する - **備考**: 全バリアントで1つのIDXを持つ。同じ接続先に配線された排他キーも別々の要素とし、選択後の出力で番号を詰める。保存した `order` は変えない
- **備考**: 既存のエコシステムでは、この並びが `LAYOUT` マクロの引数順などとして手書きされている。欠けている層ではなく、形式化されていない層 - **備考**: 既存のエコシステムでは、この並びが `LAYOUT` マクロの引数順などとして手書きされている。欠けている層ではなく、形式化されていない層
### 4. MAPレイヤー (キーマップ) ### 4. MAPレイヤー (キーマップ)
@@ -54,7 +54,7 @@ ELCはIDXを参照せず、IDXもELCを参照しない。MAPもIDXを参照し
- **入力**: PHYのキーID - **入力**: PHYのキーID
- **対応ツール**: ファームウェア、VIA、Remap - **対応ツール**: ファームウェア、VIA、Remap
- **出力**: レイヤー別キーコード - **出力**: レイヤー別キーコード
- **備考**: キーの参照にキー番号を使わない。配列としての並べ替えは、出力時にIDXの並び順で行う - **備考**: キーの参照にPHYのキーIDを使う。配列としての並べ替えは、出力時にバリアント選択後のIDXの並び順で行う
### 5. IMEレイヤー (入力変換) ### 5. IMEレイヤー (入力変換)
@@ -75,7 +75,7 @@ ELCはIDXを参照せず、IDXもELCを参照しない。MAPもIDXを参照し
#### Key Indexer #### Key Indexer
- PHY JSONを入力 - PHY JSONを入力
- キーにキー番号を割り当て - 全バリアントの対象キーを1つの並びに配置
- 出力: IDX JSON - 出力: IDX JSON
#### Wiring Editor #### Wiring Editor
@@ -95,6 +95,7 @@ ELCはIDXを参照せず、IDXもELCを参照しない。MAPもIDXを参照し
#### Firmware Generator #### Firmware Generator
- 全レイヤーの情報を統合 - 全レイヤーの情報を統合
- バリアントを選択し、IDXから対象外のキーを除いた並びで、キー番号の対応表とMAPの配列を生成
- row / col など、ターゲットが要求する派生情報をここで割り当てる - row / col など、ターゲットが要求する派生情報をここで割り当てる
- QMK/VIA/KMK等へ変換 - QMK/VIA/KMK等へ変換
- 出力: ターゲット固有の設定ファイル - 出力: ターゲット固有の設定ファイル
@@ -117,13 +118,14 @@ PHY → ELC
PHY → IDX PHY → IDX
Input: キーID + 配置 Input: キーID + 配置
Output: 番号を振るキーIDの並び Output: 全バリアントの対象キーIDの並び
PHY → MAP PHY → MAP
Input: キーID Input: キーID
Output: レイヤー別キーコード Output: レイヤー別キーコード
All → Firmware All → Firmware
Input: 各レイヤーのデータ + バリアント選択
Output: QMK keyboard.json / VIA JSON / KMK .py Output: QMK keyboard.json / VIA JSON / KMK .py
``` ```
+3 -2
View File
@@ -62,7 +62,8 @@
- **現在の課題**: KLEとファームウェア設定の手動変換。並び順は手書きで、引数の数え間違いがそのまま - **現在の課題**: KLEとファームウェア設定の手動変換。並び順は手書きで、引数の数え間違いがそのまま
キーのずれになる キーのずれになる
- **Beyond KLE対応**: **PHY** + **IDX** + **ELC** → ファームウェア自動生成。 - **Beyond KLE対応**: **PHY** + **IDX** + **ELC** → ファームウェア自動生成。
ROW/COLはELCの配線から出力時に割り当てる。並び順は**IDXレイヤー**が正本として持つ ROW/COLはELCの配線から出力時に割り当てる。並び順は**IDXレイヤー**が全バリアントにわたって正本として持ち、
出力時に選択したバリアントで使うキーだけを残して、キー番号を0から連続する番号に詰める
#### b) キーマップ設定 #### b) キーマップ設定
@@ -71,7 +72,7 @@
- 各キーへのキーコード割り当て - 各キーへのキーコード割り当て
- レイヤー設計 - レイヤー設計
- マクロ・特殊機能の設定 - マクロ・特殊機能の設定
- **Beyond KLE対応**: **MAPレイヤー**。キーの参照にはPHYのキーIDを使い、配列への並べ替えは出力時にIDXの順で行う - **Beyond KLE対応**: **MAPレイヤー**。キーの参照にはPHYのキーIDを使い、配列への並べ替えは出力時にバリアント選択後のIDXの順で行う
### 4. 製造・組み立て段階 ### 4. 製造・組み立て段階
+19 -24
View File
@@ -45,7 +45,7 @@ ELC・IDX・MAPはどれもPHYを参照する。ELCはIDXを参照せず、MAP
}, },
"kleCompatibility": { "kleCompatibility": {
"type": "object", "type": "object",
"description": "File-level opaque KLE fields kept for round-trip output (switchMount, switchBrand, switchType, backcolor, radii, background, css). The owning layer is undecided; see P-6 in PHYレイヤー.md", "description": "File-level opaque KLE fields kept for round-trip output (switchMount, switchBrand, switchType, backcolor, radii, background, css). Kept here for now; the eventual owning layer is deferred in backlog/README.md, PHY-P-6",
"additionalProperties": true "additionalProperties": true
}, },
"profiles": { "profiles": {
@@ -170,7 +170,7 @@ ELC・IDX・MAPはどれもPHYを参照する。ELCはIDXを参照せず、MAP
}, },
"kleCompatibility": { "kleCompatibility": {
"type": "object", "type": "object",
"description": "Opaque KLE fields kept for round-trip output (c, t, p, a, f, f2, fa, l, n, sm, sb, st). The owning layer is undecided; see P-6 in PHYレイヤー.md", "description": "Opaque KLE fields kept for round-trip output (c, t, p, a, f, f2, fa, l, n, sm, sb, st). Kept here for now; the eventual owning layer is deferred in backlog/README.md, PHY-P-6",
"additionalProperties": true "additionalProperties": true
} }
} }
@@ -424,8 +424,9 @@ ELC・IDX・MAPはどれもPHYを参照する。ELCはIDXを参照せず、MAP
各フィールドの意味は `IDXレイヤー.md` を正とする。 各フィールドの意味は `IDXレイヤー.md` を正とする。
番号を振るキーの並びだけを持つ。並びの位置がキー番号になるため、欠番・重複・番号の飛びは 全バリアントの対象キーIDを、重複なく1つの並びに持つ。同じ配線を使う排他キーも別々の要素とする。
構造として起こらない。 出力時は選択したバリアントで使わないキーを除き、残った並びの位置を0から連続するキー番号とする。
保存した `order` は変更しない。
### 基本構造 ### 基本構造
@@ -434,7 +435,7 @@ ELC・IDX・MAPはどれもPHYを参照する。ELCはIDXを参照せず、MAP
"$schema": "https://json-schema.org/draft/2020-12/schema", "$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://beyond-kle.org/schemas/idx-layer.json", "$id": "https://beyond-kle.org/schemas/idx-layer.json",
"title": "IDX Layer - Key Identification", "title": "IDX Layer - Key Identification",
"description": "Ordered list of PHY keys; the position in the list is the key number", "description": "Ordered PHY key IDs across all variants. Output key numbers are contiguous positions after filtering by variant selection",
"type": "object", "type": "object",
"required": ["version", "phyReference", "order"], "required": ["version", "phyReference", "order"],
"properties": { "properties": {
@@ -450,20 +451,10 @@ ELC・IDX・MAPはどれもPHYを参照する。ELCはIDXを参照せず、MAP
"order": { "order": {
"type": "array", "type": "array",
"uniqueItems": true, "uniqueItems": true,
"description": "Each PHY key has its own entry, including mutually exclusive keys wired to the same terminals. Variant selection does not modify this list",
"items": { "items": {
"oneOf": [ "type": "string",
{ "description": "Reference to PHY layer key ID"
"type": "string",
"description": "Reference to PHY layer key ID"
},
{
"type": "array",
"minItems": 2,
"uniqueItems": true,
"items": { "type": "string" },
"description": "Mutually exclusive variant keys sharing this key number. They must be wired to the same terminals in ELC"
}
]
} }
} }
} }
@@ -622,7 +613,8 @@ ELC・IDX・MAPはどれもPHYを参照する。ELCはIDXを参照せず、MAP
## 4. MAPレイヤー JSON Schema ## 4. MAPレイヤー JSON Schema
キーの参照にはPHYのキーIDを使う。キー番号は参照しない。 キーの参照にはPHYのキーIDを使う。キー番号は参照しない。
配列としての並べ替えは、ファームウェア出力時にIDXの並び順で行う。 配列としての並べ替えは、ファームウェア出力時にバリアント選択後のIDXの並び順で行う。
キー番号と接続先の対応表も同じ並びで生成し、MAPの保存データは変更しない。
### 基本構造 ### 基本構造
@@ -685,8 +677,8 @@ ELC・IDX・MAPはどれもPHYを参照する。ELCはIDXを参照せず、MAP
### PHY → IDX変換 ### PHY → IDX変換
1. PHYレイヤーからキーのリストを取得 1. PHYレイヤーから全バリアントのキーのリストを取得
2. 番号を振るキーを選び、並び順を決める 2. 番号を振る対象キーを1つの `order` に重複なく並べる。同じ配線の排他キーも別々の要素にする
### PHY → ELC変換 ### PHY → ELC変換
@@ -696,9 +688,12 @@ ELC・IDX・MAPはどれもPHYを参照する。ELCはIDXを参照せず、MAP
### IDX + ELC + MAP → ファームウェア出力 ### IDX + ELC + MAP → ファームウェア出力
1. ELCの配線から、ターゲットが要求する row / col を割り当て 1. PHYのVariantGroupごとにバリアントを選び、共通キーと、祖先を含めて選択されたバリアントのキーを特定
2. IDXの並び順に従って、MAPのキーコードを配列へ並べ替え 2. IDXの `order` から対象外のキーを除き、残った順序のまま出力用のキー番号を0から連続して割り当て
3. VIA/QMK形式への出力 3. ELCの配線から、ターゲットが要求する row / col を割り当て、手順2の並びでキー番号との対応表を生成
4. 同じ並びでMAPのキーコードを配列へ並べ替え、VIA/QMK形式へ出力
保存したPHY・IDX・ELC・MAPは書き換えない。ELCのLEDチェーンの並び・番号は、このキーの取捨では変わらない。
## 次のステップ ## 次のステップ