polydeukes 0.6.0 → 0.7.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.
- package/README.ko.md +30 -70
- package/README.md +31 -84
- package/dist/baseline.d.ts +37 -1
- package/dist/baseline.js +68 -1
- package/dist/bin.d.ts +3 -4
- package/dist/bin.js +79 -93
- package/dist/covenant/bash-line.d.ts +130 -0
- package/dist/covenant/bash-line.js +566 -0
- package/dist/covenant/declaration-engine.d.ts +108 -0
- package/dist/covenant/declaration-engine.js +415 -0
- package/dist/covenant/discipline.d.ts +103 -0
- package/dist/covenant/discipline.js +667 -0
- package/dist/covenant/dispatch.d.ts +153 -0
- package/dist/covenant/dispatch.js +237 -0
- package/dist/covenant/extract-steps.d.ts +122 -0
- package/dist/covenant/extract-steps.js +0 -0
- package/dist/covenant/mention.d.ts +117 -0
- package/dist/covenant/mention.js +209 -0
- package/dist/covenant/module.d.ts +25 -0
- package/dist/covenant/module.js +23 -0
- package/dist/covenant/mutation-rules.d.ts +40 -0
- package/dist/covenant/mutation-rules.js +146 -0
- package/dist/covenant/relations.d.ts +46 -0
- package/dist/covenant/relations.js +68 -0
- package/dist/covenant/run-covenant.d.ts +95 -0
- package/dist/covenant/run-covenant.js +164 -0
- package/dist/covenant/self-mod.d.ts +55 -0
- package/dist/covenant/self-mod.js +84 -0
- package/dist/covenant/shell-evidence.d.ts +38 -0
- package/dist/covenant/shell-evidence.js +247 -0
- package/dist/covenant/shell-mod.d.ts +84 -0
- package/dist/covenant/shell-mod.js +213 -0
- package/dist/covenant/supply.d.ts +54 -0
- package/dist/covenant/supply.js +50 -0
- package/dist/covenant/transcript-mod.d.ts +60 -0
- package/dist/covenant/transcript-mod.js +254 -0
- package/dist/covenant/ttl-witness.d.ts +47 -0
- package/dist/covenant/ttl-witness.js +80 -0
- package/dist/covenant-check.d.ts +72 -39
- package/dist/covenant-check.js +260 -121
- package/dist/diff-ir.d.ts +28 -0
- package/dist/diff-ir.js +310 -0
- package/dist/docs/README.ko.md +60 -0
- package/dist/docs/README.md +64 -0
- package/dist/docs/catalog.json +480 -0
- package/dist/docs/concepts/judgment.ko.md +113 -0
- package/dist/docs/concepts/judgment.md +113 -0
- package/dist/docs/how-to/configure-project.ko.md +99 -0
- package/dist/docs/how-to/configure-project.md +98 -0
- package/dist/docs/how-to/connect-surfaces.ko.md +120 -0
- package/dist/docs/how-to/connect-surfaces.md +121 -0
- package/dist/docs/how-to/write-disciplines.ko.md +179 -0
- package/dist/docs/how-to/write-disciplines.md +181 -0
- package/dist/docs/index.json +2280 -0
- package/dist/docs/reference/cli/covenant-check.ko.md +148 -0
- package/dist/docs/reference/cli/covenant-check.md +153 -0
- package/dist/docs/reference/cli/docs.ko.md +97 -0
- package/dist/docs/reference/cli/docs.md +95 -0
- package/dist/docs/reference/cli/explain.ko.md +84 -0
- package/dist/docs/reference/cli/explain.md +89 -0
- package/dist/docs/reference/cli/init.ko.md +119 -0
- package/dist/docs/reference/cli/init.md +119 -0
- package/dist/docs/reference/configuration/index.ko.md +553 -0
- package/dist/docs/reference/{configuration.md → configuration/index.md} +215 -90
- package/dist/docs/reference/packages/adapter-claude-code.ko.md +110 -0
- package/dist/docs/reference/{adapter-claude-code.md → packages/adapter-claude-code.md} +47 -19
- package/dist/docs/reference/packages/adapter-grok.ko.md +77 -0
- package/dist/docs/reference/packages/adapter-grok.md +79 -0
- package/dist/docs/reference/packages/core.ko.md +174 -0
- package/dist/docs/reference/{core.md → packages/core.md} +74 -12
- package/dist/docs/reference/packages/polydeukes.ko.md +195 -0
- package/dist/docs/reference/packages/polydeukes.md +204 -0
- package/dist/docs/reference/packages/sdk-ts.ko.md +161 -0
- package/dist/docs/reference/packages/sdk-ts.md +164 -0
- package/dist/docs/troubleshooting.ko.md +144 -0
- package/dist/docs/troubleshooting.md +103 -151
- package/dist/docs/tutorials/first-judgment.ko.md +82 -0
- package/dist/docs/tutorials/first-judgment.md +81 -0
- package/dist/docs-catalog.d.ts +25 -0
- package/dist/docs-catalog.js +450 -0
- package/dist/docs-library.d.ts +23 -0
- package/dist/docs-library.js +347 -0
- package/dist/docs-markdown.d.ts +32 -0
- package/dist/docs-markdown.js +150 -0
- package/dist/docs-query.d.ts +11 -40
- package/dist/docs-query.js +28 -122
- package/dist/docs-types.d.ts +105 -0
- package/dist/docs-types.js +2 -0
- package/dist/explain.d.ts +1 -1
- package/dist/explain.js +37 -22
- package/dist/load-config.d.ts +1 -1
- package/dist/load-config.js +1 -1
- package/dist/pre-state-reader.d.ts +1 -1
- package/dist/pre-state-reader.js +1 -1
- package/dist/scaffold-project.d.ts +2 -2
- package/dist/scaffold-project.js +9 -3
- package/dist/schema/polydeukes.schema.json +12 -0
- package/dist/worktree-reader.d.ts +19 -0
- package/dist/worktree-reader.js +30 -0
- package/package.json +3 -17
- package/dist/claude-code-hook.d.ts +0 -79
- package/dist/claude-code-hook.js +0 -372
- package/dist/claude-code.d.ts +0 -6
- package/dist/claude-code.js +0 -6
- package/dist/covenant-module.d.ts +0 -25
- package/dist/covenant-module.js +0 -42
- package/dist/docs/configuration.md +0 -103
- package/dist/docs/installation.md +0 -241
- package/dist/docs/reference/adapter-git.md +0 -100
- package/dist/docs/reference/covenant.md +0 -116
- package/dist/docs/reference/polydeukes.md +0 -315
- package/dist/index.d.ts +0 -22
- package/dist/index.js +0 -21
- package/dist/init-claude-code.d.ts +0 -52
- package/dist/init-claude-code.js +0 -469
- package/dist/init-grok.d.ts +0 -51
- package/dist/init-grok.js +0 -242
package/README.ko.md
CHANGED
|
@@ -1,80 +1,40 @@
|
|
|
1
|
-
# polydeukes
|
|
1
|
+
# `polydeukes`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[English](./README.md) · **한국어**
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
Polydeukes는 우산 패키지입니다. 이 패키지 하나에 `pdks` 실행 파일, 판정기,
|
|
6
|
+
두 표면의 조립 루트, 세션 표면 실행기 서브패스, 동봉 스키마가 모두 포함돼 있습니다.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
통해서만 단방향으로 흐릅니다.
|
|
8
|
+
<a id="overview"></a>
|
|
9
|
+
## 개요
|
|
11
10
|
|
|
12
|
-
|
|
11
|
+
공개 계약 진입점은 다음과 같습니다.
|
|
13
12
|
|
|
14
|
-
-
|
|
15
|
-
|
|
16
|
-
(설정 데이터는 결코 실행되지 않습니다) 코어의 `defineConfig()`에 검증을 맡깁니다. 모든 실패
|
|
17
|
-
분기는 예외를 던집니다(throw). 조용한 기본값은 금지이고, 발견된 파일은 자기 자신을 보호
|
|
18
|
-
표면에 편입시킵니다.
|
|
19
|
-
- **`pdks covenant check`.** `pdks` bin의 첫 실물 서브커맨드입니다(`polydeukes`는 별칭).
|
|
20
|
-
pre-commit 판정 러너로, 스테이징 영역의 변경을 `@polydeukes/adapter-git`이 수집해
|
|
21
|
-
약속(covenant) 입력 IR로 번역하고, 세션 훅이 부르는 바로 그 in-process 판정기로 보냅니다.
|
|
22
|
-
판정기는 하나, 표면은 여럿입니다. `--worktree`와 `--range <base>..<head>`는 같은 판정을
|
|
23
|
-
작업 트리나 ref 범위에 진단 호출로 돌리며, 증인 프롬프트는 뜨지 않습니다. 세션을 읽는 선언(`precedent`와 다른 이력 기전)도 다른 규율과
|
|
24
|
-
똑같이 조립되지만, 읽을 세션이 없으므로 자기 `supply: pass`가 처분합니다. 스테이징 변경과 매치하면 자기
|
|
25
|
-
id와 그 변경을 담은 `skipped` 이벤트를 남기고 커밋은 진행됩니다. 여기서 판정하면 걸리는
|
|
26
|
-
커밋마다 정당하게 통과할 길 없이 막히고, 아예 걸러내면 게이트가 물러섰다는 사실이 가려집니다.
|
|
27
|
-
세션 표면이 읽을 transcript를 갖지 못했을 때와 같은 처분입니다. 빈 스테이징은 명시적
|
|
28
|
-
통과이고, config가 없거나 불량이면 닫힌 실패(exit 2)입니다.
|
|
29
|
-
- **`pdks init claude-code`.** 세션 표면 설치기입니다. 아무것도 쓰기 전에 대상 프로젝트에서
|
|
30
|
-
`polydeukes`가 해소되는지부터 증명하고, 그다음 모든 배포 경로가 공유하는 것(데이터 config와
|
|
31
|
-
`.polydeukes/` 무시 줄)과 이 경로가 지는 것(위임자 훅, 그 파일이 이미 담고 있던 것에 병합되는
|
|
32
|
-
`.claude/settings.json` 등록, 그리고 AI 파트너를 `pdks docs`로 보내는 범위 한정 규율 파일)을
|
|
33
|
-
만듭니다. 기존 산출물은 덮어쓰지 않습니다. 이미 있는 것은 보고하고 그대로 두므로 재실행은
|
|
34
|
-
아무 일도 하지 않고, 사용자가 고친 내용이 살아남습니다. 생성된 config는 해소 경로를 보호
|
|
35
|
-
목록에 담고 증인(witness) 블록을 함께 갖습니다. 밸브가 없으면 첫 차단이 곧 프로젝트 정지인데,
|
|
36
|
-
방금 등록한 훅 자신이 보호 대상이기 때문입니다. config 철자 둘이 공존하는 트리, 파싱되지 않는
|
|
37
|
-
settings 파일, 해소되지 않는 패키지는 전부 선행 조건 실패입니다. 반쯤 배선된 트리를 남기는
|
|
38
|
-
대신 파일을 하나도 만들지 않습니다.
|
|
39
|
-
- **`pdks init grok`.** Grok 세션 표면 설치기입니다. 선행 검사와 공유 스캐폴드는 같고, 이 경로가
|
|
40
|
-
지는 것은 `.grok/hooks/`입니다(timeout 60인 JSON 등록, Claude 위임자가 없을 때만 위임자 파일).
|
|
41
|
-
Claude 훅 파일이 이미 있으면 JSON command가 그 파일을 가리켜 판정기를 둘 스폰하지 않습니다.
|
|
42
|
-
나중에 어느 설치기를 다시 돌려도, 설치기가 심은 grok-mjs command는 같은 방식으로 재조준되고,
|
|
43
|
-
JSON matcher는 그 command의 `.claude/settings.json` 항목을 따릅니다. Grok는 command와
|
|
44
|
-
matcher가 모두 같을 때만 등록 둘을 하나로 접기 때문입니다. 이미 열린 Grok 세션은 시작 때 훅 스냅샷을 유지합니다. 증인 밸브는 Grok에서 열리지 않습니다.
|
|
45
|
-
- **`pdks docs [topic]`.** 오프라인 문서 열람기입니다. 영어 가이드와 레퍼런스 층이 빌드 시점에
|
|
46
|
-
`dist/docs`로 복사되므로, 소비자의 AI 파트너는 검색 엔진이 색인한 아무 판본이 아니라 판정을
|
|
47
|
-
수행하는 코드와 함께 실린 문서를 읽습니다. 인자 없이 부르면 토픽 다섯을 열거하는데, 에이전트가
|
|
48
|
-
무엇을 물을 수 있는지 아는 경로가 그 목록뿐입니다. 토픽을 주면 그 절을 원문 그대로 돌려주고
|
|
49
|
-
이어서 읽을 레퍼런스를 한 줄 덧붙입니다. 질의 정의역은 그 다섯 이름뿐입니다. 모르는 토픽, 없는
|
|
50
|
-
동봉 문서, 문서가 더는 담지 않는 표제는 각각 무엇이 없었는지 stderr에 이름을 적고 exit 2로
|
|
51
|
-
끝나며 stdout은 0바이트로 둡니다. 절반만 쓰인 답은 에이전트가 그것을 문서로 읽고 그대로
|
|
52
|
-
인용하므로, 그런 상태를 만드는 경로를 두지 않습니다.
|
|
53
|
-
- **커밋 표면 증인(witness) 밸브.** `block` 수위(기본값)에서 스테이징 영역의 변경이 실제로
|
|
54
|
-
약속을 깨면 러너가 `/dev/tty`에서 증인 토큰 전문을 1회 묻습니다(부분 문자열은 거부합니다).
|
|
55
|
-
프롬프트는 깨진 등록과 걸린 항목, 그리고 이 한 번의 답이 커밋 전체를 덮는다는 사실을 적습니다.
|
|
56
|
-
깨끗한 커밋은 프롬프트를 보지 않습니다. TTY가 없으면, 즉 CI나 에이전트가 띄운 `git commit`
|
|
57
|
-
이면 프롬프트도 통과할 길도 없습니다. 밸브는 터미널 앞의 인간에게만 닿고, 어떤 상태도 남기지
|
|
58
|
-
않으며, 증언으로 통과한 판정은 모두 `witnessed`로 기록됩니다. 조용한 우회는 없습니다.
|
|
59
|
-
- **시행 수위.** git 어댑터의 네임스페이스 설정 `adapters.git.enforce: block | advise`가 커밋
|
|
60
|
-
표면 판정의 처분을 고릅니다. `advise`에서는 밸브 자체가 조립되지 않습니다. 판정은 `advised`
|
|
61
|
-
이벤트로 기록되고, stderr에 권고 한 줄이 남으며, 커밋은 진행됩니다. 차단하는 대신 측정하는
|
|
62
|
-
백스톱입니다. 완화되는 것은 판정뿐이라, 판정 자체가 불가능한 실행(설정 없음·무효, 판정 본체
|
|
63
|
-
해석 불가)은 어느 수위에서든 exit 2로 닫힙니다.
|
|
13
|
+
- `pdks` / `polydeukes` — 실행 파일
|
|
14
|
+
- `polydeukes/schema.json`
|
|
64
15
|
|
|
65
|
-
|
|
16
|
+
CLI 명령은 다음과 같습니다.
|
|
66
17
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
| `@polydeukes/adapter-git` | 커밋 표면. `staged diff`를 약속(covenant) 입력 IR로 번역 |
|
|
73
|
-
| `@polydeukes/ledger` · `@polydeukes/memory` · `@polydeukes/verify` | 청사진 단계 |
|
|
18
|
+
- `pdks covenant check`
|
|
19
|
+
- `pdks init`
|
|
20
|
+
- `pdks-grok init` (`@polydeukes/adapter-grok`가 제공)
|
|
21
|
+
- `pdks explain`
|
|
22
|
+
- `pdks docs [topic]`
|
|
74
23
|
|
|
75
|
-
|
|
76
|
-
|
|
24
|
+
<a id="public-symbols"></a>
|
|
25
|
+
## 공개 심볼
|
|
77
26
|
|
|
78
|
-
|
|
27
|
+
없습니다. 이 패키지는 TypeScript 진입점을 공개하지 않습니다. `import 'polydeukes'`는
|
|
28
|
+
`ERR_PACKAGE_PATH_NOT_EXPORTED`로 실패하고, 사용자가 닿는 것은 `pdks` 실행 파일과 동봉된
|
|
29
|
+
스키마입니다. 표면은 판정기에 표준 입력으로 입력을 넘기고 종료 코드를 읽습니다. 에이전트
|
|
30
|
+
어댑터의 훅이 하는 일이 바로 그것입니다. 이 패키지를 peer 의존으로 선언하고 import 없이
|
|
31
|
+
실행 파일을 스폰합니다.
|
|
79
32
|
|
|
80
|
-
|
|
33
|
+
<a id="see-also"></a>
|
|
34
|
+
## 같이 보기
|
|
35
|
+
|
|
36
|
+
- [`polydeukes` 패키지 레퍼런스](../../docs/reference/packages/polydeukes.ko.md)
|
|
37
|
+
- [`설정 레퍼런스`](../../docs/reference/configuration/index.ko.md)
|
|
38
|
+
- [`pdks covenant check`](../../docs/reference/cli/covenant-check.ko.md)
|
|
39
|
+
- [`pdks init`](../../docs/reference/cli/init.ko.md)
|
|
40
|
+
- [`pdks explain`](../../docs/reference/cli/explain.ko.md)
|
package/README.md
CHANGED
|
@@ -1,94 +1,41 @@
|
|
|
1
|
-
# polydeukes
|
|
1
|
+
# `polydeukes`
|
|
2
2
|
|
|
3
|
-
**English** · [한국어](
|
|
3
|
+
**English** · [한국어](./README.ko.md)
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
Polydeukes is the umbrella package. Install this package; it ships the `pdks` bin, the judge,
|
|
6
|
+
both surfaces' composition roots, the session-surface runner subpath, and the bundled schema
|
|
7
|
+
artifact.
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
one-way, through the core alone.
|
|
9
|
+
<a id="overview"></a>
|
|
10
|
+
## Overview
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
Public contract entry points:
|
|
13
13
|
|
|
14
|
-
-
|
|
15
|
-
|
|
16
|
-
a safe schema (config data is never executable) and validated by the core's `defineConfig()`.
|
|
17
|
-
Every failure branch throws — silent defaults are forbidden — and the discovered file attaches
|
|
18
|
-
itself to its own protection surface.
|
|
19
|
-
- **`pdks covenant check`** — the first real subcommand of the `pdks` bin (`polydeukes` is an
|
|
20
|
-
alias). A pre-commit judgment runner: staged changes are collected by `@polydeukes/adapter-git`,
|
|
21
|
-
translated into the covenant input IR, and dispatched through the very in-process judges the
|
|
22
|
-
session hook calls — one judge, every surface. `--worktree` and `--range <base>..<head>` run the same
|
|
23
|
-
judgment over the working tree or a ref range as a diagnostic call, with no witness prompt.
|
|
24
|
-
Declarations that read the session (`precedent` and the other history mechanisms) assemble
|
|
25
|
-
here like any other, but with no session to read their `supply: pass` takes over: when one
|
|
26
|
-
matches a staged change it records a `skipped` event carrying its id and that change, and the
|
|
27
|
-
commit proceeds. Judging them would block every matching commit with no legitimate pass path;
|
|
28
|
-
filtering them out would hide that a gate stood down. It is the same disposition the session
|
|
29
|
-
surface uses when it has no transcript. An empty staging area is an explicit pass; a missing or
|
|
30
|
-
invalid config fails closed.
|
|
31
|
-
- **`pdks explain`** — the assembly reader. It assembles both surfaces' registration sets through
|
|
32
|
-
the same functions the two runners call and prints them without judging: every registration
|
|
33
|
-
with its kind (`meta` / `judge` / `skip` / `excluded`), the routing scope of each entry, whether
|
|
34
|
-
it carries a `why`, and for each skip the compiler's reason — the one that otherwise reaches
|
|
35
|
-
stderr only when the cause is a config fault. No judge thunk is called and no telemetry row is
|
|
36
|
-
written. A config that cannot be loaded fails at exit `2` with stdout at zero bytes.
|
|
37
|
-
- **`pdks init claude-code`** — the Claude Code session-surface installer. It proves `polydeukes` resolves
|
|
38
|
-
from the target project before writing anything, then creates what every distribution path
|
|
39
|
-
shares (the data config and its `.polydeukes/` ignore line) and what this path owns (a delegator
|
|
40
|
-
hook, its `.claude/settings.json` registration merged into whatever that file already carries,
|
|
41
|
-
and a scoped discipline file pointing an AI partner at `pdks docs`). Nothing existing is
|
|
42
|
-
overwritten: an artifact already there is reported and left alone, so a re-run is a no-op and a
|
|
43
|
-
consumer's edits survive. The generated config carries the resolution paths on its protection
|
|
44
|
-
list and a witness block — without the valve the first block would freeze the project, since the
|
|
45
|
-
hook it just registered is itself protected. Two coexisting config spellings, an unparseable
|
|
46
|
-
settings file, and a package that cannot be resolved are all precondition failures: each leaves
|
|
47
|
-
zero files rather than a half-wired tree.
|
|
48
|
-
- **`pdks init grok`** — the Grok session-surface installer. Same preflight and shared scaffold;
|
|
49
|
-
this path owns `.grok/hooks/` (JSON registration with timeout 60, and a delegator file only when
|
|
50
|
-
no Claude delegator is on disk). If the Claude hook file already exists, the JSON command points
|
|
51
|
-
at it so two judges are not spawned. A later run of either installer retargets an
|
|
52
|
-
installer-generated grok-mjs command the same way, and the JSON matcher follows the
|
|
53
|
-
`.claude/settings.json` entry for that command — Grok collapses two registrations only when
|
|
54
|
-
command and matcher both match. An already-open Grok session keeps the hook snapshot from
|
|
55
|
-
start. The witness valve does not open on Grok.
|
|
56
|
-
- **`pdks docs [topic]`** — the offline documentation reader. The English guides and the reference
|
|
57
|
-
layer are copied into `dist/docs` at build time, so a consumer's AI partner reads the
|
|
58
|
-
documentation that shipped with the code doing the judging instead of whichever release a search
|
|
59
|
-
engine indexed. With no argument it lists the five topics — that listing is how an agent
|
|
60
|
-
discovers what it may ask at all; with one it returns that topic's section verbatim plus the
|
|
61
|
-
reference to read next. The query domain is those five names and nothing else: an unknown topic,
|
|
62
|
-
a bundled document that is missing, and a heading a document no longer carries each name what
|
|
63
|
-
was missing on stderr and exit 2, leaving stdout at zero bytes. A partially written answer would
|
|
64
|
-
be read as the document and quoted onward, so no path produces one.
|
|
65
|
-
- **The commit-surface witness valve** — at the `block` level (the default), when a staged change
|
|
66
|
-
actually breaks a covenant, the runner prompts once on `/dev/tty` for the full witness token (a
|
|
67
|
-
substring is refused), naming the broken registration, the matched entry, and the commit-wide
|
|
68
|
-
reach of the one answer. A clean commit never prompts. No TTY — CI, an agent-spawned
|
|
69
|
-
`git commit` — means no prompt and no way through: the valve is reachable only by a human at a
|
|
70
|
-
terminal, and nothing is ever persisted. Every witnessed pass is measured as `witnessed`, never
|
|
71
|
-
silent.
|
|
72
|
-
- **The enforcement level** — the git adapter's namespace setting
|
|
73
|
-
`adapters.git.enforce: block | advise` selects what a commit-surface verdict does. Under
|
|
74
|
-
`advise` the valve is structurally absent: a verdict is recorded as an `advised` event, one
|
|
75
|
-
advisory line lands on stderr, and the commit proceeds — a backstop that measures instead of
|
|
76
|
-
blocking. Only the verdict is relaxed: a run that cannot judge (missing or invalid config, an
|
|
77
|
-
unresolvable judge body) fails closed at exit 2 at either level.
|
|
14
|
+
- `pdks` / `polydeukes` — the bin
|
|
15
|
+
- `polydeukes/schema.json`
|
|
78
16
|
|
|
79
|
-
|
|
17
|
+
CLI commands:
|
|
80
18
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
| `@polydeukes/adapter-git` | Commit surface — staged diffs → covenant input IR |
|
|
87
|
-
| `@polydeukes/ledger` · `@polydeukes/memory` · `@polydeukes/verify` | Blueprint stage |
|
|
19
|
+
- `pdks covenant check`
|
|
20
|
+
- `pdks init`
|
|
21
|
+
- `pdks-grok init` (ships with `@polydeukes/adapter-grok`)
|
|
22
|
+
- `pdks explain`
|
|
23
|
+
- `pdks docs [topic]`
|
|
88
24
|
|
|
89
|
-
|
|
90
|
-
|
|
25
|
+
<a id="public-symbols"></a>
|
|
26
|
+
## Public symbols
|
|
91
27
|
|
|
92
|
-
|
|
28
|
+
None. This package publishes no TypeScript entry point: `import 'polydeukes'` fails with
|
|
29
|
+
`ERR_PACKAGE_PATH_NOT_EXPORTED`, and what a consumer reaches is the `pdks` bin and the
|
|
30
|
+
bundled schema. A surface hands the judge its input on stdin and reads the exit code, which
|
|
31
|
+
is what an agent adapter's hook does — it takes this package as a peer dependency and
|
|
32
|
+
spawns the bin rather than importing it.
|
|
93
33
|
|
|
94
|
-
|
|
34
|
+
<a id="see-also"></a>
|
|
35
|
+
## See also
|
|
36
|
+
|
|
37
|
+
- [`polydeukes` package reference](../../docs/reference/packages/polydeukes.md)
|
|
38
|
+
- [`Configuration reference`](../../docs/reference/configuration/index.md)
|
|
39
|
+
- [`pdks covenant check`](../../docs/reference/cli/covenant-check.md)
|
|
40
|
+
- [`pdks init`](../../docs/reference/cli/init.md)
|
|
41
|
+
- [`pdks explain`](../../docs/reference/cli/explain.md)
|
package/dist/baseline.d.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* {@link snapshotBaseline} / {@link writeBaseline} / {@link readBaseline} are the I/O
|
|
12
12
|
* points.
|
|
13
13
|
*/
|
|
14
|
-
import type
|
|
14
|
+
import { type TelemetryRecord } from '@polydeukes/core';
|
|
15
15
|
/** One hash per protected entry, keyed by the entry exactly as configured. */
|
|
16
16
|
export type BaselineSnapshot = Record<string, string>;
|
|
17
17
|
/**
|
|
@@ -80,3 +80,39 @@ export declare function writeBaseline(path: string, snapshot: BaselineSnapshot,
|
|
|
80
80
|
* Observation is fail-open: the worst outcome is a missing datum, never a blocked call.
|
|
81
81
|
*/
|
|
82
82
|
export declare function readBaseline(path: string): StoredBaseline | null;
|
|
83
|
+
/** The label every post-hoc state comparison row carries. */
|
|
84
|
+
export declare const BASELINE_LABEL = "baseline";
|
|
85
|
+
/**
|
|
86
|
+
* Compare the protected entries' on-disk state against the stored baseline and record what
|
|
87
|
+
* moved with no judgment explaining it.
|
|
88
|
+
*
|
|
89
|
+
* Runs at hook call START, before this call's own judgment rows land, so the window it reads
|
|
90
|
+
* is the one the previous comparison left open. Returns the record count as of right now —
|
|
91
|
+
* where the NEXT window opens, which {@link updateBaseline} persists at call end.
|
|
92
|
+
*
|
|
93
|
+
* The comparison records, it never blocks: no row it writes and no failure it hits changes
|
|
94
|
+
* a verdict or an exit code, which is why every caller keeps it outside the judgment path.
|
|
95
|
+
*/
|
|
96
|
+
export declare function compareBaseline(spec: {
|
|
97
|
+
repoRoot: string;
|
|
98
|
+
telemetryPath: string;
|
|
99
|
+
entries: string[];
|
|
100
|
+
}): void;
|
|
101
|
+
/**
|
|
102
|
+
* Re-establish the baseline at hook call END.
|
|
103
|
+
*
|
|
104
|
+
* At call end rather than right after the comparison: refreshing at comparison time would
|
|
105
|
+
* miss whatever this call's own judged writes changed, leaving detection permanently one
|
|
106
|
+
* call behind.
|
|
107
|
+
*
|
|
108
|
+
* The cut is stamped HERE, beside the snapshot, not at the comparison that opened the call.
|
|
109
|
+
* Both describe the same instant — everything this call did is already folded into the
|
|
110
|
+
* hashes — so the rows explaining it belong before the cut. Stamping the earlier instant
|
|
111
|
+
* instead would re-admit this call's own judgment rows into the next window, where they
|
|
112
|
+
* would attribute a change they had nothing to do with: a call that merely MENTIONED a
|
|
113
|
+
* protected entry would then absolve any tamper that followed it.
|
|
114
|
+
*/
|
|
115
|
+
export declare function updateBaseline(spec: {
|
|
116
|
+
repoRoot: string;
|
|
117
|
+
entries: string[];
|
|
118
|
+
}): void;
|
package/dist/baseline.js
CHANGED
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
* points.
|
|
13
13
|
*/
|
|
14
14
|
import { createHash } from 'node:crypto';
|
|
15
|
-
import { readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
15
|
+
import { mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
16
16
|
import { join } from 'node:path';
|
|
17
|
+
import { appendRecordFailOpen, readRecords, } from '@polydeukes/core';
|
|
17
18
|
/**
|
|
18
19
|
* The two verdict words that explain a change — the ones that mean a mutation of a
|
|
19
20
|
* protected entry was judged and let through anyway.
|
|
@@ -164,3 +165,69 @@ export function readBaseline(path) {
|
|
|
164
165
|
cutAt: typeof cutAt === 'string' ? cutAt : undefined,
|
|
165
166
|
};
|
|
166
167
|
}
|
|
168
|
+
/** The label every post-hoc state comparison row carries. */
|
|
169
|
+
export const BASELINE_LABEL = 'baseline';
|
|
170
|
+
/**
|
|
171
|
+
* Compare the protected entries' on-disk state against the stored baseline and record what
|
|
172
|
+
* moved with no judgment explaining it.
|
|
173
|
+
*
|
|
174
|
+
* Runs at hook call START, before this call's own judgment rows land, so the window it reads
|
|
175
|
+
* is the one the previous comparison left open. Returns the record count as of right now —
|
|
176
|
+
* where the NEXT window opens, which {@link updateBaseline} persists at call end.
|
|
177
|
+
*
|
|
178
|
+
* The comparison records, it never blocks: no row it writes and no failure it hits changes
|
|
179
|
+
* a verdict or an exit code, which is why every caller keeps it outside the judgment path.
|
|
180
|
+
*/
|
|
181
|
+
export function compareBaseline(spec) {
|
|
182
|
+
const baselinePath = join(spec.repoRoot, '.polydeukes', 'baseline.json');
|
|
183
|
+
// Read before any row of this comparison lands, so the rows this call is about to write
|
|
184
|
+
// cannot fall inside the window they would then explain away.
|
|
185
|
+
const { records } = readRecords(spec.telemetryPath);
|
|
186
|
+
const stored = readBaseline(baselinePath);
|
|
187
|
+
if (stored === null) {
|
|
188
|
+
// Absence and corruption are the same signal. The baseline file is NOT on the protection
|
|
189
|
+
// list — protecting it would need a comparison of its own — so its disappearance has to
|
|
190
|
+
// stay legible in the log instead.
|
|
191
|
+
appendRecordFailOpen(spec.telemetryPath, {
|
|
192
|
+
event: 'unattributed',
|
|
193
|
+
label: BASELINE_LABEL,
|
|
194
|
+
subject: baselinePath,
|
|
195
|
+
});
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const changed = findUnattributed({
|
|
199
|
+
previous: stored.entries,
|
|
200
|
+
current: snapshotBaseline({ rootDir: spec.repoRoot, entries: spec.entries }),
|
|
201
|
+
records,
|
|
202
|
+
// The cut travels with the hashes it belongs to, from the one read above. Rows older
|
|
203
|
+
// than it were already spent explaining the state that snapshot recorded.
|
|
204
|
+
cutAt: stored.cutAt,
|
|
205
|
+
});
|
|
206
|
+
// One row per changed entry — an aggregate row could not say WHICH gate definition moved.
|
|
207
|
+
for (const entry of changed) {
|
|
208
|
+
appendRecordFailOpen(spec.telemetryPath, {
|
|
209
|
+
event: 'unattributed',
|
|
210
|
+
label: BASELINE_LABEL,
|
|
211
|
+
subject: entry,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Re-establish the baseline at hook call END.
|
|
217
|
+
*
|
|
218
|
+
* At call end rather than right after the comparison: refreshing at comparison time would
|
|
219
|
+
* miss whatever this call's own judged writes changed, leaving detection permanently one
|
|
220
|
+
* call behind.
|
|
221
|
+
*
|
|
222
|
+
* The cut is stamped HERE, beside the snapshot, not at the comparison that opened the call.
|
|
223
|
+
* Both describe the same instant — everything this call did is already folded into the
|
|
224
|
+
* hashes — so the rows explaining it belong before the cut. Stamping the earlier instant
|
|
225
|
+
* instead would re-admit this call's own judgment rows into the next window, where they
|
|
226
|
+
* would attribute a change they had nothing to do with: a call that merely MENTIONED a
|
|
227
|
+
* protected entry would then absolve any tamper that followed it.
|
|
228
|
+
*/
|
|
229
|
+
export function updateBaseline(spec) {
|
|
230
|
+
const dotDir = join(spec.repoRoot, '.polydeukes');
|
|
231
|
+
mkdirSync(dotDir, { recursive: true });
|
|
232
|
+
writeBaseline(join(dotDir, 'baseline.json'), snapshotBaseline({ rootDir: spec.repoRoot, entries: spec.entries }), new Date().toISOString());
|
|
233
|
+
}
|
package/dist/bin.d.ts
CHANGED
|
@@ -6,9 +6,8 @@
|
|
|
6
6
|
* table. Anything else prints usage and exits 2 — an unknown argument must never pass
|
|
7
7
|
* silently (fail-closed, the same posture as an unjudgeable payload).
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* the witness valve is structurally unreachable, so only a human at a terminal can arm it.
|
|
9
|
+
* `covenant check` reads its observation from stdin and nothing else: the IR JSON by
|
|
10
|
+
* default, a unified diff under `--diff`. No other file descriptor is opened, so the
|
|
11
|
+
* process never asks a human anything.
|
|
13
12
|
*/
|
|
14
13
|
export {};
|
package/dist/bin.js
CHANGED
|
@@ -6,50 +6,41 @@
|
|
|
6
6
|
* table. Anything else prints usage and exits 2 — an unknown argument must never pass
|
|
7
7
|
* silently (fail-closed, the same posture as an unjudgeable payload).
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* the witness valve is structurally unreachable, so only a human at a terminal can arm it.
|
|
9
|
+
* `covenant check` reads its observation from stdin and nothing else: the IR JSON by
|
|
10
|
+
* default, a unified diff under `--diff`. No other file descriptor is opened, so the
|
|
11
|
+
* process never asks a human anything.
|
|
13
12
|
*/
|
|
14
|
-
import {
|
|
13
|
+
import { readFileSync, readSync } from 'node:fs';
|
|
15
14
|
import { dirname, join } from 'node:path';
|
|
16
15
|
import { fileURLToPath } from 'node:url';
|
|
17
16
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
17
|
+
* Read stdin to EOF. `readFileSync(0)` returns only what the first read delivers, so a
|
|
18
|
+
* diff larger than the pipe buffer would arrive truncated and translate to a partial
|
|
19
|
+
* observation; this loops until a read answers zero bytes.
|
|
21
20
|
*/
|
|
22
|
-
function
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
catch {
|
|
28
|
-
return undefined;
|
|
29
|
-
}
|
|
30
|
-
return (prompt) => {
|
|
21
|
+
function readStdin() {
|
|
22
|
+
const chunks = [];
|
|
23
|
+
const buffer = Buffer.alloc(65536);
|
|
24
|
+
for (;;) {
|
|
25
|
+
let bytes;
|
|
31
26
|
try {
|
|
32
|
-
|
|
33
|
-
const buffer = Buffer.alloc(4096);
|
|
34
|
-
const bytes = readSync(fd, buffer, 0, buffer.length, null);
|
|
35
|
-
return buffer
|
|
36
|
-
.subarray(0, Math.max(bytes, 0))
|
|
37
|
-
.toString('utf-8')
|
|
38
|
-
.replace(/\r?\n$/, '');
|
|
27
|
+
bytes = readSync(0, buffer, 0, buffer.length, null);
|
|
39
28
|
}
|
|
40
|
-
catch {
|
|
41
|
-
|
|
29
|
+
catch (error) {
|
|
30
|
+
const { code } = error;
|
|
31
|
+
// A pipe with no writer left answers EOF this way on some platforms; EAGAIN is a
|
|
32
|
+
// non-blocking fd with nothing ready yet, which is not the end of the input.
|
|
33
|
+
if (code === 'EOF')
|
|
34
|
+
break;
|
|
35
|
+
if (code === 'EAGAIN')
|
|
36
|
+
continue;
|
|
37
|
+
throw error;
|
|
42
38
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
// An EBADF thrown from this finally would override the `return null` above and
|
|
49
|
-
// escape the seam.
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
};
|
|
39
|
+
if (bytes === 0)
|
|
40
|
+
break;
|
|
41
|
+
chunks.push(Buffer.from(buffer.subarray(0, bytes)));
|
|
42
|
+
}
|
|
43
|
+
return Buffer.concat(chunks).toString('utf-8');
|
|
53
44
|
}
|
|
54
45
|
/**
|
|
55
46
|
* Write `text` to stdout and end the process — exit 0 once the write drains, exit 2 when
|
|
@@ -65,14 +56,14 @@ async function emitAndExit(text) {
|
|
|
65
56
|
process.exit(0);
|
|
66
57
|
}
|
|
67
58
|
const args = process.argv.slice(2);
|
|
68
|
-
if (args.length ===
|
|
59
|
+
if (args.length === 1 && args[0] === 'init') {
|
|
69
60
|
try {
|
|
70
|
-
// Imported inside the try, not above it: ESM imports are eager, so the
|
|
61
|
+
// Imported inside the try, not above it: ESM imports are eager, so the scaffold stays
|
|
71
62
|
// off `covenant check`'s load path, which a pre-commit hook spawns on every commit. A
|
|
72
63
|
// rejected import outside the try would reach node's unhandled-rejection exit 1, the
|
|
73
64
|
// exact crash this bin refuses to make.
|
|
74
|
-
const {
|
|
75
|
-
const { created, skipped } =
|
|
65
|
+
const { scaffoldProject } = await import('./scaffold-project.js');
|
|
66
|
+
const { created, skipped } = scaffoldProject(process.cwd());
|
|
76
67
|
for (const path of created) {
|
|
77
68
|
process.stdout.write(`created ${path}\n`);
|
|
78
69
|
}
|
|
@@ -84,36 +75,22 @@ if (args.length === 2 && args[0] === 'init' && args[1] === 'claude-code') {
|
|
|
84
75
|
catch (error) {
|
|
85
76
|
// A precondition failure leaves zero files; the message names what the user has to do
|
|
86
77
|
// before running this again.
|
|
87
|
-
process.stderr.write(`pdks init
|
|
88
|
-
process.exit(2);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
if (args.length === 2 && args[0] === 'init' && args[1] === 'grok') {
|
|
92
|
-
try {
|
|
93
|
-
const { initGrok } = await import('./init-grok.js');
|
|
94
|
-
const { created, skipped } = initGrok({ projectRoot: process.cwd() });
|
|
95
|
-
for (const path of created) {
|
|
96
|
-
process.stdout.write(`created ${path}\n`);
|
|
97
|
-
}
|
|
98
|
-
for (const path of skipped) {
|
|
99
|
-
process.stdout.write(`skipped ${path} (already present)\n`);
|
|
100
|
-
}
|
|
101
|
-
process.exit(0);
|
|
102
|
-
}
|
|
103
|
-
catch (error) {
|
|
104
|
-
process.stderr.write(`pdks init grok failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
78
|
+
process.stderr.write(`pdks init failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
105
79
|
process.exit(2);
|
|
106
80
|
}
|
|
107
81
|
}
|
|
108
|
-
if (args[0] === 'docs'
|
|
82
|
+
if (args[0] === 'docs') {
|
|
109
83
|
try {
|
|
110
84
|
// Imported inside the try for the same reason `init` is: the query core and the
|
|
111
85
|
// markdown behind it have no business on `covenant check`'s load path.
|
|
112
|
-
const {
|
|
86
|
+
const { runDocs } = await import('./docs-library.js');
|
|
113
87
|
// The bundle ships beside this file, so the docs root comes from the module's own
|
|
114
88
|
// location — never from the working directory, which is whatever shell invoked us.
|
|
115
89
|
const docsRoot = join(dirname(fileURLToPath(import.meta.url)), 'docs');
|
|
116
|
-
const
|
|
90
|
+
const manifest = JSON.parse(readFileSync(join(docsRoot, '../../package.json'), 'utf8'));
|
|
91
|
+
if (typeof manifest.version !== 'string')
|
|
92
|
+
throw new Error('missing package version');
|
|
93
|
+
const { text } = runDocs({ docsRoot, args: args.slice(1), version: manifest.version });
|
|
117
94
|
await emitAndExit(text);
|
|
118
95
|
}
|
|
119
96
|
catch (error) {
|
|
@@ -139,48 +116,57 @@ if (args.length === 1 && args[0] === 'explain') {
|
|
|
139
116
|
}
|
|
140
117
|
}
|
|
141
118
|
/**
|
|
142
|
-
* Read the `covenant check` flags
|
|
143
|
-
*
|
|
144
|
-
*
|
|
119
|
+
* Read the `covenant check` flags, or null for any argv outside the finite table: `--diff`
|
|
120
|
+
* at most once, `--enforce` at most once with `advise` or `block`, in either order, and
|
|
121
|
+
* nothing else.
|
|
145
122
|
*/
|
|
146
|
-
function
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
if (base === '' || head === '')
|
|
123
|
+
function parseCheckFlags(flags) {
|
|
124
|
+
let diffMode = false;
|
|
125
|
+
let enforce;
|
|
126
|
+
for (let i = 0; i < flags.length; i += 1) {
|
|
127
|
+
const flag = flags[i];
|
|
128
|
+
if (flag === '--diff' && !diffMode) {
|
|
129
|
+
diffMode = true;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (flag === '--enforce' && enforce === undefined) {
|
|
133
|
+
const level = flags[i + 1];
|
|
134
|
+
if (level !== 'advise' && level !== 'block')
|
|
135
|
+
return null;
|
|
136
|
+
enforce = level;
|
|
137
|
+
i += 1;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
164
140
|
return null;
|
|
165
|
-
|
|
141
|
+
}
|
|
142
|
+
return { diffMode, enforce };
|
|
166
143
|
}
|
|
167
|
-
const
|
|
168
|
-
if (
|
|
169
|
-
process.stderr.write('usage: pdks covenant check [--
|
|
144
|
+
const check = args[0] === 'covenant' && args[1] === 'check' ? parseCheckFlags(args.slice(2)) : null;
|
|
145
|
+
if (check === null) {
|
|
146
|
+
process.stderr.write('usage: pdks covenant check [--diff] [--enforce advise|block] | pdks explain | pdks init | pdks docs [topic | search <query> | show <document-id>]\n');
|
|
170
147
|
process.exit(2);
|
|
171
148
|
}
|
|
149
|
+
const { diffMode, enforce } = check;
|
|
172
150
|
try {
|
|
173
151
|
// Loaded here rather than at the top of the file. This runner statically pulls in the
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
//
|
|
152
|
+
// core and the judge, so a top-level import made every subcommand wait on both
|
|
153
|
+
// resolving — and `docs` is the one that has to answer in a tree where they do not,
|
|
154
|
+
// since a package installed but never built is exactly the state `pdks docs install`
|
|
155
|
+
// is asked about. The catch below already answers for whatever this import cannot do,
|
|
156
|
+
// at the same exit 2 it answers everything else with.
|
|
179
157
|
const { runCovenantCheck } = await import('./covenant-check.js');
|
|
158
|
+
const { covenantInputFromUnifiedDiff } = await import('./diff-ir.js');
|
|
159
|
+
const text = readStdin();
|
|
180
160
|
const { exitCode } = await runCovenantCheck({
|
|
181
161
|
repoRoot: process.cwd(),
|
|
182
|
-
|
|
183
|
-
|
|
162
|
+
// A thunk, not a value: the runner settles the telemetry path before calling it, so a
|
|
163
|
+
// translation or parse failure lands as the same one blocked row every other
|
|
164
|
+
// fail-closed branch leaves.
|
|
165
|
+
input: () => (diffMode ? covenantInputFromUnifiedDiff({ text }) : JSON.parse(text)),
|
|
166
|
+
// The input mode is the surface: a diff is a finished change set, anything else is one
|
|
167
|
+
// call the caller observed.
|
|
168
|
+
surface: diffMode ? 'changeSet' : 'session',
|
|
169
|
+
...(enforce !== undefined && { enforce }),
|
|
184
170
|
});
|
|
185
171
|
process.exit(exitCode);
|
|
186
172
|
}
|