polydeukes 0.6.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.
Files changed (108) hide show
  1. package/README.ko.md +16 -28
  2. package/README.md +16 -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 +109 -0
  12. package/dist/covenant/discipline.js +667 -0
  13. package/dist/covenant/dispatch.d.ts +156 -0
  14. package/dist/covenant/dispatch.js +250 -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 +74 -41
  40. package/dist/covenant-check.js +372 -129
  41. package/dist/diff-ir.d.ts +28 -0
  42. package/dist/diff-ir.js +310 -0
  43. package/dist/docs/README.ko.md +10 -9
  44. package/dist/docs/README.md +10 -9
  45. package/dist/docs/catalog.json +76 -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 +67 -34
  51. package/dist/docs/how-to/connect-surfaces.md +67 -34
  52. package/dist/docs/how-to/write-disciplines.ko.md +64 -7
  53. package/dist/docs/how-to/write-disciplines.md +65 -7
  54. package/dist/docs/index.json +744 -362
  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 +20 -15
  60. package/dist/docs/reference/cli/explain.md +22 -15
  61. package/dist/docs/reference/cli/init.ko.md +91 -52
  62. package/dist/docs/reference/cli/init.md +81 -55
  63. package/dist/docs/reference/configuration/index.ko.md +182 -71
  64. package/dist/docs/reference/configuration/index.md +188 -76
  65. package/dist/docs/reference/packages/adapter-claude-code.ko.md +39 -11
  66. package/dist/docs/reference/packages/adapter-claude-code.md +40 -14
  67. package/dist/docs/reference/packages/adapter-codex.ko.md +99 -0
  68. package/dist/docs/reference/packages/adapter-codex.md +100 -0
  69. package/dist/docs/reference/packages/adapter-grok.ko.md +81 -0
  70. package/dist/docs/reference/packages/adapter-grok.md +83 -0
  71. package/dist/docs/reference/packages/core.ko.md +50 -4
  72. package/dist/docs/reference/packages/core.md +55 -6
  73. package/dist/docs/reference/packages/polydeukes.ko.md +139 -77
  74. package/dist/docs/reference/packages/polydeukes.md +147 -81
  75. package/dist/docs/reference/packages/sdk-ts.ko.md +164 -0
  76. package/dist/docs/reference/packages/sdk-ts.md +167 -0
  77. package/dist/docs/troubleshooting.ko.md +51 -19
  78. package/dist/docs/troubleshooting.md +53 -17
  79. package/dist/docs/tutorials/first-judgment.ko.md +6 -4
  80. package/dist/docs/tutorials/first-judgment.md +6 -4
  81. package/dist/explain.d.ts +1 -1
  82. package/dist/explain.js +37 -22
  83. package/dist/load-config.d.ts +25 -1
  84. package/dist/load-config.js +21 -3
  85. package/dist/pre-state-reader.d.ts +1 -1
  86. package/dist/pre-state-reader.js +1 -1
  87. package/dist/scaffold-project.d.ts +2 -2
  88. package/dist/scaffold-project.js +12 -4
  89. package/dist/schema/polydeukes.schema.json +12 -0
  90. package/dist/worktree-reader.d.ts +19 -0
  91. package/dist/worktree-reader.js +30 -0
  92. package/package.json +5 -18
  93. package/dist/claude-code-hook.d.ts +0 -79
  94. package/dist/claude-code-hook.js +0 -372
  95. package/dist/claude-code.d.ts +0 -6
  96. package/dist/claude-code.js +0 -6
  97. package/dist/covenant-module.d.ts +0 -25
  98. package/dist/covenant-module.js +0 -42
  99. package/dist/docs/reference/packages/adapter-git.ko.md +0 -101
  100. package/dist/docs/reference/packages/adapter-git.md +0 -108
  101. package/dist/docs/reference/packages/covenant.ko.md +0 -115
  102. package/dist/docs/reference/packages/covenant.md +0 -123
  103. package/dist/index.d.ts +0 -22
  104. package/dist/index.js +0 -21
  105. package/dist/init-claude-code.d.ts +0 -52
  106. package/dist/init-claude-code.js +0 -586
  107. package/dist/init-grok.d.ts +0 -51
  108. package/dist/init-grok.js +0 -242
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.
@@ -20,6 +20,17 @@ export declare const CONFIG_FILENAMES: readonly ['polydeukes.config.yaml', 'poly
20
20
  export type LoadConfigSpec = {
21
21
  rootDir: string;
22
22
  };
23
+ /** {@link discoverConfigPath} input — the directory the candidates are looked for in. */
24
+ export type DiscoverConfigPathSpec = {
25
+ rootDir: string;
26
+ };
27
+ /** {@link parseConfigSource} input — the config text and the path it was discovered at. */
28
+ export type ParseConfigSourceSpec = {
29
+ /** The config file's whole text. */
30
+ source: string;
31
+ /** rootDir-relative path the source came from — the self-protection entry and error context. */
32
+ configPath: string;
33
+ };
23
34
  /** `LoadedConfig` — the loader's return value. */
24
35
  export type LoadedConfig = {
25
36
  /** defineConfig() resolution — protectedPaths already includes configPath */
@@ -43,3 +54,16 @@ export type LoadedConfig = {
43
54
  * protection surface, guaranteed here so no assembler has to remember.
44
55
  */
45
56
  export declare function loadConfig(spec: LoadConfigSpec): LoadedConfig;
57
+ /**
58
+ * The discovery half: the rootDir-relative filename of the one candidate present, or the
59
+ * throw that names the zero or the collision. Exported so a caller that has to know WHICH
60
+ * file failed to load — the runner's config-repair branch — asks the same question the
61
+ * loader does rather than a second spelling of it.
62
+ */
63
+ export declare function discoverConfigPath(spec: DiscoverConfigPathSpec): string;
64
+ /**
65
+ * The parse-and-validate half, over a text rather than a file: parse, `$schema` strip,
66
+ * `defineConfig`, self-protection attach. Exported so the runner can ask whether a text a
67
+ * call is about to write would load, without opening any file.
68
+ */
69
+ export declare function parseConfigSource(spec: ParseConfigSourceSpec): LoadedConfig;
@@ -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.
@@ -39,6 +39,17 @@ export const CONFIG_FILENAMES = [
39
39
  * protection surface, guaranteed here so no assembler has to remember.
40
40
  */
41
41
  export function loadConfig(spec) {
42
+ const configPath = discoverConfigPath({ rootDir: spec.rootDir });
43
+ const source = readFileSync(join(spec.rootDir, configPath), 'utf-8');
44
+ return parseConfigSource({ source, configPath });
45
+ }
46
+ /**
47
+ * The discovery half: the rootDir-relative filename of the one candidate present, or the
48
+ * throw that names the zero or the collision. Exported so a caller that has to know WHICH
49
+ * file failed to load — the runner's config-repair branch — asks the same question the
50
+ * loader does rather than a second spelling of it.
51
+ */
52
+ export function discoverConfigPath(spec) {
42
53
  const { rootDir } = spec;
43
54
  const found = CONFIG_FILENAMES.filter((name) => existsSync(join(rootDir, name)));
44
55
  if (found.length === 0) {
@@ -47,8 +58,15 @@ export function loadConfig(spec) {
47
58
  if (found.length > 1) {
48
59
  throw new Error(`ambiguous Polydeukes config in ${rootDir} — found ${found.join(' and ')}; keep exactly one`);
49
60
  }
50
- const configPath = found[0];
51
- const source = readFileSync(join(rootDir, configPath), 'utf-8');
61
+ return found[0];
62
+ }
63
+ /**
64
+ * The parse-and-validate half, over a text rather than a file: parse, `$schema` strip,
65
+ * `defineConfig`, self-protection attach. Exported so the runner can ask whether a text a
66
+ * call is about to write would load, without opening any file.
67
+ */
68
+ export function parseConfigSource(spec) {
69
+ const { source, configPath } = spec;
52
70
  // Default core schema — custom tags stay unresolved and surface as errors or
53
71
  // warnings depending on version; both escalate to a throw (config-as-data:
54
72
  // uncomputable, so it cannot lie).
@@ -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.
@@ -50,7 +50,8 @@ languages:
50
50
  # The protection list. A tool call whose proven target is one of these paths is blocked, and
51
51
  # so is a shell command that mentions one without a read-only head.
52
52
  #
53
- # .claude/hooks, .claude/settings.json, .grok/hooks — the gate definitions themselves.
53
+ # .claude/hooks, .claude/settings.json, .grok/hooks, .codex/hooks — the gate definitions
54
+ # themselves.
54
55
  # Editing them does not evade a judgment, it removes the judgment; the session surface
55
56
  # is the only layer that can watch it happen.
56
57
  #
@@ -62,6 +63,7 @@ protectedPaths:
62
63
  - '.claude/hooks'
63
64
  - '.claude/settings.json'
64
65
  - '.grok/hooks'
66
+ - '.codex/hooks'
65
67
 
66
68
  # The time-boxed witness — the human valve on a blocked verdict. A human types this token so
67
69
  # it stands alone on a message's FIRST line, the window holds for ttlMinutes, then blocking
@@ -79,6 +81,12 @@ witness:
79
81
  # The disciplines you judge by, and the three rungs one climbs — shown as three entries so
80
82
  # each rung is a line you can copy. Uncomment to start; ids must stay distinct.
81
83
  #
84
+ # Three lists, chosen by what a declaration reads. \`disciplines:\` is judged on both
85
+ # surfaces (the changed file's own pre/post and file sources). \`sessionDisciplines:\` is
86
+ # judged only where a live call is observed before it runs (command line, transcript,
87
+ # actor). \`changeSetDisciplines:\` is judged only over a finished change set
88
+ # (\`--diff\`). The loader refuses an entry written in a list its channels do not fit.
89
+ #
82
90
  # disciplines:
83
91
  # # A draft: prose only, no predicate. Registered and read, never judged.
84
92
  # - 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,7 +1,8 @@
1
1
  {
2
2
  "name": "polydeukes",
3
- "version": "0.6.1",
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.",
3
+ "version": "0.8.0",
4
+ "description": "A development discipline framework for building alongside an AI coding partner — deterministic covenants, a verifiable work ledger, local memory, and adversarial verification. Beta.",
5
+ "author": "huskyhoochu <dfg1499@gmail.com>",
5
6
  "keywords": [
6
7
  "harness",
7
8
  "guard",
@@ -28,18 +29,7 @@
28
29
  "pdks": "./dist/bin.js",
29
30
  "polydeukes": "./dist/bin.js"
30
31
  },
31
- "main": "./dist/index.js",
32
- "module": "./dist/index.js",
33
- "types": "./dist/index.d.ts",
34
32
  "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
33
  "./schema.json": "./dist/schema/polydeukes.schema.json"
44
34
  },
45
35
  "files": [
@@ -54,15 +44,12 @@
54
44
  },
55
45
  "dependencies": {
56
46
  "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"
47
+ "@polydeukes/core": "^0.8.0"
61
48
  },
62
49
  "devDependencies": {
63
50
  "@types/node": "^24.0.0",
64
51
  "typescript": "7.0.2",
65
- "vitest": "^4.1.0"
52
+ "vitest": "^5.0.0"
66
53
  },
67
54
  "scripts": {
68
55
  "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>;