polydeukes 0.6.1 → 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.
Files changed (106) hide show
  1. package/README.ko.md +14 -28
  2. package/README.md +14 -28
  3. package/dist/baseline.d.ts +37 -1
  4. package/dist/baseline.js +68 -1
  5. package/dist/bin.d.ts +3 -4
  6. package/dist/bin.js +73 -90
  7. package/dist/covenant/bash-line.d.ts +130 -0
  8. package/dist/covenant/bash-line.js +566 -0
  9. package/dist/covenant/declaration-engine.d.ts +108 -0
  10. package/dist/covenant/declaration-engine.js +415 -0
  11. package/dist/covenant/discipline.d.ts +103 -0
  12. package/dist/covenant/discipline.js +667 -0
  13. package/dist/covenant/dispatch.d.ts +153 -0
  14. package/dist/covenant/dispatch.js +237 -0
  15. package/dist/covenant/extract-steps.d.ts +122 -0
  16. package/dist/covenant/extract-steps.js +0 -0
  17. package/dist/covenant/mention.d.ts +117 -0
  18. package/dist/covenant/mention.js +209 -0
  19. package/dist/covenant/module.d.ts +25 -0
  20. package/dist/covenant/module.js +23 -0
  21. package/dist/covenant/mutation-rules.d.ts +40 -0
  22. package/dist/covenant/mutation-rules.js +146 -0
  23. package/dist/covenant/relations.d.ts +46 -0
  24. package/dist/covenant/relations.js +68 -0
  25. package/dist/covenant/run-covenant.d.ts +95 -0
  26. package/dist/covenant/run-covenant.js +164 -0
  27. package/dist/covenant/self-mod.d.ts +55 -0
  28. package/dist/covenant/self-mod.js +84 -0
  29. package/dist/covenant/shell-evidence.d.ts +38 -0
  30. package/dist/covenant/shell-evidence.js +247 -0
  31. package/dist/covenant/shell-mod.d.ts +84 -0
  32. package/dist/covenant/shell-mod.js +213 -0
  33. package/dist/covenant/supply.d.ts +54 -0
  34. package/dist/covenant/supply.js +50 -0
  35. package/dist/covenant/transcript-mod.d.ts +60 -0
  36. package/dist/covenant/transcript-mod.js +254 -0
  37. package/dist/covenant/ttl-witness.d.ts +47 -0
  38. package/dist/covenant/ttl-witness.js +80 -0
  39. package/dist/covenant-check.d.ts +72 -39
  40. package/dist/covenant-check.js +260 -121
  41. package/dist/diff-ir.d.ts +28 -0
  42. package/dist/diff-ir.js +310 -0
  43. package/dist/docs/README.ko.md +8 -8
  44. package/dist/docs/README.md +8 -8
  45. package/dist/docs/catalog.json +44 -28
  46. package/dist/docs/concepts/judgment.ko.md +2 -2
  47. package/dist/docs/concepts/judgment.md +2 -2
  48. package/dist/docs/how-to/configure-project.ko.md +16 -16
  49. package/dist/docs/how-to/configure-project.md +15 -12
  50. package/dist/docs/how-to/connect-surfaces.ko.md +39 -34
  51. package/dist/docs/how-to/connect-surfaces.md +36 -33
  52. package/dist/docs/how-to/write-disciplines.ko.md +61 -6
  53. package/dist/docs/how-to/write-disciplines.md +62 -6
  54. package/dist/docs/index.json +607 -373
  55. package/dist/docs/reference/cli/covenant-check.ko.md +112 -65
  56. package/dist/docs/reference/cli/covenant-check.md +114 -59
  57. package/dist/docs/reference/cli/docs.ko.md +2 -2
  58. package/dist/docs/reference/cli/docs.md +2 -2
  59. package/dist/docs/reference/cli/explain.ko.md +14 -9
  60. package/dist/docs/reference/cli/explain.md +15 -10
  61. package/dist/docs/reference/cli/init.ko.md +52 -52
  62. package/dist/docs/reference/cli/init.md +43 -55
  63. package/dist/docs/reference/configuration/index.ko.md +171 -66
  64. package/dist/docs/reference/configuration/index.md +178 -71
  65. package/dist/docs/reference/packages/adapter-claude-code.ko.md +38 -11
  66. package/dist/docs/reference/packages/adapter-claude-code.md +38 -14
  67. package/dist/docs/reference/packages/adapter-grok.ko.md +77 -0
  68. package/dist/docs/reference/packages/adapter-grok.md +79 -0
  69. package/dist/docs/reference/packages/core.ko.md +49 -3
  70. package/dist/docs/reference/packages/core.md +54 -5
  71. package/dist/docs/reference/packages/polydeukes.ko.md +138 -77
  72. package/dist/docs/reference/packages/polydeukes.md +146 -81
  73. package/dist/docs/reference/packages/sdk-ts.ko.md +161 -0
  74. package/dist/docs/reference/packages/sdk-ts.md +164 -0
  75. package/dist/docs/troubleshooting.ko.md +17 -15
  76. package/dist/docs/troubleshooting.md +18 -14
  77. package/dist/docs/tutorials/first-judgment.ko.md +2 -2
  78. package/dist/docs/tutorials/first-judgment.md +2 -2
  79. package/dist/explain.d.ts +1 -1
  80. package/dist/explain.js +37 -22
  81. package/dist/load-config.d.ts +1 -1
  82. package/dist/load-config.js +1 -1
  83. package/dist/pre-state-reader.d.ts +1 -1
  84. package/dist/pre-state-reader.js +1 -1
  85. package/dist/scaffold-project.d.ts +2 -2
  86. package/dist/scaffold-project.js +9 -3
  87. package/dist/schema/polydeukes.schema.json +12 -0
  88. package/dist/worktree-reader.d.ts +19 -0
  89. package/dist/worktree-reader.js +30 -0
  90. package/package.json +3 -17
  91. package/dist/claude-code-hook.d.ts +0 -79
  92. package/dist/claude-code-hook.js +0 -372
  93. package/dist/claude-code.d.ts +0 -6
  94. package/dist/claude-code.js +0 -6
  95. package/dist/covenant-module.d.ts +0 -25
  96. package/dist/covenant-module.js +0 -42
  97. package/dist/docs/reference/packages/adapter-git.ko.md +0 -101
  98. package/dist/docs/reference/packages/adapter-git.md +0 -108
  99. package/dist/docs/reference/packages/covenant.ko.md +0 -115
  100. package/dist/docs/reference/packages/covenant.md +0 -123
  101. package/dist/index.d.ts +0 -22
  102. package/dist/index.js +0 -21
  103. package/dist/init-claude-code.d.ts +0 -52
  104. package/dist/init-claude-code.js +0 -586
  105. package/dist/init-grok.d.ts +0 -51
  106. package/dist/init-grok.js +0 -242
