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
@@ -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
+ }
@@ -1,72 +1,105 @@
1
1
  /**
2
- * `pdks covenant check` — the commit surface's composition root.
2
+ * `pdks covenant check` — the composition root both surfaces' callers reach through the bin.
3
3
  *
4
- * Assembly mirrors the session hook loadConfig normalizeProtectedPaths collect
5
- * dispatchCovenants and spawns the same covenant dist bodies, so a change receives the
6
- * verdict a session tool call would. Each change is dispatched as its own input so
7
- * telemetry stays one row per file. The witness valve is a `/dev/tty` prompt that only
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 unbuilt body, or a collector failure exits 2 with one
11
- * blocked record. An empty domain is an explicit pass with no records.
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 Observation } from '@polydeukes/adapter-git';
14
- import type { CovenantRegistration } from '@polydeukes/covenant';
15
- import { type CovenantModule } from './covenant-module.ts';
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 staged collection both anchor here. */
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 resolved covenant dist directory (tests and assembly injection). */
40
- covenantDist?: string;
47
+ /** Overrides the judge module the run assembles against (tests and assembly injection). */
48
+ covenant?: CovenantModule;
41
49
  /**
42
- * TTY valve seam: writes the given prompt and returns the line a human typed, or null
43
- * for no input. ABSENT means a non-TTY environment — the valve never opens, which is
44
- * what keeps it human-only.
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
- ttyPrompt?: (prompt: string) => string | null;
47
- /** Which observation to judge. ABSENT means `staged`. */
48
- domain?: CheckDomain;
56
+ enforce?: 'advise' | 'block';
49
57
  };
50
- /** {@link assembleCommitRegistrations} input — what the commit surface's assembly needs. */
51
- export type CommitAssemblySpec = {
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 covenant surface the registrations are built from the module the caller loaded
56
- * from the resolved dist, so what judges a change is what that dist carries, and what
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
- * The commit surface's registration set — one assembly that the runner dispatches and
64
- * `explain` renders.
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 assembleCommitRegistrations(spec: CommitAssemblySpec): CovenantRegistration[];
88
+ export declare function assembleCheckRegistrations(spec: CheckAssemblySpec): CovenantRegistration[];
67
89
  /**
68
- * Judge one observation of `repoRoot` exactly as the session surface would — the staged
69
- * diff by default, the working tree or a ref range on request. Async because the dispatcher
70
- * spawns covenant bodies. An empty domain is an explicit pass: nothing to judge, no records.
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>;