polydeukes 0.7.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +2 -0
- package/README.md +2 -0
- package/dist/covenant/discipline.d.ts +6 -0
- package/dist/covenant/discipline.js +1 -1
- package/dist/covenant/dispatch.d.ts +10 -7
- package/dist/covenant/dispatch.js +21 -8
- package/dist/covenant-check.d.ts +2 -2
- package/dist/covenant-check.js +113 -9
- package/dist/docs/README.ko.md +3 -2
- package/dist/docs/README.md +3 -2
- package/dist/docs/catalog.json +32 -0
- package/dist/docs/how-to/connect-surfaces.ko.md +33 -5
- package/dist/docs/how-to/connect-surfaces.md +37 -7
- package/dist/docs/how-to/write-disciplines.ko.md +3 -1
- package/dist/docs/how-to/write-disciplines.md +3 -1
- package/dist/docs/index.json +338 -190
- package/dist/docs/reference/cli/explain.ko.md +8 -8
- package/dist/docs/reference/cli/explain.md +10 -8
- package/dist/docs/reference/cli/init.ko.md +43 -4
- package/dist/docs/reference/cli/init.md +43 -5
- package/dist/docs/reference/configuration/index.ko.md +11 -5
- package/dist/docs/reference/configuration/index.md +10 -5
- package/dist/docs/reference/packages/adapter-claude-code.ko.md +5 -4
- package/dist/docs/reference/packages/adapter-claude-code.md +6 -4
- package/dist/docs/reference/packages/adapter-codex.ko.md +99 -0
- package/dist/docs/reference/packages/adapter-codex.md +100 -0
- package/dist/docs/reference/packages/adapter-grok.ko.md +6 -2
- package/dist/docs/reference/packages/adapter-grok.md +6 -2
- package/dist/docs/reference/packages/core.ko.md +1 -1
- package/dist/docs/reference/packages/core.md +1 -1
- package/dist/docs/reference/packages/polydeukes.ko.md +2 -1
- package/dist/docs/reference/packages/polydeukes.md +2 -1
- package/dist/docs/reference/packages/sdk-ts.ko.md +6 -3
- package/dist/docs/reference/packages/sdk-ts.md +6 -3
- package/dist/docs/troubleshooting.ko.md +35 -5
- package/dist/docs/troubleshooting.md +37 -5
- package/dist/docs/tutorials/first-judgment.ko.md +5 -3
- package/dist/docs/tutorials/first-judgment.md +5 -3
- package/dist/load-config.d.ts +24 -0
- package/dist/load-config.js +20 -2
- package/dist/scaffold-project.js +3 -1
- package/package.json +4 -3
package/README.ko.md
CHANGED
|
@@ -17,7 +17,9 @@ CLI 명령은 다음과 같습니다.
|
|
|
17
17
|
|
|
18
18
|
- `pdks covenant check`
|
|
19
19
|
- `pdks init`
|
|
20
|
+
- `pdks-claude-code init` (`@polydeukes/adapter-claude-code`가 제공)
|
|
20
21
|
- `pdks-grok init` (`@polydeukes/adapter-grok`가 제공)
|
|
22
|
+
- `pdks-codex init` (`@polydeukes/adapter-codex`가 제공)
|
|
21
23
|
- `pdks explain`
|
|
22
24
|
- `pdks docs [topic]`
|
|
23
25
|
|
package/README.md
CHANGED
|
@@ -18,7 +18,9 @@ CLI commands:
|
|
|
18
18
|
|
|
19
19
|
- `pdks covenant check`
|
|
20
20
|
- `pdks init`
|
|
21
|
+
- `pdks-claude-code init` (ships with `@polydeukes/adapter-claude-code`)
|
|
21
22
|
- `pdks-grok init` (ships with `@polydeukes/adapter-grok`)
|
|
23
|
+
- `pdks-codex init` (ships with `@polydeukes/adapter-codex`)
|
|
22
24
|
- `pdks explain`
|
|
23
25
|
- `pdks docs [topic]`
|
|
24
26
|
|
|
@@ -41,6 +41,12 @@ export type CompileDisciplinesSpec = {
|
|
|
41
41
|
witness?: CovenantRegistration['witness'];
|
|
42
42
|
transcript?: CanonicalTranscript;
|
|
43
43
|
};
|
|
44
|
+
/**
|
|
45
|
+
* Relativize a file-change path against the root for glob matching. A relative path passes
|
|
46
|
+
* through; an absolute path outside `rootDir` yields null (never matches — discipline scope
|
|
47
|
+
* is declared repo-relative).
|
|
48
|
+
*/
|
|
49
|
+
export declare function relativizeForScope(filePath: string, rootDir: string): string | null;
|
|
44
50
|
/** One change as one world, under the repo-relative path the declaration's scope reads. */
|
|
45
51
|
export type SuppliedWorld = {
|
|
46
52
|
readonly path: string;
|
|
@@ -18,7 +18,7 @@ import { deriveShellChanges } from './shell-evidence.js';
|
|
|
18
18
|
* through; an absolute path outside `rootDir` yields null (never matches — discipline scope
|
|
19
19
|
* is declared repo-relative).
|
|
20
20
|
*/
|
|
21
|
-
function relativizeForScope(filePath, rootDir) {
|
|
21
|
+
export function relativizeForScope(filePath, rootDir) {
|
|
22
22
|
if (!isAbsolute(filePath)) {
|
|
23
23
|
// A relative spelling normalizes before matching — `./x` and `a/../x` name x, and a
|
|
24
24
|
// spelling that resolves out of the root matches nothing. Matching verbatim instead
|
|
@@ -83,13 +83,16 @@ export type CovenantRegistration = {
|
|
|
83
83
|
* Match registrations against a {@link CovenantInput} by path mention (pure).
|
|
84
84
|
*
|
|
85
85
|
* A registration matches when any of its `protectedPaths` is an ancestor/descendant/equal of
|
|
86
|
-
* a path candidate
|
|
87
|
-
* `input.toolCalls[].args
|
|
88
|
-
* write still routes
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
86
|
+
* a path candidate. Candidates come from two places: every string value reachable at any
|
|
87
|
+
* depth inside `input.toolCalls[].args`, read as quote-aware tokenizer words so a
|
|
88
|
+
* quote-split write still routes, and each call's proven `fileChange` path, taken whole
|
|
89
|
+
* because an adapter already resolved it. A call carrying only the latter is the ordinary
|
|
90
|
+
* shape where a host names its target in a form the adapter must parse — the patch text of
|
|
91
|
+
* one edit rather than a path argument. An unread span with a non-empty `protectedPaths`
|
|
92
|
+
* routes fail-closed (the registration matches on its first protected path) rather than
|
|
93
|
+
* silently miss. `subagentSpawns` and `userMessages` never participate. `mentionedPath` is
|
|
94
|
+
* the first protected path (in array order) that mentions. Result preserves registration
|
|
95
|
+
* order, at most one entry per registration.
|
|
93
96
|
*
|
|
94
97
|
* A registration carrying a `matches` predicate routes on it exclusively, path mention
|
|
95
98
|
* skipped for it: non-null return → included with that string as `mentionedPath`; null →
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { appendRecordFailOpen, EXIT_BREAK_BLOCKING, EXIT_UPHOLD, noopTranscript, parseInput, } from '@polydeukes/core';
|
|
11
11
|
import { tokenizeCommandLine } from './bash-line.js';
|
|
12
|
-
import { pathCandidates, pathMatchesProtected } from './mention.js';
|
|
12
|
+
import { pathCandidates, pathMatchesProtected, provenChangePath } from './mention.js';
|
|
13
13
|
import { runCovenant } from './run-covenant.js';
|
|
14
14
|
/**
|
|
15
15
|
* Collect path candidates from every string value inside `value`. Each string is tokenized
|
|
@@ -55,13 +55,16 @@ function collectPathCandidates(value) {
|
|
|
55
55
|
* Match registrations against a {@link CovenantInput} by path mention (pure).
|
|
56
56
|
*
|
|
57
57
|
* A registration matches when any of its `protectedPaths` is an ancestor/descendant/equal of
|
|
58
|
-
* a path candidate
|
|
59
|
-
* `input.toolCalls[].args
|
|
60
|
-
* write still routes
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
58
|
+
* a path candidate. Candidates come from two places: every string value reachable at any
|
|
59
|
+
* depth inside `input.toolCalls[].args`, read as quote-aware tokenizer words so a
|
|
60
|
+
* quote-split write still routes, and each call's proven `fileChange` path, taken whole
|
|
61
|
+
* because an adapter already resolved it. A call carrying only the latter is the ordinary
|
|
62
|
+
* shape where a host names its target in a form the adapter must parse — the patch text of
|
|
63
|
+
* one edit rather than a path argument. An unread span with a non-empty `protectedPaths`
|
|
64
|
+
* routes fail-closed (the registration matches on its first protected path) rather than
|
|
65
|
+
* silently miss. `subagentSpawns` and `userMessages` never participate. `mentionedPath` is
|
|
66
|
+
* the first protected path (in array order) that mentions. Result preserves registration
|
|
67
|
+
* order, at most one entry per registration.
|
|
65
68
|
*
|
|
66
69
|
* A registration carrying a `matches` predicate routes on it exclusively, path mention
|
|
67
70
|
* skipped for it: non-null return → included with that string as `mentionedPath`; null →
|
|
@@ -70,6 +73,16 @@ function collectPathCandidates(value) {
|
|
|
70
73
|
*/
|
|
71
74
|
export function matchRegistrations(input, registrations) {
|
|
72
75
|
const { candidates, failed } = collectPathCandidates(input.toolCalls.map((call) => call.args));
|
|
76
|
+
// A proven change path joins the candidates directly, untokenized: it is a path the
|
|
77
|
+
// adapter already resolved, not a line to be read for one, and tokenizing it would split
|
|
78
|
+
// a path containing a space into words that name nothing. Without this a call whose only
|
|
79
|
+
// evidence is its `fileChange` routes nowhere — the judge bodies read that evidence, so
|
|
80
|
+
// the input would be refused before the layer that can settle it.
|
|
81
|
+
for (const call of input.toolCalls) {
|
|
82
|
+
const proven = provenChangePath(call);
|
|
83
|
+
if (proven !== null)
|
|
84
|
+
candidates.push(proven);
|
|
85
|
+
}
|
|
73
86
|
const matches = [];
|
|
74
87
|
for (const registration of registrations) {
|
|
75
88
|
if (registration.matches !== undefined) {
|
package/dist/covenant-check.d.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import { type CanonicalTranscript, type CovenantInput } from '@polydeukes/core';
|
|
14
14
|
import type { CovenantRegistration } from './covenant/dispatch.ts';
|
|
15
15
|
import { type CovenantModule } from './covenant/module.ts';
|
|
16
|
-
import {
|
|
16
|
+
import { type LoadedConfig } from './load-config.ts';
|
|
17
17
|
/** {@link runCovenantCheck} result — the exit code the check process leaves with. */
|
|
18
18
|
export type CovenantCheckOutcome = {
|
|
19
19
|
exitCode: 0 | 2;
|
|
@@ -57,7 +57,7 @@ export type CovenantCheckSpec = {
|
|
|
57
57
|
};
|
|
58
58
|
/** {@link assembleCheckRegistrations} input — what this runner's assembly needs. */
|
|
59
59
|
export type CheckAssemblySpec = {
|
|
60
|
-
config:
|
|
60
|
+
config: LoadedConfig['config'];
|
|
61
61
|
rootDir: string;
|
|
62
62
|
/**
|
|
63
63
|
* The judge module the registrations are built from, so what judges a change and what
|
package/dist/covenant-check.js
CHANGED
|
@@ -10,14 +10,16 @@
|
|
|
10
10
|
* own `world` exits 2 with one blocked record. An input with no toolCalls is an explicit pass
|
|
11
11
|
* with no records.
|
|
12
12
|
*/
|
|
13
|
+
import { readFileSync } from 'node:fs';
|
|
13
14
|
import { homedir } from 'node:os';
|
|
14
|
-
import { resolve } from 'node:path';
|
|
15
|
+
import { join, resolve } from 'node:path';
|
|
15
16
|
import { appendRecordFailOpen, DEFAULT_TELEMETRY_LOG_PATH, isPlainObject, normalizeProtectedPaths, transcriptFromSession, } from '@polydeukes/core';
|
|
16
17
|
import { compareBaseline, updateBaseline } from './baseline.js';
|
|
18
|
+
import { relativizeForScope } from './covenant/discipline.js';
|
|
17
19
|
import { covenantModule } from './covenant/module.js';
|
|
18
20
|
import { ttlWitness } from './covenant/ttl-witness.js';
|
|
19
21
|
import { STAGED_DELETE, STAGED_WRITE } from './diff-ir.js';
|
|
20
|
-
import {
|
|
22
|
+
import { discoverConfigPath, parseConfigSource } from './load-config.js';
|
|
21
23
|
import { sessionPreStateReader, unobservedPreStateReader } from './pre-state-reader.js';
|
|
22
24
|
import { worktreeReader } from './worktree-reader.js';
|
|
23
25
|
/**
|
|
@@ -115,21 +117,38 @@ export function assembleCheckRegistrations(spec) {
|
|
|
115
117
|
* `surface: 'changeSet'` and neither IR key.
|
|
116
118
|
*/
|
|
117
119
|
export const assembleChangeSetRegistrations = assembleCheckRegistrations;
|
|
118
|
-
/**
|
|
119
|
-
function
|
|
120
|
-
|
|
120
|
+
/** The message a thrown value carries, for a stderr line. */
|
|
121
|
+
function messageOf(error) {
|
|
122
|
+
return error instanceof Error ? error.message : String(error);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* One stage's failure disposition: the stderr line, the recorded row, and exit 2. `suffix`
|
|
126
|
+
* is appended to the message, for a caller that can name the one call which would clear the
|
|
127
|
+
* failure.
|
|
128
|
+
*/
|
|
129
|
+
function failClosed(telemetryPath, error, suffix) {
|
|
130
|
+
process.stderr.write(`covenant check failed closed: ${messageOf(error)}${suffix ?? ''}\n`);
|
|
121
131
|
recordFailClosed(telemetryPath);
|
|
122
132
|
return { exitCode: 2 };
|
|
123
133
|
}
|
|
134
|
+
/** The observation, from the caller's value or from the thunk that produces it. */
|
|
135
|
+
function readInput(spec) {
|
|
136
|
+
return typeof spec.input === 'function' ? spec.input() : spec.input;
|
|
137
|
+
}
|
|
124
138
|
/**
|
|
125
139
|
* Settle the telemetry path and load the config once, or fail closed. The provisional
|
|
126
140
|
* path is settled before the load so a config that never loads still has somewhere to
|
|
127
141
|
* write its blocked row; both terms use `resolve` so a relative `repoRoot` cannot send
|
|
128
142
|
* them to different files. The provisional term sits inside the try because `resolve`
|
|
129
143
|
* throws on a non-string `repoRoot`.
|
|
144
|
+
*
|
|
145
|
+
* The load runs as its three steps rather than through `loadConfig`, so a failure carries
|
|
146
|
+
* how far it got: which file was discovered, and the bytes that file held.
|
|
130
147
|
*/
|
|
131
148
|
function settleConfig(spec) {
|
|
132
149
|
let telemetryPath;
|
|
150
|
+
let configPath;
|
|
151
|
+
let source;
|
|
133
152
|
try {
|
|
134
153
|
// The environment variable sits between the caller's path and the config's, matching
|
|
135
154
|
// what the baseline comparison in this same process already resolves — the two write
|
|
@@ -138,13 +157,21 @@ function settleConfig(spec) {
|
|
|
138
157
|
const envPath = process.env.POLYDEUKES_TELEMETRY_PATH;
|
|
139
158
|
telemetryPath =
|
|
140
159
|
spec.telemetryPath ?? envPath ?? resolve(spec.repoRoot, DEFAULT_TELEMETRY_LOG_PATH);
|
|
141
|
-
|
|
160
|
+
configPath = discoverConfigPath({ rootDir: spec.repoRoot });
|
|
161
|
+
source = readFileSync(join(spec.repoRoot, configPath), 'utf-8');
|
|
162
|
+
const { config } = parseConfigSource({ source, configPath });
|
|
142
163
|
telemetryPath =
|
|
143
164
|
spec.telemetryPath ?? envPath ?? resolve(spec.repoRoot, config.telemetry.logPath);
|
|
144
165
|
return { settled: true, telemetryPath, config };
|
|
145
166
|
}
|
|
146
167
|
catch (error) {
|
|
147
|
-
return {
|
|
168
|
+
return {
|
|
169
|
+
settled: false,
|
|
170
|
+
telemetryPath,
|
|
171
|
+
error,
|
|
172
|
+
...(configPath === undefined ? {} : { configPath }),
|
|
173
|
+
...(source === undefined ? {} : { source }),
|
|
174
|
+
};
|
|
148
175
|
}
|
|
149
176
|
}
|
|
150
177
|
/**
|
|
@@ -312,6 +339,83 @@ function assertJudgeableShape(input) {
|
|
|
312
339
|
throw new Error('input carries a session.channels.sidecar that is not text');
|
|
313
340
|
}
|
|
314
341
|
}
|
|
342
|
+
/**
|
|
343
|
+
* The disposition of a run whose config never loaded.
|
|
344
|
+
*
|
|
345
|
+
* While the one discovered file does not load there is no assembly to judge against, so the
|
|
346
|
+
* session surface admits exactly one shape: a single tool call whose file-change evidence
|
|
347
|
+
* modifies that file, starting from the bytes the loader read, and whose `post` is a text the
|
|
348
|
+
* loader accepts. That call is judged — its result loads — and lands as one `advised` row
|
|
349
|
+
* naming the config path, so the next call's baseline comparison reads the change as
|
|
350
|
+
* explained. Everything else fails closed, and where a single file was discovered the line
|
|
351
|
+
* says which call would repair it.
|
|
352
|
+
*
|
|
353
|
+
* This branch reads no posture: the session hook always spawns with `--enforce block`, so a
|
|
354
|
+
* repair that blocked under that posture would never run anywhere.
|
|
355
|
+
*/
|
|
356
|
+
function settleLoadFailure(spec, failure) {
|
|
357
|
+
const { telemetryPath, error, configPath } = failure;
|
|
358
|
+
if (configPath === undefined || spec.surface !== 'session') {
|
|
359
|
+
return failClosed(telemetryPath, error);
|
|
360
|
+
}
|
|
361
|
+
let input;
|
|
362
|
+
try {
|
|
363
|
+
input = readInput(spec);
|
|
364
|
+
}
|
|
365
|
+
catch (inputError) {
|
|
366
|
+
return failClosed(telemetryPath, inputError);
|
|
367
|
+
}
|
|
368
|
+
const loaded = repairs(input, failure, spec.repoRoot);
|
|
369
|
+
if (loaded !== null) {
|
|
370
|
+
// The repaired config's own log path, under the precedence `settleConfig` uses, so the
|
|
371
|
+
// next call's baseline comparison reads this row where it looks for it.
|
|
372
|
+
const rowPath = spec.telemetryPath ??
|
|
373
|
+
process.env.POLYDEUKES_TELEMETRY_PATH ??
|
|
374
|
+
resolve(spec.repoRoot, loaded.config.telemetry.logPath);
|
|
375
|
+
appendRecordFailOpen(rowPath, {
|
|
376
|
+
event: 'advised',
|
|
377
|
+
label: 'covenant-check',
|
|
378
|
+
subject: configPath,
|
|
379
|
+
});
|
|
380
|
+
process.stderr.write(`covenant check: ${configPath} does not load (${messageOf(error)}) — this call rewrites it into one that does; advised, not judged\n`);
|
|
381
|
+
return { exitCode: 0 };
|
|
382
|
+
}
|
|
383
|
+
return failClosed(telemetryPath, error, ` — fix ${configPath} in one Edit or Write whose result loads; every other call stays blocked until it does`);
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* The loaded config a single call would leave behind, or null when this observation is not
|
|
387
|
+
* that call: exactly one call carrying a plain object, its evidence a modification of the
|
|
388
|
+
* discovered config file (relativized against the root, since a host names its paths
|
|
389
|
+
* absolutely), starting from the bytes the loader read, and leaving a `post` the loader
|
|
390
|
+
* accepts. Requiring the pre to be those bytes keeps a partial view of the file — one
|
|
391
|
+
* notebook cell, or evidence a caller composed — out of the branch.
|
|
392
|
+
*/
|
|
393
|
+
function repairs(input, failure, repoRoot) {
|
|
394
|
+
const { configPath, source } = failure;
|
|
395
|
+
if (configPath === undefined || source === undefined)
|
|
396
|
+
return null;
|
|
397
|
+
if (!Array.isArray(input.toolCalls) || input.toolCalls.length !== 1)
|
|
398
|
+
return null;
|
|
399
|
+
const call = input.toolCalls[0];
|
|
400
|
+
if (!isPlainObject(call))
|
|
401
|
+
return null;
|
|
402
|
+
const { fileChange } = call;
|
|
403
|
+
if (fileChange === undefined || fileChange.kind !== 'modify')
|
|
404
|
+
return null;
|
|
405
|
+
if (relativizeForScope(fileChange.path, repoRoot) !== configPath)
|
|
406
|
+
return null;
|
|
407
|
+
if (fileChange.pre !== source)
|
|
408
|
+
return null;
|
|
409
|
+
const post = fileChange.post;
|
|
410
|
+
if (typeof post !== 'string')
|
|
411
|
+
return null;
|
|
412
|
+
try {
|
|
413
|
+
return parseConfigSource({ source: post, configPath });
|
|
414
|
+
}
|
|
415
|
+
catch {
|
|
416
|
+
return null;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
315
419
|
/**
|
|
316
420
|
* Judge one observation of `repoRoot` exactly as the session surface would, from the IR the
|
|
317
421
|
* caller hands in. Async because the dispatcher spawns covenant bodies. An input with no
|
|
@@ -326,11 +430,11 @@ function assertJudgeableShape(input) {
|
|
|
326
430
|
export async function runCovenantCheck(spec) {
|
|
327
431
|
const settlement = settleConfig(spec);
|
|
328
432
|
if (!settlement.settled)
|
|
329
|
-
return
|
|
433
|
+
return settleLoadFailure(spec, settlement);
|
|
330
434
|
const { telemetryPath, config } = settlement;
|
|
331
435
|
let input;
|
|
332
436
|
try {
|
|
333
|
-
input =
|
|
437
|
+
input = readInput(spec);
|
|
334
438
|
// The world axis is this root's to fill. An input that supplies its own would let a
|
|
335
439
|
// caller choose the files the judge reads.
|
|
336
440
|
if ('world' in input) {
|
package/dist/docs/README.ko.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
| 하려는 것 | 읽을 문서 |
|
|
11
11
|
|---|---|
|
|
12
12
|
| 첫 눈에 보이는 판정을 보고 싶다 | [첫 판정](./tutorials/first-judgment.ko.md) — 설치하고 Claude Code를 배선한 뒤 보호된 편집이 판정되는 모습을 봅니다 |
|
|
13
|
-
| Claude Code, Grok, git을 연결하고 싶다 | [표면 연결하기](./how-to/connect-surfaces.ko.md) — 세션과 커밋 배선, Grok 포함 |
|
|
13
|
+
| Claude Code, Grok, Codex, git을 연결하고 싶다 | [표면 연결하기](./how-to/connect-surfaces.ko.md) — 세션과 커밋 배선, Grok와 Codex 포함 |
|
|
14
14
|
| 프로젝트 설정을 다듬고 싶다 | [프로젝트 설정하기](./how-to/configure-project.ko.md) — 발견, IDE 지원, advise와 block의 선택 |
|
|
15
15
|
| 실제 규율을 쓰고 싶다 | [규율 작성하기](./how-to/write-disciplines.ko.md) — 실전 선언 예제, 특히 locale key pairing |
|
|
16
16
|
| 차단이나 건너뜀에서 회복하고 싶다 | [문제 해결](./troubleshooting.ko.md) — fail-closed 상태, 증인 밸브, 로그 |
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
| [`@polydeukes/core`](./reference/packages/core.ko.md) | 프로토콜, 입력 IR, 설정 스키마, 텔레메트리 |
|
|
30
30
|
| [`@polydeukes/adapter-claude-code`](./reference/packages/adapter-claude-code.ko.md) | Claude Code 세션 표면입니다. 훅 페이로드에서 입력 IR로 |
|
|
31
31
|
| [`@polydeukes/adapter-grok`](./reference/packages/adapter-grok.ko.md) | Grok 세션 표면입니다. 훅 페이로드에서 입력 IR로 |
|
|
32
|
+
| [`@polydeukes/adapter-codex`](./reference/packages/adapter-codex.ko.md) | Codex 세션 표면입니다. 훅 페이로드에서 입력 IR로, 패치가 건드리는 파일마다 원소 하나 |
|
|
32
33
|
|
|
33
34
|
<a id="shape-of-the-thing"></a>
|
|
34
35
|
## 한 페이지로 보는 구조
|
|
@@ -52,7 +53,7 @@
|
|
|
52
53
|
|
|
53
54
|
| 표면 | 판정 대상 | 배선 방법 | 대상 |
|
|
54
55
|
|---|---|---|---|
|
|
55
|
-
| **세션** | 도구 호출, 실행되기 전에 | `pdks-claude-code init` 또는 `pdks-
|
|
56
|
+
| **세션** | 도구 호출, 실행되기 전에 | `pdks-claude-code init`, `pdks-grok init`, 또는 `pdks-codex init` | AI 파트너와 함께 개발하는 프로젝트 |
|
|
56
57
|
| **커밋** | stdin의 unified diff — 스테이징 영역, 작업 트리, ref 범위 | `git diff --cached`를 파이프로 넘기는 pre-commit 훅, 또는 필요할 때 직접 실행 | 혼자 개발하는 사람, 그리고 CI |
|
|
57
58
|
|
|
58
59
|
커밋 판정기는 필요할 때 직접 실행할 수도 있습니다. 작업 후에는 `git diff HEAD | pdks covenant check --diff`,
|
package/dist/docs/README.md
CHANGED
|
@@ -11,7 +11,7 @@ question is.
|
|
|
11
11
|
| If you want to | Read |
|
|
12
12
|
|---|---|
|
|
13
13
|
| Get to a first visible judgment | [First judgment](./tutorials/first-judgment.md) — install, wire Claude Code, and watch one protected edit get judged |
|
|
14
|
-
| Connect Claude Code, Grok, or git | [Connect the surfaces](./how-to/connect-surfaces.md) — session and commit setup, including Grok |
|
|
14
|
+
| Connect Claude Code, Grok, Codex, or git | [Connect the surfaces](./how-to/connect-surfaces.md) — session and commit setup, including Grok and Codex |
|
|
15
15
|
| Shape the project config | [Configure the project](./how-to/configure-project.md) — discovery, IDE support, and advise versus block |
|
|
16
16
|
| Write a real discipline | [Write disciplines](./how-to/write-disciplines.md) — worked declarations, especially locale key pairing |
|
|
17
17
|
| Recover from a block or skip | [Troubleshooting](./troubleshooting.md) — the fail-closed states, the witness valve, and the log |
|
|
@@ -31,6 +31,7 @@ is aspirational.
|
|
|
31
31
|
| [`@polydeukes/core`](./reference/packages/core.md) | The protocol, the input IR, the config schema, telemetry |
|
|
32
32
|
| [`@polydeukes/adapter-claude-code`](./reference/packages/adapter-claude-code.md) | Claude Code session surface — hook payloads become the input IR |
|
|
33
33
|
| [`@polydeukes/adapter-grok`](./reference/packages/adapter-grok.md) | Grok session surface — hook payloads become the input IR |
|
|
34
|
+
| [`@polydeukes/adapter-codex`](./reference/packages/adapter-codex.md) | Codex session surface — hook payloads become the input IR, one element per file the patch touches |
|
|
34
35
|
|
|
35
36
|
<a id="shape-of-the-thing"></a>
|
|
36
37
|
## The shape of the thing, in one page
|
|
@@ -56,7 +57,7 @@ in the whitepaper, which were all found by counting rows rather than by reading
|
|
|
56
57
|
|
|
57
58
|
| Surface | Judges | Wired by | For |
|
|
58
59
|
|---|---|---|---|
|
|
59
|
-
| **Session** | A tool call, before it runs | `pdks-claude-code init` or `pdks-
|
|
60
|
+
| **Session** | A tool call, before it runs | `pdks-claude-code init`, `pdks-grok init`, or `pdks-codex init` | A project developed with an AI partner |
|
|
60
61
|
| **Commit** | A unified diff on stdin — staged, the working tree, or a ref range | A pre-commit hook piping `git diff --cached`, or run on demand | A human developing alone, and CI |
|
|
61
62
|
|
|
62
63
|
The commit judge also answers on demand: `git diff HEAD | pdks covenant check --diff` after a task,
|
package/dist/docs/catalog.json
CHANGED
|
@@ -266,6 +266,22 @@
|
|
|
266
266
|
"summary": "Grok 세션 표면을 설치하고, 어댑터 패키지의 계약과 한계를 확인합니다."
|
|
267
267
|
}
|
|
268
268
|
},
|
|
269
|
+
{
|
|
270
|
+
"id": "package-adapter-codex",
|
|
271
|
+
"category": "reference",
|
|
272
|
+
"order": 17,
|
|
273
|
+
"bundled": true,
|
|
274
|
+
"en": {
|
|
275
|
+
"path": "reference/packages/adapter-codex.md",
|
|
276
|
+
"title": "`@polydeukes/adapter-codex`",
|
|
277
|
+
"summary": "Install the Codex session surface, and consult how patch text becomes file-change evidence."
|
|
278
|
+
},
|
|
279
|
+
"ko": {
|
|
280
|
+
"path": "reference/packages/adapter-codex.ko.md",
|
|
281
|
+
"title": "`@polydeukes/adapter-codex`",
|
|
282
|
+
"summary": "Codex 세션 표면을 설치하고, 패치 텍스트가 파일 변경 증거가 되는 방식을 확인합니다."
|
|
283
|
+
}
|
|
284
|
+
},
|
|
269
285
|
{
|
|
270
286
|
"id": "package-sdk-ts",
|
|
271
287
|
"category": "reference",
|
|
@@ -425,6 +441,22 @@
|
|
|
425
441
|
"title": "<!--",
|
|
426
442
|
"summary": "당시의 개발 기록이며 현재 사용법을 설명하는 참조 문서는 아닙니다."
|
|
427
443
|
}
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
"id": "post-8",
|
|
447
|
+
"category": "history",
|
|
448
|
+
"order": 25,
|
|
449
|
+
"bundled": false,
|
|
450
|
+
"en": {
|
|
451
|
+
"path": "build-in-public/2026-09-v0.7-entrypoint-reshape.md",
|
|
452
|
+
"title": "<!--",
|
|
453
|
+
"summary": "Historical development account; not a current usage reference."
|
|
454
|
+
},
|
|
455
|
+
"ko": {
|
|
456
|
+
"path": "build-in-public/2026-09-v0.7-entrypoint-reshape.ko.md",
|
|
457
|
+
"title": "<!--",
|
|
458
|
+
"summary": "당시의 개발 기록이며 현재 사용법을 설명하는 참조 문서는 아닙니다."
|
|
459
|
+
}
|
|
428
460
|
}
|
|
429
461
|
],
|
|
430
462
|
"topics": {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[English](../how-to/connect-surfaces.md) · **한국어**
|
|
4
4
|
|
|
5
|
-
> 작업에 맞는 표면을 고릅니다. Claude Code와 Grok는 세션 표면을 배선하고, git은 변경 집합 표면을 배선합니다.
|
|
5
|
+
> 작업에 맞는 표면을 고릅니다. Claude Code와 Grok, Codex는 세션 표면을 배선하고, git은 변경 집합 표면을 배선합니다.
|
|
6
6
|
|
|
7
7
|
두 표면은 같은 설정 어휘를 쓰지만 판정 시점이 다릅니다. AI 파트너가 편집할 때는 세션 표면을,
|
|
8
8
|
변경을 이력으로 기록하기 전에는 변경 집합 표면을 사용합니다.
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
|
|
13
13
|
Claude Code에서 AI 파트너와 함께 개발할 때 씁니다.
|
|
14
14
|
|
|
15
|
-
1.
|
|
16
|
-
@polydeukes/adapter-claude-code`. 일회성 `npx` 실행만으로는 부족합니다. 두 표면 모두
|
|
15
|
+
1. 세 패키지를 프로젝트 의존성으로 설치합니다. `pnpm add -D polydeukes
|
|
16
|
+
@polydeukes/core @polydeukes/adapter-claude-code`. 일회성 `npx` 실행만으로는 부족합니다. 두 표면 모두
|
|
17
17
|
프로젝트에 설치된 패키지에서 판정기를 불러옵니다.
|
|
18
18
|
2. 프로젝트 루트에서 배선합니다. `pnpm exec pdks-claude-code init`. 이 실행 파일은 어댑터가
|
|
19
19
|
제공하며, 먼저 `pdks init`으로 초기 파일을 만든 뒤 Claude Code 등록 산출물을 씁니다.
|
|
@@ -31,14 +31,14 @@ Claude Code에서 AI 파트너와 함께 개발할 때 씁니다.
|
|
|
31
31
|
|
|
32
32
|
Grok에서 개발할 때 씁니다.
|
|
33
33
|
|
|
34
|
-
1.
|
|
34
|
+
1. 세 패키지를 프로젝트 의존성으로 설치합니다. `pnpm add -D polydeukes @polydeukes/core @polydeukes/adapter-grok`.
|
|
35
35
|
2. 프로젝트 루트에서 배선합니다. `pnpm exec pdks-grok init`. 어댑터가 이 실행 파일을
|
|
36
36
|
제공합니다. 먼저 `pdks init`으로 초기 파일을 만든 뒤 Grok 등록 산출물을 씁니다.
|
|
37
37
|
3. 설치가 끝나면 Hooks 탭을 다시 불러오거나 새 세션을 엽니다.
|
|
38
38
|
|
|
39
39
|
Grok 프로젝트에는 `.grok/hooks/` 아래에 훅 JSON과 위임자가 생깁니다. 새 등록의 제한 시간은
|
|
40
40
|
60초입니다. Grok 호스트의 기본값은 5초이며, 훅 실행이 시간 초과로 끝나면 해당 호출을
|
|
41
|
-
차단하지 않습니다(fail-open).
|
|
41
|
+
차단하지 않습니다(fail-open). 세션 어댑터를 한 프로젝트에 둘 이상 설치하면 호출마다
|
|
42
42
|
판정기가 두 번 실행될 수 있습니다.
|
|
43
43
|
|
|
44
44
|
Grok는 세션 증인(witness) 밸브에 필요한 Claude 형식의 인간 메시지를 공급하지 않습니다. 대화
|
|
@@ -46,6 +46,34 @@ Grok는 세션 증인(witness) 밸브에 필요한 Claude 형식의 인간 메
|
|
|
46
46
|
의도한 편집이 차단되면 자신의 터미널에서 수행하세요. 변경 집합 표면에는 증인 프롬프트가 없으므로
|
|
47
47
|
차단된 Grok 도구 호출을 커밋 쪽에서 허용할 방법도 없습니다.
|
|
48
48
|
|
|
49
|
+
<a id="codex"></a>
|
|
50
|
+
## Codex 세션 표면
|
|
51
|
+
|
|
52
|
+
Codex에서 개발할 때 씁니다.
|
|
53
|
+
|
|
54
|
+
1. 세 패키지를 프로젝트 의존성으로 설치합니다. `pnpm add -D polydeukes @polydeukes/core @polydeukes/adapter-codex`.
|
|
55
|
+
2. 프로젝트 루트에서 배선합니다. `pnpm exec pdks-codex init`. 어댑터가 이 실행 파일을
|
|
56
|
+
제공합니다. 먼저 `pdks init`으로 초기 파일을 만든 뒤 Codex 등록 산출물을 씁니다.
|
|
57
|
+
3. Codex에서 `/hooks`로 생성된 훅을 승인합니다. 승인하기 전까지는 훅을 건너뜁니다.
|
|
58
|
+
|
|
59
|
+
Codex 프로젝트에는 `.codex/hooks/covenant-pretooluse.mjs` 위임자와 `.codex/hooks.json`의 항목이
|
|
60
|
+
생깁니다. 이 JSON은 덮어쓰지 않고 병합합니다. 다른 이벤트, 다른 matcher, 설치기가 모르는 키는
|
|
61
|
+
그대로 둡니다. 초기 설정은 기본적으로 `.codex/hooks`를 보호합니다.
|
|
62
|
+
|
|
63
|
+
**승인은 선택이 아닙니다.** Codex는 훅 정의의 해시로 신뢰를 기록하므로, 새로 쓴 훅은 검토
|
|
64
|
+
대상으로 표시되고 누군가 승인하기 전까지 건너뛰어집니다. 그때까지는 아무것도 판정되지
|
|
65
|
+
않습니다. `init`은 실행할 때마다 바이트가 같은 명령 문자열을 쓰므로, 다시 설치해도 이미 받은
|
|
66
|
+
승인이 무효가 되지 않습니다.
|
|
67
|
+
|
|
68
|
+
Codex는 모든 파일 편집을 `apply_patch` 하나로 정규화하고, 경로 인자가 아니라 패치 텍스트를
|
|
69
|
+
보냅니다. `Edit`과 `Write`는 훅 파일에 적을 수 있는 matcher 별칭이며 도구 이름으로 도착하지
|
|
70
|
+
않습니다. 패치 하나가 여러 파일을 건드리면 파일마다 IR 원소 하나가 실리고, 그중 하나라도
|
|
71
|
+
차단되면 호출 전체가 차단됩니다.
|
|
72
|
+
|
|
73
|
+
Codex에는 대화 기록 채널이 없어서 세션 증인(witness) 밸브가 읽을 사람 메시지가 없습니다.
|
|
74
|
+
의도한 편집이 차단되면 자신의 터미널에서 수행하세요. 세션 어댑터를 한 프로젝트에 둘 이상
|
|
75
|
+
설치하면 호출마다 판정기가 두 번 실행될 수 있습니다.
|
|
76
|
+
|
|
49
77
|
<a id="change-set-surface"></a>
|
|
50
78
|
## 변경 집합 표면
|
|
51
79
|
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
**English** · [한국어](../how-to/connect-surfaces.ko.md)
|
|
4
4
|
|
|
5
|
-
> Pick the surface that matches the job. Claude Code and
|
|
6
|
-
the change-set surface.
|
|
5
|
+
> Pick the surface that matches the job. Claude Code, Grok, and Codex wire the session surface;
|
|
6
|
+
git wires the change-set surface.
|
|
7
7
|
|
|
8
8
|
The two surfaces share the same config vocabulary, but they answer different moments. Use the
|
|
9
9
|
session surface when an AI partner is making edits, and use the change-set surface when history
|
|
@@ -14,9 +14,9 @@ is about to be written.
|
|
|
14
14
|
|
|
15
15
|
Use this when the project is developed alongside an AI partner in Claude Code.
|
|
16
16
|
|
|
17
|
-
1. Install
|
|
18
|
-
@polydeukes/adapter-claude-code`. A one-off `npx` run is not enough —
|
|
19
|
-
judge from the project's own installed package.
|
|
17
|
+
1. Install the three packages as project dependencies: `pnpm add -D polydeukes
|
|
18
|
+
@polydeukes/core @polydeukes/adapter-claude-code`. A one-off `npx` run is not enough —
|
|
19
|
+
both surfaces load the judge from the project's own installed package.
|
|
20
20
|
2. Wire the project from its root: `pnpm exec pdks-claude-code init`. The adapter ships this bin;
|
|
21
21
|
it runs `pdks init` for the scaffold, then writes the Claude Code registration artifacts.
|
|
22
22
|
3. Keep the generated hook file, settings merge, starter config, discovery rule, and
|
|
@@ -34,20 +34,50 @@ described problem into either a judged entry or a draft entry.
|
|
|
34
34
|
|
|
35
35
|
Use this when the project is developed in Grok.
|
|
36
36
|
|
|
37
|
-
1. Install
|
|
37
|
+
1. Install the three packages as project dependencies: `pnpm add -D polydeukes @polydeukes/core @polydeukes/adapter-grok`.
|
|
38
38
|
2. Wire the project from its root: `pnpm exec pdks-grok init`. The adapter ships this bin; it
|
|
39
39
|
runs `pdks init` for the scaffold, then writes the Grok registration artifacts.
|
|
40
40
|
3. Reload the Hooks tab or open a new session after the installer finishes.
|
|
41
41
|
|
|
42
42
|
A Grok tree gets its own hook JSON and delegator under `.grok/hooks/`. Generated registrations
|
|
43
43
|
use a timeout of 60 seconds. The Grok host default is 5 seconds, and a timed-out hook fails
|
|
44
|
-
open. Installing
|
|
44
|
+
open. Installing more than one session adapter in one project can run the judge twice per call.
|
|
45
45
|
|
|
46
46
|
Grok does not supply the Claude-format human message needed by the session witness valve. The
|
|
47
47
|
session log is ACP `updates.jsonl`, not Claude's JSONL.
|
|
48
48
|
For an intentional blocked edit, use your own terminal. The change-set surface has no prompt, so
|
|
49
49
|
there is no way to authorize a blocked Grok tool call from that side either.
|
|
50
50
|
|
|
51
|
+
<a id="codex"></a>
|
|
52
|
+
## Codex session surface
|
|
53
|
+
|
|
54
|
+
Use this when the project is developed in Codex.
|
|
55
|
+
|
|
56
|
+
1. Install the three packages as project dependencies: `pnpm add -D polydeukes @polydeukes/core @polydeukes/adapter-codex`.
|
|
57
|
+
2. Wire the project from its root: `pnpm exec pdks-codex init`. The adapter ships this bin; it
|
|
58
|
+
runs `pdks init` for the scaffold, then writes the Codex registration artifacts.
|
|
59
|
+
3. Approve the generated hook with `/hooks` in Codex. Until you do, it is skipped.
|
|
60
|
+
|
|
61
|
+
A Codex tree gets a delegator at `.codex/hooks/covenant-pretooluse.mjs` and an entry in
|
|
62
|
+
`.codex/hooks.json`. That JSON is merged, not overwritten: other events, other matchers, and
|
|
63
|
+
keys the installer does not know stay where they are. The scaffold config protects
|
|
64
|
+
`.codex/hooks` by default.
|
|
65
|
+
|
|
66
|
+
**Approval is not optional.** Codex records trust against the hash of a hook's definition, so a
|
|
67
|
+
newly written hook is listed for review and skipped until someone approves it — until then
|
|
68
|
+
nothing is judged. `init` writes a byte-identical command string on every run, so a re-install
|
|
69
|
+
does not invalidate an approval you already gave.
|
|
70
|
+
|
|
71
|
+
Codex normalises every file edit into one tool, `apply_patch`, and sends the patch text rather
|
|
72
|
+
than a path argument. `Edit` and `Write` are matcher aliases you may write in the hooks file;
|
|
73
|
+
they never arrive as the tool name. One patch that touches several files carries one IR element
|
|
74
|
+
per file, and any one of them blocking blocks the whole call.
|
|
75
|
+
|
|
76
|
+
Codex supplies no transcript channel, so the session witness valve has no human message to read.
|
|
77
|
+
For an intentional blocked edit, use your own terminal. Installing more than one session adapter
|
|
78
|
+
in one project can run the judge twice per call.
|
|
79
|
+
|
|
80
|
+
|
|
51
81
|
<a id="change-set-surface"></a>
|
|
52
82
|
## Change-set surface
|
|
53
83
|
|
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
|
|
12
12
|
두 JSON 번역 파일의 키 집합을 비교합니다. 중첩된 키도 비교 대상입니다.
|
|
13
13
|
아래 전체 YAML을 **예제 프로젝트**의 `polydeukes.config.yaml`로 저장합니다. 기존 프로젝트의
|
|
14
|
-
설정을 덮어쓰지 마세요. 기존 설정에 추가할 때는 규율 항목만 복사합니다.
|
|
14
|
+
설정을 덮어쓰지 마세요. 기존 설정에 추가할 때는 규율 항목만 복사합니다. 아래 YAML에는
|
|
15
|
+
`protectedPaths`와 `witness` 블록이 없으므로, 생성된 설정 위에 그대로 저장하면 증인 밸브가
|
|
16
|
+
사라집니다.
|
|
15
17
|
설치 절차는 [첫 판정 튜토리얼](../tutorials/first-judgment.ko.md)에 있습니다.
|
|
16
18
|
|
|
17
19
|
```yaml
|
|
@@ -11,7 +11,9 @@ at `advise` until you decide that the observed results justify blocking.
|
|
|
11
11
|
|
|
12
12
|
This declaration compares the key sets of two JSON translation files, including nested keys.
|
|
13
13
|
Save the complete YAML below as `polydeukes.config.yaml` in an **example project**, not over an
|
|
14
|
-
existing project's configuration. In an existing project, copy only the discipline entry
|
|
14
|
+
existing project's configuration. In an existing project, copy only the discipline entry — the
|
|
15
|
+
YAML below carries no `protectedPaths` and no `witness` block, so saving it over a generated
|
|
16
|
+
config removes the witness valve.
|
|
15
17
|
The [first-judgment tutorial](../tutorials/first-judgment.md) supplies installation steps.
|
|
16
18
|
|
|
17
19
|
```yaml
|