@@ -11,8 +11,8 @@
11
11
 
12
12
  프로젝트 루트 바로 아래에 `polydeukes.config.yaml`, `polydeukes.config.yml`,
13
13
  `polydeukes.config.json` 중 하나도 없으면 설정이 필요한 명령은 종료 코드 2를 반환합니다.
14
- Git에서 원래 파일을 복원하세요. 새 프로젝트라면 `pdks init claude-code` 또는
15
- `pdks init grok`로 만들 수 있습니다. 이후 `pdks explain`을 실행합니다. 설정이 없다고
14
+ Git에서 원래 파일을 복원하세요. 새 프로젝트라면 `pdks init`, `pdks-claude-code init`,
15
+ `pdks-grok init`로 만들 수 있습니다. 이후 `pdks explain`을 실행합니다. 설정이 없다고
16
16
  기본 정책으로 대신 실행하지는 않습니다.
17
17
 
18
18
  <a id="multiple-config"></a>
@@ -31,15 +31,15 @@ Git에서 원래 파일을 복원하세요. 새 프로젝트라면 `pdks init cl
31
31
  설정은 실행할 코드가 아니라 데이터이기 때문입니다.
32
32
 
33
33
  `protectedPath:`나 `adaptors:` 같은 오타는 거부합니다. 다만 어댑터 이름에는 확장 가능성을
34
- 남겨 두었습니다. `adapters.gti:`도 설정으로 읽히지만 Git 어댑터는 사용하지 않습니다.
35
- 올바른 키는 `adapters.git`입니다. 수정한 뒤 `pdks explain`에서 실제 등록 내용을 확인하세요.
34
+ 남겨 두었습니다. 아무도 구현하지 않은 네임스페이스도 설정으로 읽히지만 읽는 쪽이 없습니다.
35
+ 수정한 뒤 `pdks explain`에서 실제 등록 내용을 확인하세요.
36
36
 
37
37
  <a id="grok-witness"></a>
38
38
  ## Grok 증인
39
39
 
40
40
  훅을 아직 읽지 않은 문제와 증인(witness) 밸브를 사용할 수 없는 문제는 구별해야 합니다.
41
41
 
42
- - `pdks init grok` 실행 뒤 Hooks 탭을 다시 불러오거나 새 세션을 시작하세요. 실제 도구를
42
+ - `pdks-grok init` 실행 뒤 Hooks 탭을 다시 불러오거나 새 세션을 시작하세요. 실제 도구를
43
43
  호출하고 텔레메트리를 확인합니다. 설치기가 성공했다고 해서 열린 세션에 훅이 적용됐다는
44
44
  뜻은 아닙니다.
45
45
  - Grok의 ACP 대화 기록에는 현재 세션 증인 밸브가 요구하는 Claude 형식의 인간 메시지가
@@ -105,19 +105,21 @@ covenant check failed closed: the covenant judges could not be loaded from …
105
105
  <a id="blocked-commit"></a>
106
106
  ## 커밋이 차단됐을 때
107
107
 
108
- 본인의 터미널에서 커밋을 실행하고 TTY 프롬프트에 설정된 토큰 전체를 입력하세요.
109
- 비대화형 스테이징 검사는 응답을 받을 없습니다. 검사가 작업을 거부하면 종료 코드 2를
110
- 반환합니다. Git은 커밋 실패를 다른 0이 아닌 코드로 알릴 수도 있습니다.
108
+ 변경 집합 표면은 사람에게 묻지 않습니다. 기본값에서는 모든 판정에 종료 코드 0을 내고 위반을
109
+ `advised`로 기록합니다. 종료 코드 2는 검사를 `--enforce block`으로 실행했을 때(보호 경로나
110
+ `enforce: block` 항목의 위반) 또는 판정 자체가 불가능할 때만 냅니다. 커밋을 멈출지는 훅
111
+ 배선이 정합니다. 종료 코드를 따르는 훅은 커밋을 멈추고, 무시하는 훅은 멈추지 않습니다. Git은
112
+ 커밋 실패를 다른 0이 아닌 코드로 알릴 수도 있습니다.
111
113
 
112
- 일반 규율 항목은 자체 수준과 어댑터 수준이 모두 차단을 허용해야 작업을 멈춥니다.
113
- `adapters.git.enforce: block`만 설정해도 기본값 `advise`인 항목이 자동 승격되지는 않습니다.
114
- 수준 변경은 정책을 바꾸는 결정이지 필수 복구 절차가 아닙니다. 커밋 프롬프트와 세션 메시지는
115
- 서로 별개의 증인 입력 경로입니다.
114
+ 일반 규율 항목은 자신이 `enforce: block`을 적고 검사가 `--enforce block`으로 실행될 때만
115
+ 작업을 멈춥니다. 설정에는 기본값 `advise`인 항목을 승격하는 것이 없습니다. 수준 변경은
116
+ 정책을 바꾸는 결정이지 필수 복구 절차가 아닙니다. 판정된 위반을 통과시키려면 설정이 아니라
117
+ 명령에서 `--enforce block`을 빼세요. 행은 그대로 남습니다.
116
118
 
117
- <a id="skipped-rows-on-the-commit-surface"></a>
118
- ## 커밋 표면의 미판정 기록
119
+ <a id="skipped-rows-on-the-change-set-surface"></a>
120
+ ## 변경 집합 표면의 미판정 기록
119
121
 
120
- 대화 기록을 읽는 선언에 `supply: { session: 'pass' }`가 있으면 세션이 없는 커밋 표면에서는
122
+ 대화 기록을 읽는 선언에 `supply: { session: 'pass' }`가 있으면 세션이 없는 변경 집합 표면에서는
121
123
  `supply-pass`를 기록합니다. 이 약속은 세션 표면에서 확인하세요. 미판정은 과거 작업을
122
124
  검증했다는 뜻이 아닙니다. 다른 채널을 공급할 수 없으면 `no-observation`이 나올 수도
123
125
  있습니다. 모든 자료 부재를 같은 실패로 취급하지 말고 등록 내용과 사유를 확인하세요.
@@ -11,8 +11,9 @@ or the judging packages, provided its own installed documentation bundle is inta
11
11
 
12
12
  Commands that need configuration exit 2 when none of `polydeukes.config.yaml`,
13
13
  `polydeukes.config.yml`, or `polydeukes.config.json` exists directly at the project root.
14
- Restore the intended file from Git, or use `pdks init claude-code` / `pdks init grok` for a new
15
- project. Then run `pdks explain`. No configuration means no silent default policy.
14
+ Restore the intended file from Git, or use `pdks init` / `pdks-claude-code init` /
15
+ `pdks-grok init` for a new project. Then run `pdks explain`. No configuration means no silent
16
+ default policy.
16
17
 
17
18
  <a id="multiple-config"></a>
18
19
  ## More than one config file
@@ -28,15 +29,15 @@ field. Repair invalid YAML, custom tags, unknown fields, or an empty `languages`
28
29
  Custom YAML tags are rejected even if the parser cannot execute them: configuration is data.
29
30
 
30
31
  Typos such as `protectedPath:` or `adaptors:` are refused. Adapter namespace names are deliberately
31
- open, however: `adapters.gti:` can load but is not read by the Git adapter. Its actual key is
32
- `adapters.git`. After repair, run `pdks explain` and check the assembled registrations.
32
+ open, however: a namespace nobody implements loads without being read by anything. After repair,
33
+ run `pdks explain` and check the assembled registrations.
33
34
 
34
35
  <a id="grok-witness"></a>
35
36
  ## Grok witness
36
37
 
37
38
  A hook not yet loaded and an unavailable witness valve are different problems:
38
39
 
39
- - After `pdks init grok`, reload the Hooks tab or start a new session. Verify an actual call and
40
+ - After `pdks-grok init`, reload the Hooks tab or start a new session. Verify an actual call and
40
41
  its telemetry; a successful installer run does not prove the open host loaded the hook.
41
42
  - Grok's ACP history does not supply the Claude-format human message required by the current
42
43
  session witness valve. Reloading does not add that capability. Perform a necessary repair
@@ -101,19 +102,22 @@ A witness cannot repair missing modules or other failures that prevent judgment
101
102
  <a id="blocked-commit"></a>
102
103
  ## A blocked commit
103
104
 
104
- Run the commit from your own terminal and answer its TTY prompt with the complete configured
105
- token. A non-interactive staged check cannot obtain that answer. The check exits 2 when it refuses;
106
- Git may report a different nonzero exit code for the failed commit.
105
+ The change-set surface does not prompt. By default it exits 0 on every verdict and records the
106
+ break as `advised`; it exits 2 only when the check runs with `--enforce block` (a protected
107
+ path or an entry set to `enforce: block` broke) or when it could not judge at all. Whether the
108
+ commit stops is your hook wiring — a hook that honours the exit code stops it, one that ignores
109
+ it does not. Git may report a different nonzero exit code for the failed commit.
107
110
 
108
- A normal entry blocks only when its own level and the adapter's level both permit blocking.
109
- Setting only `adapters.git.enforce: block` does not promote default-`advise` entries. Changing a
110
- level is a policy decision, not a required repair. The prompt is separate from a session message.
111
+ A normal entry blocks only when it declares `enforce: block` and the check runs with
112
+ `--enforce block`; nothing in the config promotes a default-`advise` entry. Changing a level is
113
+ a policy decision, not a required repair. To let a judged break through, drop `--enforce block`
114
+ from the hook command rather than editing the config — the row is still written.
111
115
 
112
- <a id="skipped-rows-on-the-commit-surface"></a>
113
- ## `skipped` rows on the commit surface
116
+ <a id="skipped-rows-on-the-change-set-surface"></a>
117
+ ## `skipped` rows on the change-set surface
114
118
 
115
119
  A transcript-reading declaration with `supply: { session: 'pass' }` records `supply-pass` when the
116
- commit surface has no session. Use the session surface for that promise; a skip does not verify
120
+ change-set surface has no session. Use the session surface for that promise; a skip does not verify
117
121
  history. Other unavailable channels can produce `no-observation`. Inspect the registration and
118
122
  reason instead of treating every missing source as the same failure.
119
123
 
@@ -17,8 +17,8 @@ mkdir pdks-example
17
17
  cd pdks-example
18
18
  git init
19
19
  printf '{"name":"pdks-example","private":true}\n' > package.json
20
- pnpm add -D polydeukes # 프로젝트 의존성. 일회성 npx 실행이 아님
21
- pnpm exec pdks init claude-code
20
+ pnpm add -D polydeukes @polydeukes/adapter-claude-code # 프로젝트 의존성. 일회성 npx 실행이 아님
21
+ pnpm exec pdks-claude-code init
22
22
  ```
