polydeukes 0.6.0 → 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 (117) hide show
  1. package/README.ko.md +30 -70
  2. package/README.md +31 -84
  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 +79 -93
  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 +60 -0
  44. package/dist/docs/README.md +64 -0
  45. package/dist/docs/catalog.json +480 -0
  46. package/dist/docs/concepts/judgment.ko.md +113 -0
  47. package/dist/docs/concepts/judgment.md +113 -0
  48. package/dist/docs/how-to/configure-project.ko.md +99 -0
  49. package/dist/docs/how-to/configure-project.md +98 -0
  50. package/dist/docs/how-to/connect-surfaces.ko.md +120 -0
  51. package/dist/docs/how-to/connect-surfaces.md +121 -0
  52. package/dist/docs/how-to/write-disciplines.ko.md +179 -0
  53. package/dist/docs/how-to/write-disciplines.md +181 -0
  54. package/dist/docs/index.json +2280 -0
  55. package/dist/docs/reference/cli/covenant-check.ko.md +148 -0
  56. package/dist/docs/reference/cli/covenant-check.md +153 -0
  57. package/dist/docs/reference/cli/docs.ko.md +97 -0
  58. package/dist/docs/reference/cli/docs.md +95 -0
  59. package/dist/docs/reference/cli/explain.ko.md +84 -0
  60. package/dist/docs/reference/cli/explain.md +89 -0
  61. package/dist/docs/reference/cli/init.ko.md +119 -0
  62. package/dist/docs/reference/cli/init.md +119 -0
  63. package/dist/docs/reference/configuration/index.ko.md +553 -0
  64. package/dist/docs/reference/{configuration.md → configuration/index.md} +215 -90
  65. package/dist/docs/reference/packages/adapter-claude-code.ko.md +110 -0
  66. package/dist/docs/reference/{adapter-claude-code.md → packages/adapter-claude-code.md} +47 -19
  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 +174 -0
  70. package/dist/docs/reference/{core.md → packages/core.md} +74 -12
  71. package/dist/docs/reference/packages/polydeukes.ko.md +195 -0
  72. package/dist/docs/reference/packages/polydeukes.md +204 -0
  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 +144 -0
  76. package/dist/docs/troubleshooting.md +103 -151
  77. package/dist/docs/tutorials/first-judgment.ko.md +82 -0
  78. package/dist/docs/tutorials/first-judgment.md +81 -0
  79. package/dist/docs-catalog.d.ts +25 -0
  80. package/dist/docs-catalog.js +450 -0
  81. package/dist/docs-library.d.ts +23 -0
  82. package/dist/docs-library.js +347 -0
  83. package/dist/docs-markdown.d.ts +32 -0
  84. package/dist/docs-markdown.js +150 -0
  85. package/dist/docs-query.d.ts +11 -40
  86. package/dist/docs-query.js +28 -122
  87. package/dist/docs-types.d.ts +105 -0
  88. package/dist/docs-types.js +2 -0
  89. package/dist/explain.d.ts +1 -1
  90. package/dist/explain.js +37 -22
  91. package/dist/load-config.d.ts +1 -1
  92. package/dist/load-config.js +1 -1
  93. package/dist/pre-state-reader.d.ts +1 -1
  94. package/dist/pre-state-reader.js +1 -1
  95. package/dist/scaffold-project.d.ts +2 -2
  96. package/dist/scaffold-project.js +9 -3
  97. package/dist/schema/polydeukes.schema.json +12 -0
  98. package/dist/worktree-reader.d.ts +19 -0
  99. package/dist/worktree-reader.js +30 -0
  100. package/package.json +3 -17
  101. package/dist/claude-code-hook.d.ts +0 -79
  102. package/dist/claude-code-hook.js +0 -372
  103. package/dist/claude-code.d.ts +0 -6
  104. package/dist/claude-code.js +0 -6
  105. package/dist/covenant-module.d.ts +0 -25
  106. package/dist/covenant-module.js +0 -42
  107. package/dist/docs/configuration.md +0 -103
  108. package/dist/docs/installation.md +0 -241
  109. package/dist/docs/reference/adapter-git.md +0 -100
  110. package/dist/docs/reference/covenant.md +0 -116
  111. package/dist/docs/reference/polydeukes.md +0 -315
  112. package/dist/index.d.ts +0 -22
  113. package/dist/index.js +0 -21
  114. package/dist/init-claude-code.d.ts +0 -52
  115. package/dist/init-claude-code.js +0 -469
  116. package/dist/init-grok.d.ts +0 -51
  117. package/dist/init-grok.js +0 -242
