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.
- package/README.ko.md +14 -28
- package/README.md +14 -28
- package/dist/baseline.d.ts +37 -1
- package/dist/baseline.js +68 -1
- package/dist/bin.d.ts +3 -4
- package/dist/bin.js +73 -90
- 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 +8 -8
- package/dist/docs/README.md +8 -8
- package/dist/docs/catalog.json +44 -28
- package/dist/docs/concepts/judgment.ko.md +2 -2
- package/dist/docs/concepts/judgment.md +2 -2
- package/dist/docs/how-to/configure-project.ko.md +16 -16
- package/dist/docs/how-to/configure-project.md +15 -12
- package/dist/docs/how-to/connect-surfaces.ko.md +39 -34
- package/dist/docs/how-to/connect-surfaces.md +36 -33
- package/dist/docs/how-to/write-disciplines.ko.md +61 -6
- package/dist/docs/how-to/write-disciplines.md +62 -6
- package/dist/docs/index.json +607 -373
- package/dist/docs/reference/cli/covenant-check.ko.md +112 -65
- package/dist/docs/reference/cli/covenant-check.md +114 -59
- package/dist/docs/reference/cli/docs.ko.md +2 -2
- package/dist/docs/reference/cli/docs.md +2 -2
- package/dist/docs/reference/cli/explain.ko.md +14 -9
- package/dist/docs/reference/cli/explain.md +15 -10
- package/dist/docs/reference/cli/init.ko.md +52 -52
- package/dist/docs/reference/cli/init.md +43 -55
- package/dist/docs/reference/configuration/index.ko.md +171 -66
- package/dist/docs/reference/configuration/index.md +178 -71
- package/dist/docs/reference/packages/adapter-claude-code.ko.md +38 -11
- package/dist/docs/reference/packages/adapter-claude-code.md +38 -14
- 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 +49 -3
- package/dist/docs/reference/packages/core.md +54 -5
- package/dist/docs/reference/packages/polydeukes.ko.md +138 -77
- package/dist/docs/reference/packages/polydeukes.md +146 -81
- 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 +17 -15
- package/dist/docs/troubleshooting.md +18 -14
- package/dist/docs/tutorials/first-judgment.ko.md +2 -2
- package/dist/docs/tutorials/first-judgment.md +2 -2
- 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/reference/packages/adapter-git.ko.md +0 -101
- package/dist/docs/reference/packages/adapter-git.md +0 -108
- package/dist/docs/reference/packages/covenant.ko.md +0 -115
- package/dist/docs/reference/packages/covenant.md +0 -123
- 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 -586
- package/dist/init-grok.d.ts +0 -51
- package/dist/init-grok.js +0 -242
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ttlWitness` — a time-boxed witness predicate.
|
|
3
|
+
*
|
|
4
|
+
* The predicate on the witness seam. Expiry is built in rather than left to the human
|
|
5
|
+
* to disarm: a human types the agreed token into the conversation, the witness holds
|
|
6
|
+
* for `ttlMs` from that user message's timestamp, then blocking resumes automatically
|
|
7
|
+
* — no stored state, every dispatch re-judges against the injected clock.
|
|
8
|
+
*
|
|
9
|
+
* Invoking the witness is distinct from talking about it: the token must stand alone on
|
|
10
|
+
* the utterance's first line, so quoting or asking about it never opens the valve. Only
|
|
11
|
+
* the token's *placement* is constrained — its value is free, and this module never
|
|
12
|
+
* inspects its shape.
|
|
13
|
+
*/
|
|
14
|
+
import type { WitnessPredicate } from './dispatch.ts';
|
|
15
|
+
/** Configuration for {@link ttlWitness}. */
|
|
16
|
+
export type TtlWitnessSpec = {
|
|
17
|
+
/**
|
|
18
|
+
* The agreed phrase a human types on the first line of a message, alone. Any value
|
|
19
|
+
* works — the defence is provenance, not secrecy, so the phrase is never checked for
|
|
20
|
+
* a prefix, a command shape, or any other form. Surrounding whitespace is trimmed at
|
|
21
|
+
* assembly (both sides of the comparison normalise the same way); trimmed-empty
|
|
22
|
+
* throws, as does a value containing a line break (it could never match a first line).
|
|
23
|
+
*/
|
|
24
|
+
token: string;
|
|
25
|
+
/** Validity window in milliseconds from the user message's timestamp. Must be finite and > 0. */
|
|
26
|
+
ttlMs: number;
|
|
27
|
+
/** Injectable clock (defaults to Date.now). For test determinism. */
|
|
28
|
+
now?: () => number;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Build a predicate that returns `true` iff some user message carries the token
|
|
32
|
+
* within the TTL window.
|
|
33
|
+
*
|
|
34
|
+
* A message witnesses only when all hold: it comes from `transcript.findUserMessages()`
|
|
35
|
+
* (no other text surface is consulted — an AI-synthesised token never counts), the
|
|
36
|
+
* first line of its `text` equals the token once trimmed, its `timestampMs` is
|
|
37
|
+
* present, and `0 <= now() - timestampMs <= ttlMs` (closed interval; a future
|
|
38
|
+
* timestamp is rejected). A missing `timestampMs` means freshness is unprovable and
|
|
39
|
+
* defers to `resolveFailMode('evidence-absence')`, so the disposition's single source of
|
|
40
|
+
* truth stays in the core policy table.
|
|
41
|
+
*
|
|
42
|
+
* Validation is a factory-time concern: a trimmed-empty token or a non-finite /
|
|
43
|
+
* non-positive `ttlMs` throws here; the returned predicate itself never throws.
|
|
44
|
+
* The input is unused: the valve keys on session evidence, not on the payload being
|
|
45
|
+
* judged. Pure: no I/O, no state, no mutation.
|
|
46
|
+
*/
|
|
47
|
+
export declare function ttlWitness(spec: TtlWitnessSpec): WitnessPredicate;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ttlWitness` — a time-boxed witness predicate.
|
|
3
|
+
*
|
|
4
|
+
* The predicate on the witness seam. Expiry is built in rather than left to the human
|
|
5
|
+
* to disarm: a human types the agreed token into the conversation, the witness holds
|
|
6
|
+
* for `ttlMs` from that user message's timestamp, then blocking resumes automatically
|
|
7
|
+
* — no stored state, every dispatch re-judges against the injected clock.
|
|
8
|
+
*
|
|
9
|
+
* Invoking the witness is distinct from talking about it: the token must stand alone on
|
|
10
|
+
* the utterance's first line, so quoting or asking about it never opens the valve. Only
|
|
11
|
+
* the token's *placement* is constrained — its value is free, and this module never
|
|
12
|
+
* inspects its shape.
|
|
13
|
+
*/
|
|
14
|
+
import { resolveFailMode } from '@polydeukes/core';
|
|
15
|
+
/**
|
|
16
|
+
* Build a predicate that returns `true` iff some user message carries the token
|
|
17
|
+
* within the TTL window.
|
|
18
|
+
*
|
|
19
|
+
* A message witnesses only when all hold: it comes from `transcript.findUserMessages()`
|
|
20
|
+
* (no other text surface is consulted — an AI-synthesised token never counts), the
|
|
21
|
+
* first line of its `text` equals the token once trimmed, its `timestampMs` is
|
|
22
|
+
* present, and `0 <= now() - timestampMs <= ttlMs` (closed interval; a future
|
|
23
|
+
* timestamp is rejected). A missing `timestampMs` means freshness is unprovable and
|
|
24
|
+
* defers to `resolveFailMode('evidence-absence')`, so the disposition's single source of
|
|
25
|
+
* truth stays in the core policy table.
|
|
26
|
+
*
|
|
27
|
+
* Validation is a factory-time concern: a trimmed-empty token or a non-finite /
|
|
28
|
+
* non-positive `ttlMs` throws here; the returned predicate itself never throws.
|
|
29
|
+
* The input is unused: the valve keys on session evidence, not on the payload being
|
|
30
|
+
* judged. Pure: no I/O, no state, no mutation.
|
|
31
|
+
*/
|
|
32
|
+
export function ttlWitness(spec) {
|
|
33
|
+
const { token: rawToken, ttlMs, now = Date.now } = spec;
|
|
34
|
+
// Normalise once, at assembly: the first line is compared trimmed, so a token
|
|
35
|
+
// carrying stray surrounding whitespace could never equal it — the valve would
|
|
36
|
+
// pass validation and then silently never open.
|
|
37
|
+
const token = rawToken.trim();
|
|
38
|
+
if (token.length === 0) {
|
|
39
|
+
throw new TypeError('ttlWitness: token must be non-empty after trimming');
|
|
40
|
+
}
|
|
41
|
+
// A token containing a line break is structurally inert: the judgment compares the
|
|
42
|
+
// FIRST LINE of an utterance, which by construction never contains one, so such a
|
|
43
|
+
// token could never match anything — the valve would pass validation and then
|
|
44
|
+
// silently refuse every utterance. Rejecting it here is validation of viability,
|
|
45
|
+
// not interpretation of the phrase (the value stays free otherwise).
|
|
46
|
+
if (/[\r\n]/.test(token)) {
|
|
47
|
+
throw new TypeError('ttlWitness: token must not contain line breaks');
|
|
48
|
+
}
|
|
49
|
+
if (!(Number.isFinite(ttlMs) && ttlMs > 0)) {
|
|
50
|
+
throw new TypeError('ttlWitness: ttlMs must be a finite positive number');
|
|
51
|
+
}
|
|
52
|
+
return (_input, transcript) => {
|
|
53
|
+
// One clock read per judgment: every message in this dispatch is measured
|
|
54
|
+
// against the same "now", so the closed-interval boundary cannot flip with
|
|
55
|
+
// a message's position in the transcript.
|
|
56
|
+
const judgedAt = now();
|
|
57
|
+
return transcript.findUserMessages().some((message) => {
|
|
58
|
+
// The first line only, compared whole: an utterance invokes the witness, it does
|
|
59
|
+
// not merely mention it. `split` keeps a leading blank line as an empty first
|
|
60
|
+
// element, so a token below it never matches, and `trim` absorbs both surrounding
|
|
61
|
+
// spaces and the trailing `\r` of a CRLF transport — CRLF only: a lone `\r` with no
|
|
62
|
+
// `\n` is not treated as a line break, so such a message fails closed (refused),
|
|
63
|
+
// never open.
|
|
64
|
+
//
|
|
65
|
+
// `trim` covers the whole Unicode space class, so a token padded with NBSP, an
|
|
66
|
+
// ideographic space, or a BOM opens the valve exactly as an ASCII-padded one does.
|
|
67
|
+
// That latitude is intentional: this seam defends provenance, not secrecy — the
|
|
68
|
+
// token is read only from `findUserMessages()`, so no AI-synthesised padding
|
|
69
|
+
// reaches it, while a human's IME or clipboard routinely supplies these.
|
|
70
|
+
const [firstLine = ''] = message.text.split('\n');
|
|
71
|
+
if (firstLine.trim() !== token)
|
|
72
|
+
return false;
|
|
73
|
+
if (message.timestampMs === undefined) {
|
|
74
|
+
return resolveFailMode('evidence-absence') === 'open';
|
|
75
|
+
}
|
|
76
|
+
const elapsed = judgedAt - message.timestampMs;
|
|
77
|
+
return elapsed >= 0 && elapsed <= ttlMs;
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
}
|
package/dist/covenant-check.d.ts
CHANGED
|
@@ -1,72 +1,105 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `pdks covenant check` — the
|
|
2
|
+
* `pdks covenant check` — the composition root both surfaces' callers reach through the bin.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* the staged domain assembles; the other domains open no commit.
|
|
4
|
+
* The judged unit is the input IR the caller hands in; this root opens no repository. Assembly
|
|
5
|
+
* mirrors the session hook — loadConfig → normalizeProtectedPaths → dispatchCovenants — and
|
|
6
|
+
* runs the same judge bodies, so a change receives the verdict a session tool call would.
|
|
7
|
+
* Each toolCall is dispatched as its own input so telemetry stays one row per file.
|
|
9
8
|
*
|
|
10
|
-
* fail-closed: a missing config, an
|
|
11
|
-
* blocked record. An
|
|
9
|
+
* fail-closed: a missing config, an input that could not be produced, or an input carrying its
|
|
10
|
+
* own `world` exits 2 with one blocked record. An input with no toolCalls is an explicit pass
|
|
11
|
+
* with no records.
|
|
12
12
|
*/
|
|
13
|
-
import { type
|
|
14
|
-
import type { CovenantRegistration } from '
|
|
15
|
-
import { type CovenantModule } from './covenant
|
|
13
|
+
import { type CanonicalTranscript, type CovenantInput } from '@polydeukes/core';
|
|
14
|
+
import type { CovenantRegistration } from './covenant/dispatch.ts';
|
|
15
|
+
import { type CovenantModule } from './covenant/module.ts';
|
|
16
16
|
import { loadConfig } from './load-config.ts';
|
|
17
|
-
/**
|
|
18
|
-
* Which observation of the commit surface a run judges. Only the collector differs between
|
|
19
|
-
* them; the IR, the assembly, and the dispatcher are one path.
|
|
20
|
-
*
|
|
21
|
-
* The adapter that owns the git grammar owns the type: its supply body reads a path the way
|
|
22
|
-
* each observation sees the tree, and this root names the same fact for its callers.
|
|
23
|
-
*/
|
|
24
|
-
export type CheckDomain = Observation;
|
|
25
17
|
/** {@link runCovenantCheck} result — the exit code the check process leaves with. */
|
|
26
18
|
export type CovenantCheckOutcome = {
|
|
27
19
|
exitCode: 0 | 2;
|
|
28
20
|
};
|
|
21
|
+
/**
|
|
22
|
+
* `CovenantSurface` — which observation unit the input is, and so which discipline list
|
|
23
|
+
* stands beside the shared one.
|
|
24
|
+
*
|
|
25
|
+
* `session` is one call the host observed before it ran; `changeSet` is a finished change
|
|
26
|
+
* set. The caller's input mode is the whole answer — the IR's own keys never choose.
|
|
27
|
+
*/
|
|
28
|
+
export type CovenantSurface = 'session' | 'changeSet';
|
|
29
29
|
/** `runCovenantCheck` input. */
|
|
30
30
|
export type CovenantCheckSpec = {
|
|
31
|
-
/** Repository root — config discovery and
|
|
31
|
+
/** Repository root — config discovery and the world axis's disk reads both anchor here. */
|
|
32
32
|
repoRoot: string;
|
|
33
|
+
/**
|
|
34
|
+
* The observation to judge, or a thunk that produces it. A thunk that throws fails the
|
|
35
|
+
* run closed after the config settles, so a caller translating an input of its own leaves
|
|
36
|
+
* the same one blocked row a missing config would.
|
|
37
|
+
*/
|
|
38
|
+
input: CovenantInput | (() => CovenantInput);
|
|
39
|
+
/** Which surface this input is an observation of — the caller's input mode says so. */
|
|
40
|
+
surface: CovenantSurface;
|
|
33
41
|
/**
|
|
34
42
|
* Overrides where telemetry is written (tests and assembly injection) — the first term
|
|
35
43
|
* of the precedence, ahead of the config's `telemetry.logPath` and of the default this
|
|
36
44
|
* runner settles before the config loads. Absent, both of those apply in that order.
|
|
37
45
|
*/
|
|
38
46
|
telemetryPath?: string;
|
|
39
|
-
/** Overrides the
|
|
40
|
-
|
|
47
|
+
/** Overrides the judge module the run assembles against (tests and assembly injection). */
|
|
48
|
+
covenant?: CovenantModule;
|
|
41
49
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
50
|
+
* The observer's posture for the whole run. ABSENT means `advise`: every break, a
|
|
51
|
+
* protected path included, lands as a row and exit 0 — the change-set surface's default, since
|
|
52
|
+
* a staged gate-file change has already passed the session surface or was made by a human,
|
|
53
|
+
* and this surface has no valve a human could answer. `block` is the caller's opt-in
|
|
54
|
+
* (`--enforce block` on the bin); an entry's own level composes lenient-wins as always.
|
|
45
55
|
*/
|
|
46
|
-
|
|
47
|
-
/** Which observation to judge. ABSENT means `staged`. */
|
|
48
|
-
domain?: CheckDomain;
|
|
56
|
+
enforce?: 'advise' | 'block';
|
|
49
57
|
};
|
|
50
|
-
/** {@link
|
|
51
|
-
export type
|
|
58
|
+
/** {@link assembleCheckRegistrations} input — what this runner's assembly needs. */
|
|
59
|
+
export type CheckAssemblySpec = {
|
|
52
60
|
config: ReturnType<typeof loadConfig>['config'];
|
|
53
61
|
rootDir: string;
|
|
54
62
|
/**
|
|
55
|
-
* The
|
|
56
|
-
*
|
|
57
|
-
* `explain` renders is what would judge it.
|
|
63
|
+
* The judge module the registrations are built from, so what judges a change and what
|
|
64
|
+
* `explain` renders come from one surface.
|
|
58
65
|
*/
|
|
59
66
|
covenant: CovenantModule;
|
|
67
|
+
/**
|
|
68
|
+
* Which surface these registrations judge for: `disciplines` plus `sessionDisciplines`
|
|
69
|
+
* on `session`, `disciplines` plus `changeSetDisciplines` on `changeSet`.
|
|
70
|
+
*/
|
|
71
|
+
surface: CovenantSurface;
|
|
72
|
+
/** The host's tool roster. ABSENT leaves the staged names as the mutating roster. */
|
|
73
|
+
tools?: CovenantInput['tools'];
|
|
74
|
+
/** The host's session evidence. ABSENT is the absence of a session. */
|
|
75
|
+
session?: CovenantInput['session'];
|
|
76
|
+
/** The session flattened for a `transcript` binding — the runner derives it once from `session`. */
|
|
77
|
+
transcript?: CanonicalTranscript;
|
|
60
78
|
witness?: CovenantRegistration['witness'];
|
|
61
79
|
};
|
|
62
80
|
/**
|
|
63
|
-
*
|
|
64
|
-
*
|
|
81
|
+
* This runner's registration set — one assembly that the runner dispatches and `explain`
|
|
82
|
+
* renders.
|
|
83
|
+
*
|
|
84
|
+
* The surface picks the discipline lists; the two IR keys shape the meta-covenants and
|
|
85
|
+
* nothing else — the roster says which names route to which meta-covenant, and the session
|
|
86
|
+
* says whether there is history to protect, to bind, and to read pre-state from disk for.
|
|
65
87
|
*/
|
|
66
|
-
export declare function
|
|
88
|
+
export declare function assembleCheckRegistrations(spec: CheckAssemblySpec): CovenantRegistration[];
|
|
67
89
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
|
|
90
|
+
* The name `explain` renders the change-set surface from — the same assembly, called with
|
|
91
|
+
* `surface: 'changeSet'` and neither IR key.
|
|
92
|
+
*/
|
|
93
|
+
export declare const assembleChangeSetRegistrations: typeof assembleCheckRegistrations;
|
|
94
|
+
/**
|
|
95
|
+
* Judge one observation of `repoRoot` exactly as the session surface would, from the IR the
|
|
96
|
+
* caller hands in. Async because the dispatcher spawns covenant bodies. An input with no
|
|
97
|
+
* toolCalls is an explicit pass: nothing to judge, no records.
|
|
98
|
+
*
|
|
99
|
+
* An input carrying a session is one call of a live agent session, so the post-hoc state
|
|
100
|
+
* comparison wraps the judgment: it runs first, reading the window the previous call left,
|
|
101
|
+
* and the re-establishment runs last, folding this call's own judged writes in. Both sides
|
|
102
|
+
* sit OUTSIDE the judgment with their own catch — a mechanism whose purpose is to record
|
|
103
|
+
* rather than stop may never reach an exit code.
|
|
71
104
|
*/
|
|
72
105
|
export declare function runCovenantCheck(spec: CovenantCheckSpec): Promise<CovenantCheckOutcome>;
|