23
23
 
24
24
  설치기는 각 파일을 만들었으면 `created`, 이미 있어서 보존했으면 `skipped`로 보고합니다.
@@ -17,8 +17,8 @@ mkdir pdks-example
17
17
  cd pdks-example
18
18
  git init
19
19
  printf '{"name":"pdks-example","private":true}\n' > package.json
20
- pnpm add -D polydeukes # a project dependency, not a one-off npx run
21
- pnpm exec pdks init claude-code
20
+ pnpm add -D polydeukes @polydeukes/adapter-claude-code # project dependencies, not a one-off npx run
21
+ pnpm exec pdks-claude-code init
22
22
  ```
23
23
 
24
24
  The installer reports `created` or `skipped` for each artifact. It creates a starter config,
package/dist/explain.d.ts CHANGED
@@ -17,7 +17,7 @@ export type ExplainSpec = {
17
17
  * Read the config at `repoRoot`, assemble both surfaces, and render them.
18
18
  *
19
19
  * The session assembly is given a transcript path, so its `transcript-mod` registration
20
- * exists here exactly as it does under a normal hook payload — the path is never read,
20
+ * exists here exactly as it does under a normal call payload — the path is never read,
21
21
  * because the injected transcript is the no-op one.
22
22
  */
23
23
  export declare function explain(spec: ExplainSpec): Promise<{
package/dist/explain.js CHANGED
@@ -9,15 +9,18 @@
9
9
  * nothing and reads no disk. Every failure throws: an answer that cannot be given is never
10
10
  * given halfway.
11
11
  */
12
- import { join } from 'node:path';
13
- import { resolveGitAdapterSettings } from '@polydeukes/adapter-git';
14
12
  import { AXIS_NAMES, deriveShape, noopTranscript, RELATION_NAMES } from '@polydeukes/core';
15
- import { assembleSessionRegistrations } from './claude-code-hook.js';
16
- import { assembleCommitRegistrations } from './covenant-check.js';
17
- import { loadCovenantModule, resolveCovenantDist } from './covenant-module.js';
13
+ import { covenantModule } from './covenant/module.js';
14
+ import { assembleChangeSetRegistrations, assembleCheckRegistrations } from './covenant-check.js';
18
15
  import { loadConfig } from './load-config.js';
19
16
  /** The three meta-covenant labels: registrations that protect the judging chain itself. */
20
17
  const META_LABELS = new Set(['self-mod', 'shell-mod', 'transcript-mod']);
18
+ /**
19
+ * What the session assembly here is given as its evidence path. The real one is loaded per
20
+ * call by an adapter, so this renderer names the IR key instead of a file: the assembly
21
+ * needs a present value to build the conditional registration, and nothing prints it.
22
+ */
23
+ const SESSION_EVIDENCE_PATH_KEY = 'session.evidencePath';
21
24
  /**
22
25
  * The description of a declaration entry: its catalogue coordinate (the mechanism, the axes
23
26
  * its sources derive, and the relations its entries decide), then what it routes on, how
@@ -58,7 +61,7 @@ function row(kind, label, width, description) {
58
61
  /** The description of a meta-covenant registration — how much surface it covers. */
59
62
  function metaDescription(registration, surface) {
60
63
  if (registration.label === 'transcript-mod') {
61
- return 'content predicate · conditional: transcript_path';
64
+ return 'content predicate · conditional: session.evidencePath';
62
65
  }
63
66
  return `paths ${registration.protectedPaths.length} (${surface})`;
64
67
  }
@@ -105,47 +108,59 @@ function renderSurface(spec) {
105
108
  * Read the config at `repoRoot`, assemble both surfaces, and render them.
106
109
  *
107
110
  * The session assembly is given a transcript path, so its `transcript-mod` registration
108
- * exists here exactly as it does under a normal hook payload — the path is never read,
111
+ * exists here exactly as it does under a normal call payload — the path is never read,
109
112
  * because the injected transcript is the no-op one.
110
113
  */
111
114
  export async function explain(spec) {
112
115
  const { config, configPath } = loadConfig({ rootDir: spec.repoRoot });
113
- // Resolved and imported exactly as the two runners do, so what this renders is the table
114
- // that would judge: a dist those runners would refuse cannot be rendered as if it worked.
115
- // The load names the missing module and the recovery command.
116
- const covenant = await loadCovenantModule(resolveCovenantDist());
117
- const disciplines = config.disciplines ?? [];
116
+ // The judge module the two runners assemble against, so what this renders is the table
117
+ // that would judge.
118
+ const covenant = covenantModule;
119
+ const shared = config.disciplines ?? [];
120
+ const sessionOnly = config.sessionDisciplines ?? [];
121
+ const changeSetOnly = config.changeSetDisciplines ?? [];
118
122
  const drafts = config.drafts ?? [];
119
- const session = assembleSessionRegistrations({
123
+ const session = assembleCheckRegistrations({
120
124
  config,
121
125
  rootDir: spec.repoRoot,
122
126
  covenant,
123
- transcriptPath: join(spec.repoRoot, 'transcript.jsonl'),
127
+ surface: 'session',
128
+ // The session key alone, and no roster: a roster is what an adapter loads onto each
129
+ // call, so a reader of the config would be shown one agent's names chosen by this
130
+ // renderer. The evidence path is the same kind of per-call value and is named by the
131
+ // key rather than by a filename: what a config reader is being shown is that the
132
+ // session surface carries a transcript-mod registration, and the renderer prints the
133
+ // key, never the path. The empty lists are the session a run supplies.
134
+ session: { evidencePath: SESSION_EVIDENCE_PATH_KEY, userMessages: [], toolCalls: [] },
124
135
  transcript: noopTranscript,
125
136
  });
126
- const commit = assembleCommitRegistrations({
137
+ const changeSet = assembleChangeSetRegistrations({
127
138
  config,
128
139
  rootDir: spec.repoRoot,
129
140
  covenant,
141
+ surface: 'changeSet',
130
142
  });
131
- const gitSettings = resolveGitAdapterSettings({ namespace: config.adapters?.git });
132
143
  const text = [
133
144
  `pdks explain — ${configPath}`,
134
145
  '',
135
146
  renderSurface({
136
- header: 'surface: session (claude-code hook) · disciplines: advise unless enforce: block · meta: block',
147
+ header: 'input: call IR (one call, stdin) · ' +
148
+ `disciplines ${shared.length} · sessionDisciplines ${sessionOnly.length} · ` +
149
+ 'disciplines: advise unless enforce: block · meta: block',
137
150
  registrations: session,
138
151
  drafts,
139
- disciplines,
152
+ disciplines: [...shared, ...sessionOnly],
140
153
  selfModScope: 'common; includes the config file itself',
141
154
  }),
142
155
  '',
143
156
  renderSurface({
144
- header: `surface: commit (git pre-commit) · enforce: ${gitSettings.enforce} · disciplines: advise unless enforce: block`,
145
- registrations: commit,
157
+ header: 'input: --diff (change set, stdin) · ' +
158
+ `disciplines ${shared.length} · changeSetDisciplines ${changeSetOnly.length} · ` +
159
+ 'disciplines: advise unless enforce: block',
160
+ registrations: changeSet,
146
161
  drafts,
147
- disciplines,
148
- selfModScope: 'common ∪ adapters.git; deduped, includes the config file itself',
162
+ disciplines: [...shared, ...changeSetOnly],
163
+ selfModScope: 'common; includes the config file itself',
149
164
  }),
150
165
  '',
151
166
  ].join('\n');
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * This lives in its own module rather than in the package barrel because ESM re-exports are
6
6
  * eager: importing `loadConfig` from the barrel would instantiate both composition roots,
7
- * putting the session adapter on the commit surface's load path where it is never used. A
7
+ * putting the session adapter on the change-set surface's load path where it is never used. A
8
8
  * workspace missing only that dist would then kill `pdks covenant check` before its
9
9
  * fail-closed handler could record a row. Both composition roots import this module directly
10
10
  * for the same reason.
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * This lives in its own module rather than in the package barrel because ESM re-exports are
6
6
  * eager: importing `loadConfig` from the barrel would instantiate both composition roots,
7
- * putting the session adapter on the commit surface's load path where it is never used. A
7
+ * putting the session adapter on the change-set surface's load path where it is never used. A
8
8
  * workspace missing only that dist would then kill `pdks covenant check` before its
9
9
  * fail-closed handler could record a row. Both composition roots import this module directly
10
10
  * for the same reason.
@@ -13,7 +13,7 @@
13
13
  */
14
14
  export declare function sessionPreStateReader(location: string): string | null | undefined;
15
15
  /**
16
- * The commit surface's reader: it observes a staged diff, whose payloads already carry the
16
+ * The change-set surface's reader: it observes a staged diff, whose payloads already carry the
17
17
  * pre their own observation saw, and it registers no shell axis — so no derivation ever asks
18
18
  * for a pre-state here. Should one arrive, the working tree is not what this surface judges,
19
19
  * and answering from it would compare the diff against the wrong baseline; `undefined` says
@@ -21,7 +21,7 @@ export function sessionPreStateReader(location) {
21
21
  }
22
22
  }
23
23
  /**
24
- * The commit surface's reader: it observes a staged diff, whose payloads already carry the
24
+ * The change-set surface's reader: it observes a staged diff, whose payloads already carry the
25
25
  * pre their own observation saw, and it registers no shell axis — so no derivation ever asks
26
26
  * for a pre-state here. Should one arrive, the working tree is not what this surface judges,
27
27
  * and answering from it would compare the diff against the wrong baseline; `undefined` says
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * The half of an installation every distribution path shares: the data config the judges
5
5
  * read, and the telemetry ignore line. What differs between paths is REGISTRATION — how the
6
- * agent is told to spawn a judge at all — and that lives one layer up (`initClaudeCode` for
7
- * the `init` path). The split is what lets a second path reuse this function unchanged
6
+ * agent is told to spawn a judge at all — and that lives in each agent's own adapter
7
+ * installer. The split is what lets a second path reuse this function unchanged
8
8
  * instead of scaffolding a config a second time, so nothing that registers anything belongs
9
9
  * here.
10
10
  *
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * The half of an installation every distribution path shares: the data config the judges
5
5
  * read, and the telemetry ignore line. What differs between paths is REGISTRATION — how the
6
- * agent is told to spawn a judge at all — and that lives one layer up (`initClaudeCode` for
7
- * the `init` path). The split is what lets a second path reuse this function unchanged
6
+ * agent is told to spawn a judge at all — and that lives in each agent's own adapter
7
+ * installer. The split is what lets a second path reuse this function unchanged
8
8
  * instead of scaffolding a config a second time, so nothing that registers anything belongs
9
9
  * here.
10
10
  *
@@ -32,7 +32,7 @@ const GITIGNORE_ENTRY = `# Polydeukes telemetry — local observation data, neve
32
32
  * {@link schemaDirective} prepends the `yaml-language-server` line when the schema is where