@@ -1,138 +1,44 @@
1
- /**
2
- * `queryDocs` — the offline documentation query.
3
- *
4
- * The bundled English guides, answered from the installed version. An AI partner that
5
- * searches the web gets whatever release the internet indexed; this returns the document
6
- * that shipped with the code doing the judging, with no network at all.
7
- *
8
- * The domain is the five topics below and nothing else. An unknown topic throws instead of
9
- * resolving to something near it: an answer to a question we never mapped is
10
- * indistinguishable from a real one by the time it reaches a reader.
11
- *
12
- * Every failure throws so the bin can leave stdout at zero bytes and exit 2. Text written
13
- * halfway is read as the document and quoted as the document — the same direction the
14
- * judging surface fails in, for the same reason.
15
- */
16
- import { existsSync, readFileSync } from 'node:fs';
17
- import { join } from 'node:path';
18
- /** The finite query domain — the topic list `pdks docs` prints with no argument. */
19
- export const TOPICS = ['install', 'config', 'discipline', 'covenant', 'witness'];
20
- /** The mapping, as data: which document answers a topic, and what to read next. */
21
- const TOPIC_MAP = {
22
- install: {
23
- sections: [{ file: 'installation.md' }],
24
- seeAlso: 'reference/polydeukes.md',
25
- },
26
- config: {
27
- sections: [{ file: 'reference/configuration.md' }],
28
- seeAlso: 'reference/core.md',
29
- },
30
- discipline: {
31
- sections: [{ file: 'reference/configuration.md', heading: '## `disciplines`' }],
32
- seeAlso: 'reference/covenant.md',
33
- },
34
- covenant: {
35
- sections: [{ file: 'configuration.md', heading: '## What enforcement looks like' }],
36
- seeAlso: 'reference/polydeukes.md',
37
- },
38
- witness: {
39
- sections: [
40
- { file: 'reference/configuration.md', heading: '## `witness`' },
41
- { file: 'troubleshooting.md', heading: '## Opening a blocked call — the witness' },
42
- ],
43
- seeAlso: 'reference/covenant.md',
44
- },
45
- };
46
- /** A fenced block opens and closes on a line whose trimmed form starts with the marker. */
47
- const FENCE = /^(?:`{3,}|~{3,})/;
48
- /** An ATX heading, and its level in the capture. */
49
- const HEADING = /^(#{1,6}) /;
50
- function isTopic(value) {
51
- return TOPICS.includes(value);
52
- }
53
- function headingLevel(line) {
54
- return HEADING.exec(line)?.[1].length ?? 0;
1
+ import { runDocs } from './docs-library.js';
2
+ export { DOCS_TOPICS as TOPICS } from './docs-types.js';
3
+ /** Answer a legacy topic using the same catalog as search and show. */
4
+ export function queryDocs(spec) {
5
+ return runDocs({
6
+ docsRoot: spec.docsRoot,
7
+ args: spec.topic === undefined ? [] : [spec.topic],
8
+ version: '',
9
+ });
55
10
  }
56
11
  /**
57
- * The body of one section of `markdown`: from the line equal to `heading` up to just before
58
- * the next heading of the same or a higher level, returned verbatim.
59
- *
60
- * `heading` is matched by exact string equality. A document that renames its heading kills
61
- * the query here rather than letting a normalizing matcher hand back a neighbouring section
62
- * with full confidence.
63
- *
64
- * Both scans — for the start and for the boundary — run outside code fences. `#` lines
65
- * inside a fence are content: the guides really carry them, and a fence-blind scanner cuts
66
- * the answer at one of those lines while still looking like a success.
12
+ * Extract a section by its exact heading, retaining the original internal helper's contract.
13
+ * Topic retrieval uses stable IDs instead; this helper remains for existing internal callers.
67
14
  */
68
15
  export function extractSection(markdown, heading) {
69
16
  const lines = markdown.split('\n');
70
- const level = headingLevel(heading);
71
- let openMarker;
17
+ const level = /^(#{1,6}) /.exec(heading)?.[1].length ?? 0;
18
+ let fence;
72
19
  let start = -1;
73
- for (let i = 0; i < lines.length; i += 1) {
74
- const line = lines[i];
75
- const trimmed = line.trim();
76
- if (FENCE.test(trimmed)) {
77
- // Opener and closer are both compared trimmed. An indented fence closed by a strict
78
- // bare-marker test would stay open to end of file, and every heading after it would
79
- // silently stop being a heading — the guides carry a two-space-indented one.
80
- if (openMarker === undefined) {
81
- openMarker = trimmed[0];
82
- }
83
- else if (trimmed[0] === openMarker) {
84
- openMarker = undefined;
85
- }
20
+ for (let index = 0; index < lines.length; index += 1) {
21
+ const line = lines[index];
22
+ const marker = /^(`{3,}|~{3,})/.exec(line.trim())?.[1];
23
+ if (marker) {
24
+ if (fence === undefined)
25
+ fence = { marker: marker[0], length: marker.length };
26
+ else if (fence.marker === marker[0] && marker.length >= fence.length)
27
+ fence = undefined;
86
28
  continue;
87
29
  }
88
- if (openMarker !== undefined) {
30
+ if (fence !== undefined)
89
31
  continue;
90
- }
91
32
  if (start === -1) {
92
- if (line === heading) {
93
- start = i;
94
- }
33
+ if (line === heading)
34
+ start = index;
95
35
  continue;
96
36
  }
97
- if (headingLevel(line) > 0 && headingLevel(line) <= level) {
98
- return lines.slice(start, i).join('\n');
99
- }
37
+ const nextLevel = /^(#{1,6}) /.exec(line)?.[1].length ?? 0;
38
+ if (nextLevel > 0 && nextLevel <= level)
39
+ return lines.slice(start, index).join('\n');
100
40
  }
101
- if (start === -1) {
41
+ if (start === -1)
102
42
  throw new Error(`heading not found: ${heading}`);
103
- }
104
- // A section that closes the document ends at end of file; the topic map points at one.
105
43
  return lines.slice(start).join('\n');
106
44
  }
107
- function readSection(docsRoot, section) {
108
- const path = join(docsRoot, section.file);
109
- if (!existsSync(path)) {
110
- // Named, never swallowed into empty text: a silently incomplete bundle would otherwise
111
- // reach a reader as the document itself.
112
- throw new Error(`bundled document missing: ${section.file}`);
113
- }
114
- const markdown = readFileSync(path, 'utf-8');
115
- return section.heading === undefined ? markdown : extractSection(markdown, section.heading);
116
- }
117
- /**
118
- * Answer one documentation query.
119
- *
120
- * With no topic the result is the listing — how an AI discovers what it may ask at all.
121
- * With one, it is the mapped section body followed by the bundled reference to read next.
122
- */
123
- export function queryDocs(spec) {
124
- if (spec.topic === undefined) {
125
- return { text: `Polydeukes docs:\n${TOPICS.map((t) => ` pdks docs ${t}`).join('\n')}\n` };
126
- }
127
- if (!isTopic(spec.topic)) {
128
- throw new Error(`unknown docs topic '${spec.topic}' — known topics: ${TOPICS.join(', ')}`);
129
- }
130
- const entry = TOPIC_MAP[spec.topic];
131
- const body = entry.sections.map((section) => readSection(spec.docsRoot, section)).join('\n');
132
- // Resolved against the bundle, not printed as the bare relative name. A reader given
133
- // `reference/core.md` has to guess where the bundle lives before it can open anything, and
134
- // this line is the only way most of the reference layer is reached at all. A path a file-read
135
- // tool can take is the difference between a pointer and a dead end, and a dead end sends the
136
- // reader back to the web search this command replaces.
137
- return { text: `${body}\nSee also: ${join(spec.docsRoot, entry.seeAlso)}\n` };
138
- }
@@ -0,0 +1,105 @@
1
+ /** Languages included in every bundled document. */
2
+ export type DocsLanguage = 'en' | 'ko';
3
+ /** Backward-compatible topic names; their targets belong to the catalog. */
4
+ export declare const DOCS_TOPICS: readonly ['install', 'config', 'discipline', 'covenant', 'witness'];
5
+ /** Source-root-relative Markdown path and localized metadata used for retrieval and ranking. */
6
+ export type DocsTranslation = {
7
+ path: string;
8
+ title: string;
9
+ summary: string;
10
+ terms?: string[];
11
+ };
12
+ /** Catalog entry identifying a document across languages and selecting it for bundling. */
13
+ export type DocsDocument = {
14
+ id: string;
15
+ category: string;
16
+ order: number;
17
+ bundled: boolean;
18
+ en: DocsTranslation;
19
+ ko?: DocsTranslation;
20
+ };
21
+ /** Source inventory shared by the documentation build and offline query commands. */
22
+ export type DocsCatalog = {
23
+ schemaVersion: number;
24
+ documents: DocsDocument[];
25
+ topics: Record<string, DocsTopic>;
26
+ redirects?: DocsRedirect[];
27
+ };
28
+ /** Ordered content references for a legacy topic, plus a document ID for further reading. */
29
+ export type DocsTopic = {
30
+ references: DocsReference[];
31
+ seeAlso: string;
32
+ };
33
+ /** Stable document ID, optionally narrowed to a section; omission selects the whole document. */
34
+ export type DocsReference = {
35
+ documentId: string;
36
+ sectionId?: string;
37
+ };
38
+ /** Move-notice path and canonical destination, both relative to the documentation root. */
39
+ export type DocsRedirect = {
40
+ path: string;
41
+ target: string;
42
+ };
43
+ /** Anchored Markdown slice with zero-based line bounds: inclusive start, exclusive end. */
44
+ export type DocsSection = {
45
+ id: string;
46
+ title: string;
47
+ level: number;
48
+ startLine: number;
49
+ endLine: number;
50
+ text: string;
51
+ };
52
+ /** Bilingual metadata returned for a document included in a completed build. */
53
+ export type DocsBundleDocument = {
54
+ id: string;
55
+ bundled: boolean;
56
+ category: string;
57
+ order: number;
58
+ translations: Record<DocsLanguage, DocsTranslation>;
59
+ };
60
+ /** Indexed section location; the hash covers its entire translated document, not the section. */
61
+ export type DocsBundleSection = {
62
+ documentId: string;
63
+ language: DocsLanguage;
64
+ sectionId: string;
65
+ title: string;
66
+ level: number;
67
+ path: string;
68
+ hash: string;
69
+ };
70
+ /** Persisted bundle metadata and document hashes, reconstructed on load to detect inconsistency. */
71
+ export type DocsIndex = {
72
+ schemaVersion: 1;
73
+ documents: Array<{
74
+ id: string;
75
+ bundled: boolean;
76
+ category: string;
77
+ order: number;
78
+ translations: Record<DocsLanguage, DocsTranslation>;
79
+ hashes: Record<DocsLanguage, string>;
80
+ }>;
81
+ sections: DocsBundleSection[];
82
+ };
83
+ /** In-memory catalog and index with loaded Markdown keyed by stable document ID. */
84
+ export type LoadedDocsBundle = {
85
+ catalog: DocsCatalog;
86
+ index: DocsIndex;
87
+ documents: Map<string, LoadedDocsDocument>;
88
+ topics: Record<string, DocsTopic>;
89
+ };
90
+ /** Loaded bilingual Markdown with parsed sections and empty search-term lists where omitted. */
91
+ export type LoadedDocsDocument = {
92
+ id: string;
93
+ bundled: boolean;
94
+ category: string;
95
+ order: number;
96
+ translations: Record<DocsLanguage, {
97
+ path: string;
98
+ title: string;
99
+ summary: string;
100
+ terms: string[];
101
+ markdown: string;
102
+ hash: string;
103
+ sections: DocsSection[];
104
+ }>;
105
+ };
@@ -0,0 +1,2 @@
1
+ /** Backward-compatible topic names; their targets belong to the catalog. */
2
+ export const DOCS_TOPICS = ['install', 'config', 'discipline', 'covenant', 'witness'];
package/dist/explain.d.ts CHANGED
@@ -17,7 +17,7 @@ export type ExplainSpec = {
17
17
  * Read the config at `repoRoot`, assemble both surfaces, and render them.
18
18
  *
19
19
  * The session assembly is given a transcript path, so its `transcript-mod` registration
20
- * exists here exactly as it does under a normal hook payload — the path is never read,
20
+ * exists here exactly as it does under a normal call payload — the path is never read,
21
21
  * because the injected transcript is the no-op one.
22
22
  */
23
23
  export declare function explain(spec: ExplainSpec): Promise<{
package/dist/explain.js CHANGED
@@ -9,15 +9,18 @@
9
9
  * nothing and reads no disk. Every failure throws: an answer that cannot be given is never
10
10
  * given halfway.
11
11
  */
12
- import { join } from 'node:path';
13
- import { resolveGitAdapterSettings } from '@polydeukes/adapter-git';
14
12
  import { AXIS_NAMES, deriveShape, noopTranscript, RELATION_NAMES } from '@polydeukes/core';
15
- import { assembleSessionRegistrations } from './claude-code-hook.js';
16
- import { assembleCommitRegistrations } from './covenant-check.js';
17
- import { loadCovenantModule, resolveCovenantDist } from './covenant-module.js';
13
+ import { covenantModule } from './covenant/module.js';
14
+ import { assembleChangeSetRegistrations, assembleCheckRegistrations } from './covenant-check.js';
18
15
  import { loadConfig } from './load-config.js';
19
16
  /** The three meta-covenant labels: registrations that protect the judging chain itself. */
20
17
  const META_LABELS = new Set(['self-mod', 'shell-mod', 'transcript-mod']);
18
+ /**
19
+ * What the session assembly here is given as its evidence path. The real one is loaded per
20
+ * call by an adapter, so this renderer names the IR key instead of a file: the assembly
21
+ * needs a present value to build the conditional registration, and nothing prints it.
22
+ */
23
+ const SESSION_EVIDENCE_PATH_KEY = 'session.evidencePath';
21
24
  /**
22
25
  * The description of a declaration entry: its catalogue coordinate (the mechanism, the axes
23
26
  * its sources derive, and the relations its entries decide), then what it routes on, how
@@ -58,7 +61,7 @@ function row(kind, label, width, description) {
58
61
  /** The description of a meta-covenant registration — how much surface it covers. */
59
62
  function metaDescription(registration, surface) {
60
63
  if (registration.label === 'transcript-mod') {
61
- return 'content predicate · conditional: transcript_path';
64
+ return 'content predicate · conditional: session.evidencePath';
62
65
  }
63
66
  return `paths ${registration.protectedPaths.length} (${surface})`;
64
67
  }
@@ -105,47 +108,59 @@ function renderSurface(spec) {
105
108
  * Read the config at `repoRoot`, assemble both surfaces, and render them.
106
109
  *
107
110
  * The session assembly is given a transcript path, so its `transcript-mod` registration
108
- * exists here exactly as it does under a normal hook payload — the path is never read,
111
+ * exists here exactly as it does under a normal call payload — the path is never read,
109
112
  * because the injected transcript is the no-op one.
110
113
  */
111
114
  export async function explain(spec) {
112
115
  const { config, configPath } = loadConfig({ rootDir: spec.repoRoot });
113
- // Resolved and imported exactly as the two runners do, so what this renders is the table
114
- // that would judge: a dist those runners would refuse cannot be rendered as if it worked.
115
- // The load names the missing module and the recovery command.
116
- const covenant = await loadCovenantModule(resolveCovenantDist());
117
- const disciplines = config.disciplines ?? [];
116
+ // The judge module the two runners assemble against, so what this renders is the table
117
+ // that would judge.
118
+ const covenant = covenantModule;
119
+ const shared = config.disciplines ?? [];
120
+ const sessionOnly = config.sessionDisciplines ?? [];
121
+ const changeSetOnly = config.changeSetDisciplines ?? [];
118
122
  const drafts = config.drafts ?? [];
119
- const session = assembleSessionRegistrations({
123
+ const session = assembleCheckRegistrations({
120
124
  config,
121
125
  rootDir: spec.repoRoot,
122
126
  covenant,
123
- transcriptPath: join(spec.repoRoot, 'transcript.jsonl'),
127
+ surface: 'session',
128
+ // The session key alone, and no roster: a roster is what an adapter loads onto each
129
+ // call, so a reader of the config would be shown one agent's names chosen by this
130
+ // renderer. The evidence path is the same kind of per-call value and is named by the
131
+ // key rather than by a filename: what a config reader is being shown is that the
132
+ // session surface carries a transcript-mod registration, and the renderer prints the
133
+ // key, never the path. The empty lists are the session a run supplies.
134
+ session: { evidencePath: SESSION_EVIDENCE_PATH_KEY, userMessages: [], toolCalls: [] },
124
135
  transcript: noopTranscript,
125
136
  });
126
- const commit = assembleCommitRegistrations({
137
+ const changeSet = assembleChangeSetRegistrations({
127
138
  config,
128
139
  rootDir: spec.repoRoot,
129
140
  covenant,
141
+ surface: 'changeSet',
130
142
  });
131
- const gitSettings = resolveGitAdapterSettings({ namespace: config.adapters?.git });
132
143
  const text = [
133
144
  `pdks explain — ${configPath}`,
134
145
  '',
135
146
  renderSurface({
136
- header: 'surface: session (claude-code hook) · disciplines: advise unless enforce: block · meta: block',
147
+ header: 'input: call IR (one call, stdin) · ' +
148
+ `disciplines ${shared.length} · sessionDisciplines ${sessionOnly.length} · ` +
149
+ 'disciplines: advise unless enforce: block · meta: block',
137
150
  registrations: session,
138
151
  drafts,
139
- disciplines,
152
+ disciplines: [...shared, ...sessionOnly],
140
153
  selfModScope: 'common; includes the config file itself',
141
154
  }),
142
155
  '',
143
156
  renderSurface({
144
- header: `surface: commit (git pre-commit) · enforce: ${gitSettings.enforce} · disciplines: advise unless enforce: block`,
145
- registrations: commit,
157
+ header: 'input: --diff (change set, stdin) · ' +
158
+ `disciplines ${shared.length} · changeSetDisciplines ${changeSetOnly.length} · ` +
159
+ 'disciplines: advise unless enforce: block',
160
+ registrations: changeSet,
146
161
  drafts,
147
- disciplines,
148
- selfModScope: 'common ∪ adapters.git; deduped, includes the config file itself',
162
+ disciplines: [...shared, ...changeSetOnly],
163
+ selfModScope: 'common; includes the config file itself',
149
164
  }),
150
165
  '',
151
166
  ].join('\n');
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * This lives in its own module rather than in the package barrel because ESM re-exports are
6
6
  * eager: importing `loadConfig` from the barrel would instantiate both composition roots,
7
- * putting the session adapter on the commit surface's load path where it is never used. A
7
+ * putting the session adapter on the change-set surface's load path where it is never used. A
8
8
  * workspace missing only that dist would then kill `pdks covenant check` before its
9
9
  * fail-closed handler could record a row. Both composition roots import this module directly
10
10
  * for the same reason.
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * This lives in its own module rather than in the package barrel because ESM re-exports are
6
6
  * eager: importing `loadConfig` from the barrel would instantiate both composition roots,
7
- * putting the session adapter on the commit surface's load path where it is never used. A
7
+ * putting the session adapter on the change-set surface's load path where it is never used. A
8
8
  * workspace missing only that dist would then kill `pdks covenant check` before its
9
9
  * fail-closed handler could record a row. Both composition roots import this module directly
10
10
  * for the same reason.
@@ -13,7 +13,7 @@
13
13
  */
14
14
  export declare function sessionPreStateReader(location: string): string | null | undefined;
15
15
  /**
16
- * The commit surface's reader: it observes a staged diff, whose payloads already carry the
16
+ * The change-set surface's reader: it observes a staged diff, whose payloads already carry the
17
17
  * pre their own observation saw, and it registers no shell axis — so no derivation ever asks
18
18
  * for a pre-state here. Should one arrive, the working tree is not what this surface judges,
19
19
  * and answering from it would compare the diff against the wrong baseline; `undefined` says
@@ -21,7 +21,7 @@ export function sessionPreStateReader(location) {
21
21
  }
22
22
  }
23
23
  /**
24
- * The commit surface's reader: it observes a staged diff, whose payloads already carry the
24
+ * The change-set surface's reader: it observes a staged diff, whose payloads already carry the
25
25
  * pre their own observation saw, and it registers no shell axis — so no derivation ever asks
26
26
  * for a pre-state here. Should one arrive, the working tree is not what this surface judges,
27
27
  * and answering from it would compare the diff against the wrong baseline; `undefined` says
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * The half of an installation every distribution path shares: the data config the judges
5
5
  * read, and the telemetry ignore line. What differs between paths is REGISTRATION — how the
6
- * agent is told to spawn a judge at all — and that lives one layer up (`initClaudeCode` for
7
- * the `init` path). The split is what lets a second path reuse this function unchanged
6
+ * agent is told to spawn a judge at all — and that lives in each agent's own adapter
7
+ * installer. The split is what lets a second path reuse this function unchanged
8
8
  * instead of scaffolding a config a second time, so nothing that registers anything belongs
9
9
  * here.
10
10
  *
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * The half of an installation every distribution path shares: the data config the judges
5
5
  * read, and the telemetry ignore line. What differs between paths is REGISTRATION — how the
6
- * agent is told to spawn a judge at all — and that lives one layer up (`initClaudeCode` for
7
- * the `init` path). The split is what lets a second path reuse this function unchanged
6
+ * agent is told to spawn a judge at all — and that lives in each agent's own adapter
7
+ * installer. The split is what lets a second path reuse this function unchanged
8
8
  * instead of scaffolding a config a second time, so nothing that registers anything belongs
9
9
  * here.
10
10
  *
@@ -32,7 +32,7 @@ const GITIGNORE_ENTRY = `# Polydeukes telemetry — local observation data, neve
32
32
  * {@link schemaDirective} prepends the `yaml-language-server` line when the schema is where
33
33
  * that line would name it.
34
34
  */
35
- const GENERATED_CONFIG = `# Polydeukes protection policy — generated by \`pdks init claude-code\`.
35
+ const GENERATED_CONFIG = `# Polydeukes protection policy — generated by \`pdks init\`.
36
36
  #
37
37
  # This file is data, never code. The judges read it, every verdict traces back to an entry
38
38
  # below, and editing it is how you change what is judged.
@@ -79,6 +79,12 @@ witness:
79
79
  # The disciplines you judge by, and the three rungs one climbs — shown as three entries so
80
80
  # each rung is a line you can copy. Uncomment to start; ids must stay distinct.
81
81
  #
82
+ # Three lists, chosen by what a declaration reads. \`disciplines:\` is judged on both
83
+ # surfaces (the changed file's own pre/post and file sources). \`sessionDisciplines:\` is
84
+ # judged only where a live call is observed before it runs (command line, transcript,
85
+ # actor). \`changeSetDisciplines:\` is judged only over a finished change set
86
+ # (\`--diff\`). The loader refuses an entry written in a list its channels do not fit.
87
+ #
82
88
  # disciplines:
83
89
  # # A draft: prose only, no predicate. Registered and read, never judged.
84
90
  # - id: 'no-todo-in-shipped-code-draft'
@@ -33,6 +33,18 @@
33
33
  "uniqueItems": true,
34
34
  "items": { "$ref": "#/$defs/discipline" }
35
35
  },
36
+ "sessionDisciplines": {
37
+ "description": "Disciplines only the session surface observes — every entry whose declaration binds a transcript, channel, command, or actor evidence channel. Fully identical entries are copy-paste duplicates (by-id uniqueness and the placement rule are enforced by the runtime validator).",
38
+ "type": "array",
39
+ "uniqueItems": true,
40
+ "items": { "$ref": "#/$defs/discipline" }
41
+ },
42
+ "changeSetDisciplines": {
43
+ "description": "Disciplines only the change-set surface observes — every entry whose declaration binds the changes source. Fully identical entries are copy-paste duplicates (by-id uniqueness and the placement rule are enforced by the runtime validator).",
44
+ "type": "array",
45
+ "uniqueItems": true,
46
+ "items": { "$ref": "#/$defs/discipline" }
47
+ },
36
48
  "witness": { "$ref": "#/$defs/witness" }
37
49
  },
38
50
  "$defs": {
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The change-set surface's supply body for named file sources — one repo-relative path read from
3
+ * the working tree.
4
+ */
5
+ import type { SourceReader } from '@polydeukes/core';
6
+ /** {@link worktreeReader} input — the repository the paths are relative to. */
7
+ export type WorktreeReaderSpec = {
8
+ repoRoot: string;
9
+ };
10
+ /**
11
+ * A reader over the working tree under `repoRoot`, with the same absence table the session
12
+ * surface's disk reader pins in its own package.
13
+ *
14
+ * Symlinks are followed: on disk the observable fact IS the target's text, and the session
15
+ * surface reads the same way. A path the tree holds but cannot give as judgeable text —
16
+ * a directory, or bytes carrying a NUL — answers `undefined`, which the declaration's
17
+ * `supply` policy disposes of.
18
+ */
19
+ export declare function worktreeReader(spec: WorktreeReaderSpec): SourceReader;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The change-set surface's supply body for named file sources — one repo-relative path read from
3
+ * the working tree.
4
+ */
5
+ import { readFileSync } from 'node:fs';
6
+ import { join } from 'node:path';
7
+ /**
8
+ * A reader over the working tree under `repoRoot`, with the same absence table the session
9
+ * surface's disk reader pins in its own package.
10
+ *
11
+ * Symlinks are followed: on disk the observable fact IS the target's text, and the session
12
+ * surface reads the same way. A path the tree holds but cannot give as judgeable text —
13
+ * a directory, or bytes carrying a NUL — answers `undefined`, which the declaration's
14
+ * `supply` policy disposes of.
15
+ */
16
+ export function worktreeReader(spec) {
17
+ return (path) => {
18
+ let bytes;
19
+ try {
20
+ bytes = readFileSync(join(spec.repoRoot, path));
21
+ }
22
+ catch (error) {
23
+ const { code } = error;
24
+ if (code === 'ENOENT' || code === 'EISDIR' || code === 'ENOTDIR')
25
+ return undefined;
26
+ throw error;
27
+ }
28
+ return bytes.includes(0) ? undefined : bytes.toString('utf-8');
29
+ };
30
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polydeukes",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "A development discipline framework for building alongside an AI coding partner — deterministic covenants, a verifiable work ledger, local memory, and adversarial verification. Alpha.",
5
5
  "keywords": [
6
6
  "harness",
@@ -28,18 +28,7 @@
28
28
  "pdks": "./dist/bin.js",
29
29
  "polydeukes": "./dist/bin.js"
30
30
  },
31
- "main": "./dist/index.js",
32
- "module": "./dist/index.js",
33
- "types": "./dist/index.d.ts",
34
31
  "exports": {
35
- ".": {
36
- "types": "./dist/index.d.ts",
37
- "import": "./dist/index.js"
38
- },
39
- "./claude-code": {
40
- "types": "./dist/claude-code.d.ts",
41
- "import": "./dist/claude-code.js"
42
- },
43
32
  "./schema.json": "./dist/schema/polydeukes.schema.json"
44
33
  },
45
34
  "files": [
@@ -54,15 +43,12 @@
54
43
  },
55
44
  "dependencies": {
56
45
  "yaml": "2.9.0",
57
- "@polydeukes/adapter-git": "^0.6.0",
58
- "@polydeukes/covenant": "^0.6.0",
59
- "@polydeukes/core": "^0.6.0",
60
- "@polydeukes/adapter-claude-code": "^0.6.0"
46
+ "@polydeukes/core": "^0.7.1"
61
47
  },
62
48
  "devDependencies": {
63
49
  "@types/node": "^24.0.0",
64
50
  "typescript": "7.0.2",
65
- "vitest": "^4.1.0"
51
+ "vitest": "^5.0.0"
66
52
  },
67
53
  "scripts": {
68
54
  "build": "tsc -p tsconfig.build.json && node scripts/copy-docs.mjs && node scripts/copy-schema.mjs",