33
33
  * that line would name it.
34
34
  */
35
- const GENERATED_CONFIG = `# Polydeukes protection policy — generated by \`pdks init claude-code\`.
35
+ const GENERATED_CONFIG = `# Polydeukes protection policy — generated by \`pdks init\`.
36
36
  #
37
37
  # This file is data, never code. The judges read it, every verdict traces back to an entry
38
38
  # below, and editing it is how you change what is judged.
@@ -79,6 +79,12 @@ witness:
79
79
  # The disciplines you judge by, and the three rungs one climbs — shown as three entries so
80
80
  # each rung is a line you can copy. Uncomment to start; ids must stay distinct.
81
81
  #
82
+ # Three lists, chosen by what a declaration reads. \`disciplines:\` is judged on both
83
+ # surfaces (the changed file's own pre/post and file sources). \`sessionDisciplines:\` is
84
+ # judged only where a live call is observed before it runs (command line, transcript,
85
+ # actor). \`changeSetDisciplines:\` is judged only over a finished change set
86
+ # (\`--diff\`). The loader refuses an entry written in a list its channels do not fit.
87
+ #
82
88
  # disciplines:
83
89
  # # A draft: prose only, no predicate. Registered and read, never judged.
84
90
  # - id: 'no-todo-in-shipped-code-draft'
@@ -33,6 +33,18 @@
33
33
  "uniqueItems": true,
34
34
  "items": { "$ref": "#/$defs/discipline" }
35
35
  },
36
+ "sessionDisciplines": {
37
+ "description": "Disciplines only the session surface observes — every entry whose declaration binds a transcript, channel, command, or actor evidence channel. Fully identical entries are copy-paste duplicates (by-id uniqueness and the placement rule are enforced by the runtime validator).",
38
+ "type": "array",
39
+ "uniqueItems": true,
40
+ "items": { "$ref": "#/$defs/discipline" }
41
+ },
42
+ "changeSetDisciplines": {
43
+ "description": "Disciplines only the change-set surface observes — every entry whose declaration binds the changes source. Fully identical entries are copy-paste duplicates (by-id uniqueness and the placement rule are enforced by the runtime validator).",
44
+ "type": "array",
45
+ "uniqueItems": true,
46
+ "items": { "$ref": "#/$defs/discipline" }
47
+ },
36
48
  "witness": { "$ref": "#/$defs/witness" }
37
49
  },
38
50
  "$defs": {
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The change-set surface's supply body for named file sources — one repo-relative path read from
3
+ * the working tree.
4
+ */
5
+ import type { SourceReader } from '@polydeukes/core';
6
+ /** {@link worktreeReader} input — the repository the paths are relative to. */
7
+ export type WorktreeReaderSpec = {
8
+ repoRoot: string;
9
+ };
10
+ /**
11
+ * A reader over the working tree under `repoRoot`, with the same absence table the session
12
+ * surface's disk reader pins in its own package.
13
+ *
14
+ * Symlinks are followed: on disk the observable fact IS the target's text, and the session
15
+ * surface reads the same way. A path the tree holds but cannot give as judgeable text —
16
+ * a directory, or bytes carrying a NUL — answers `undefined`, which the declaration's
17
+ * `supply` policy disposes of.
18
+ */
19
+ export declare function worktreeReader(spec: WorktreeReaderSpec): SourceReader;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The change-set surface's supply body for named file sources — one repo-relative path read from
3
+ * the working tree.
4
+ */
5
+ import { readFileSync } from 'node:fs';
6
+ import { join } from 'node:path';
7
+ /**
8
+ * A reader over the working tree under `repoRoot`, with the same absence table the session
9
+ * surface's disk reader pins in its own package.
10
+ *
11
+ * Symlinks are followed: on disk the observable fact IS the target's text, and the session
12
+ * surface reads the same way. A path the tree holds but cannot give as judgeable text —
13
+ * a directory, or bytes carrying a NUL — answers `undefined`, which the declaration's
14
+ * `supply` policy disposes of.
15
+ */
16
+ export function worktreeReader(spec) {
17
+ return (path) => {
18
+ let bytes;
19
+ try {
20
+ bytes = readFileSync(join(spec.repoRoot, path));
21
+ }
22
+ catch (error) {
23
+ const { code } = error;
24
+ if (code === 'ENOENT' || code === 'EISDIR' || code === 'ENOTDIR')
25
+ return undefined;
26
+ throw error;
27
+ }
28
+ return bytes.includes(0) ? undefined : bytes.toString('utf-8');
29
+ };
30
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polydeukes",
3
- "version": "0.6.1",
3
+ "version": "0.7.1",
4
4
  "description": "A development discipline framework for building alongside an AI coding partner — deterministic covenants, a verifiable work ledger, local memory, and adversarial verification. Alpha.",
5
5
  "keywords": [
6
6
  "harness",
@@ -28,18 +28,7 @@
28
28
  "pdks": "./dist/bin.js",
29
29
  "polydeukes": "./dist/bin.js"
30
30
  },
31
- "main": "./dist/index.js",
32
- "module": "./dist/index.js",
33
- "types": "./dist/index.d.ts",
34
31
  "exports": {
35
- ".": {
36
- "types": "./dist/index.d.ts",
37
- "import": "./dist/index.js"
38
- },
39
- "./claude-code": {
40
- "types": "./dist/claude-code.d.ts",
41
- "import": "./dist/claude-code.js"
42
- },
43
32
  "./schema.json": "./dist/schema/polydeukes.schema.json"
44
33
  },
45
34
  "files": [
@@ -54,15 +43,12 @@
54
43
  },
55
44
  "dependencies": {
56
45
  "yaml": "2.9.0",
57
- "@polydeukes/adapter-claude-code": "^0.6.1",
58
- "@polydeukes/core": "^0.6.1",
59
- "@polydeukes/covenant": "^0.6.1",
60
- "@polydeukes/adapter-git": "^0.6.1"
46
+ "@polydeukes/core": "^0.7.1"
61
47
  },
62
48
  "devDependencies": {
63
49
  "@types/node": "^24.0.0",
64
50
  "typescript": "7.0.2",
65
- "vitest": "^4.1.0"
51
+ "vitest": "^5.0.0"
66
52
  },
67
53
  "scripts": {
68
54
  "build": "tsc -p tsconfig.build.json && node scripts/copy-docs.mjs && node scripts/copy-schema.mjs",
@@ -1,79 +0,0 @@
1
- /**
2
- * `runClaudeCodeHook` — the assembled session-surface judgment runner.
3
- *
4
- * The session counterpart of {@link runCovenantCheck}, and the one place where the Claude
5
- * Code adapter (tool vocabulary, up-translation) and the covenant package (dispatcher +
6
- * judge bodies) meet. Packages stay one-way — each depends only on core — so their
7
- * composition lives here, in the umbrella, and the repository's PreToolUse hook shrinks to
8
- * a delegator that calls this function. That is what makes the session surface installable:
9
- * a consumer registers a hook that resolves this package instead of copying assembly.
10
- *
11
- * The protection-policy data (protectedPaths / disciplines / witness) is read from the root
12
- * data config through {@link loadConfig}, which also attaches the config file to its own
13
- * surface.
14
- *
15
- * The valve is the TTL witness, judged over the JSONL transcript provider. The judge body
16
- * always spawns, and only an outcome that translated to blocked consults the witness —
17
- * `witnessed` rows are would-block only. Its defence is provenance rather than secrecy: only
18
- * a real human utterance carries the transcript marking `findUserMessages()` admits.
19
- *
20
- * fail-closed: ANY failure — an unbuilt judge body, an unreadable stdin, a missing or
21
- * invalid config file — resolves to `{ exitCode: 2 }` with one `blocked` record under the
22
- * `hook` label. Nothing throws: an uncaught rejection would exit the delegator non-blocking,
23
- * the cheapest bypass vector there is. Recovery from an unbuilt clone is `pnpm build` (it
24
- * mentions no protected path, so it is never blocked).
25
- */
26
- import { type CanonicalTranscript } from '@polydeukes/core';
27
- import { type CovenantRegistration } from '@polydeukes/covenant';
28
- import { type CovenantModule } from './covenant-module.ts';
29
- import { loadConfig } from './load-config.ts';
30
- /** {@link runClaudeCodeHook} result — the exit code the hook process leaves with. */
31
- export type ClaudeCodeHookOutcome = {
32
- exitCode: 0 | 2;
33
- };
34
- /** `runClaudeCodeHook` input — the `CovenantCheckSpec` shape, session side. */
35
- export type ClaudeCodeHookSpec = {
36
- /** Repository root — config discovery and discipline glob scoping both anchor here. */
37
- repoRoot: string;
38
- /** One raw PreToolUse payload. ABSENT means read fd 0 (the hook's real stdin). */
39
- rawPayload?: string;
40
- /** Overrides the config's telemetry log path (tests and assembly injection). */
41
- telemetryPath?: string;
42
- /** Overrides the resolved covenant dist directory (tests and assembly injection). */
43
- covenantDist?: string;
44
- };
45
- /** {@link assembleSessionRegistrations} input — what the session surface's assembly needs. */
46
- export type SessionAssemblySpec = {
47
- config: ReturnType<typeof loadConfig>['config'];
48
- rootDir: string;
49
- /**
50
- * The covenant surface the registrations are built from — the module the caller loaded
51
- * from the resolved dist, so what judges a call is what that dist carries, and what
52
- * `explain` renders is what would judge it.
53
- */
54
- covenant: CovenantModule;
55
- /** The payload's transcript path. ABSENT leaves the transcript-mod registration out. */
56
- transcriptPath?: string;
57
- transcript?: CanonicalTranscript;
58
- witness?: CovenantRegistration['witness'];
59
- };
60
- /**
61
- * The session surface's registration set. One assembly, two consumers: the runner below
62
- * dispatches it, `explain` renders it — so what a reader is shown is the table the judgment
63
- * actually uses, never a second opinion about it.
64
- */
65
- export declare function assembleSessionRegistrations(spec: SessionAssemblySpec): CovenantRegistration[];
66
- /**
67
- * The session-surface entry point: the post-hoc state comparison wrapped around the judgment.
68
- *
69
- * The comparison sits OUTSIDE {@link judgeHookCall}'s fail-closed try on both ends. Inside
70
- * it, a comparison failure would become a blocked call — the opposite of a mechanism whose
71
- * whole purpose is to record rather than stop — so each side carries its own catch and
72
- * neither can reach the verdict. Observation is fail-open, the direction
73
- * `appendRecordFailOpen` already established: the worst outcome is a missing datum.
74
- *
75
- * Order is the contract. The comparison runs first, so it reads the window the previous call
76
- * left and its rows land ahead of this call's judgment; the re-establishment runs last, so
77
- * this call's own judged writes are folded in rather than alarmed on next time.
78
- */
79
- export declare function runClaudeCodeHook(spec: ClaudeCodeHookSpec): Promise<ClaudeCodeHookOutcome>;