polydeukes 0.5.0 → 0.6.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 (79) hide show
  1. package/README.ko.md +54 -74
  2. package/README.md +55 -86
  3. package/dist/baseline.d.ts +82 -0
  4. package/dist/baseline.js +166 -0
  5. package/dist/bin.d.ts +1 -1
  6. package/dist/bin.js +26 -6
  7. package/dist/claude-code-hook.d.ts +7 -5
  8. package/dist/claude-code-hook.js +69 -17
  9. package/dist/claude-code.d.ts +6 -0
  10. package/dist/claude-code.js +6 -0
  11. package/dist/covenant-check.d.ts +10 -17
  12. package/dist/covenant-check.js +44 -12
  13. package/dist/covenant-module.d.ts +2 -2
  14. package/dist/covenant-module.js +9 -1
  15. package/dist/docs/README.ko.md +60 -0
  16. package/dist/docs/README.md +64 -0
  17. package/dist/docs/catalog.json +464 -0
  18. package/dist/docs/concepts/judgment.ko.md +113 -0
  19. package/dist/docs/concepts/judgment.md +113 -0
  20. package/dist/docs/how-to/configure-project.ko.md +99 -0
  21. package/dist/docs/how-to/configure-project.md +95 -0
  22. package/dist/docs/how-to/connect-surfaces.ko.md +115 -0
  23. package/dist/docs/how-to/connect-surfaces.md +118 -0
  24. package/dist/docs/how-to/write-disciplines.ko.md +124 -0
  25. package/dist/docs/how-to/write-disciplines.md +125 -0
  26. package/dist/docs/index.json +2046 -0
  27. package/dist/docs/reference/cli/covenant-check.ko.md +101 -0
  28. package/dist/docs/reference/cli/covenant-check.md +98 -0
  29. package/dist/docs/reference/cli/docs.ko.md +97 -0
  30. package/dist/docs/reference/cli/docs.md +95 -0
  31. package/dist/docs/reference/cli/explain.ko.md +79 -0
  32. package/dist/docs/reference/cli/explain.md +84 -0
  33. package/dist/docs/reference/cli/init.ko.md +119 -0
  34. package/dist/docs/reference/cli/init.md +131 -0
  35. package/dist/docs/reference/configuration/index.ko.md +448 -0
  36. package/dist/docs/reference/configuration/index.md +474 -0
  37. package/dist/docs/reference/packages/adapter-claude-code.ko.md +83 -0
  38. package/dist/docs/reference/{adapter-claude-code.md → packages/adapter-claude-code.md} +16 -10
  39. package/dist/docs/reference/packages/adapter-git.ko.md +101 -0
  40. package/dist/docs/reference/{adapter-git.md → packages/adapter-git.md} +25 -15
  41. package/dist/docs/reference/packages/core.ko.md +128 -0
  42. package/dist/docs/reference/{core.md → packages/core.md} +32 -13
  43. package/dist/docs/reference/packages/covenant.ko.md +115 -0
  44. package/dist/docs/reference/{covenant.md → packages/covenant.md} +41 -26
  45. package/dist/docs/reference/packages/polydeukes.ko.md +134 -0
  46. package/dist/docs/reference/packages/polydeukes.md +139 -0
  47. package/dist/docs/troubleshooting.ko.md +142 -0
  48. package/dist/docs/troubleshooting.md +97 -122
  49. package/dist/docs/tutorials/first-judgment.ko.md +82 -0
  50. package/dist/docs/tutorials/first-judgment.md +81 -0
  51. package/dist/docs-catalog.d.ts +25 -0
  52. package/dist/docs-catalog.js +450 -0
  53. package/dist/docs-library.d.ts +23 -0
  54. package/dist/docs-library.js +347 -0
  55. package/dist/docs-markdown.d.ts +32 -0
  56. package/dist/docs-markdown.js +150 -0
  57. package/dist/docs-query.d.ts +11 -40
  58. package/dist/docs-query.js +28 -122
  59. package/dist/docs-types.d.ts +105 -0
  60. package/dist/docs-types.js +2 -0
  61. package/dist/explain.d.ts +3 -5
  62. package/dist/explain.js +48 -47
  63. package/dist/index.d.ts +2 -3
  64. package/dist/index.js +1 -2
  65. package/dist/init-claude-code.d.ts +5 -3
  66. package/dist/init-claude-code.js +226 -63
  67. package/dist/init-grok.d.ts +51 -0
  68. package/dist/init-grok.js +242 -0
  69. package/dist/load-config.d.ts +5 -1
  70. package/dist/load-config.js +2 -1
  71. package/dist/pre-state-reader.d.ts +22 -0
  72. package/dist/pre-state-reader.js +32 -0
  73. package/dist/scaffold-project.js +48 -8
  74. package/dist/schema/polydeukes.schema.json +38 -91
  75. package/package.json +7 -7
  76. package/dist/docs/configuration.md +0 -103
  77. package/dist/docs/installation.md +0 -212
  78. package/dist/docs/reference/configuration.md +0 -338
  79. package/dist/docs/reference/polydeukes.md +0 -287
@@ -14,7 +14,9 @@
14
14
  *
15
15
  * Nothing existing is overwritten. The settings file in particular is merged, never
16
16
  * replaced: a consumer's other PreToolUse registrations and permissions are live
17
- * configuration, and replacing them would disarm every other tool they wired.
17
+ * configuration, and replacing them would disarm every other tool they wired. A grok JSON
18
+ * whose command still names the grok mjs is rewritten to this hook's command so the two
19
+ * installers do not leave two spawn strings.
18
20
  */
19
21
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
20
22
  import { findPackageJSON } from 'node:module';
@@ -22,6 +24,7 @@ import { dirname, join } from 'node:path';
22
24
  import { MUTATING_TOOLS, SHELL_TOOLS } from '@polydeukes/adapter-claude-code';
23
25
  import { isPlainObject } from '@polydeukes/core';
24
26
  import { TOPICS } from './docs-query.js';
27
+ import { retargetGrokHookCommandToClaude } from './init-grok.js';
25
28
  import { CONFIG_FILENAMES } from './load-config.js';
26
29
  import { scaffoldProject } from './scaffold-project.js';
27
30
  /** The published entry point the generated hook loads the judge through. */
@@ -110,7 +113,9 @@ This project is judged by Polydeukes, and the matching documentation ships insid
110
113
  installed package. \`pdks docs\` answers offline, from the same version that does the
111
114
  judging; a web search answers from whichever release it indexed.
112
115
 
113
- Run \`pdks docs\` for the topic list, \`pdks docs <topic>\` for one section.
116
+ Run \`pdks docs\` for the topic list, \`pdks docs <topic>\` for topic content,
117
+ \`pdks docs search "locale key pairing"\` to find a section, or
118
+ \`pdks docs show write-disciplines\` to retrieve the guide. Add \`--lang ko\` for Korean.
114
119
 
115
120
  A local install puts the bin in \`node_modules/.bin\`, which a plain shell does not have on
116
121
  PATH. If \`pdks\` is not found, run \`./node_modules/.bin/pdks docs <topic>\` — or your package
@@ -131,7 +136,7 @@ ${TOPICS.map((topic) => `| ${DOCS_TOPIC_PURPOSE[topic]} | \`pdks docs ${topic}\`
131
136
  */
132
137
  export const GENERATED_SKILL = `---
133
138
  name: discipline-draft
134
- description: Turn a described discipline problem into a registered entry in polydeukes.config — a judged entry when the current families can express it, a draft entry otherwise. Use when the user describes a recurring problem they want promised away ("I keep...", "stop X from happening", "we should never...", "how do I enforce Y").
139
+ description: Turn a described discipline problem into a registered entry in polydeukes.config — a judged entry when the declaration grammar and observed evidence can express it, a draft entry otherwise. Use when the user describes a recurring problem they want promised away ("I keep...", "stop X from happening", "we should never...", "how do I enforce Y").
135
140
  ---
136
141
 
137
142
  # discipline-draft — from a problem description to a registered discipline
@@ -151,33 +156,67 @@ promises and classify each separately.
151
156
 
152
157
  ### 2. Classify the shape
153
158
 
154
- Ask these questions in order; the first yes decides.
159
+ Choose from the current catalogue, then check whether the intended surface can supply the
160
+ required evidence. A mechanism name constrains the declaration; it does not implement the
161
+ promise by itself. The extracted axes and body relations must be subsets of the admitted
162
+ sets below. Scope filtering is separate from the extracted axes.
155
163
 
156
- | # | Question | Family | Entry key |
164
+ | Mechanism | Admitted axes | Body relations | Evidence or structural condition |
157
165
  | --- | --- | --- | --- |
158
- | 1 | Is the promise about content newly ADDED to a file (a pattern that must not appear in new lines)? | delta | \`forbid\` |
159
- | 2 | Is it about a whole path that must not be modified or deleted (creating it once stays allowed)? | path | \`immutable\` |
160
- | 3 | Is it about the shell command line itself, regardless of files? | command | \`forbidCommand\` |
161
- | 4 | Does it require that something else was already done earlier in the session (a tool call that must precede this one)? | context | \`requirePrecedent\` |
162
- | 5 | None of the above | | \`draft: true\` (step 4b) |
166
+ | \`pairing\` | \`world\` | \`equal\` | Compare supplied files or channels; extract keys when values may differ. |
167
+ | \`companion\` | \`change\`, \`world\` | \`implies\` | Compare presence by key; a multi-file promise needs the observed change set. |
168
+ | \`monotonic-order\` | \`change\`, \`world\` | \`ordered\` | Extract a sequence with an explicit comparison field; order is not presence. |
169
+ | \`fingerprint-sync\` | \`world\` | \`equal\` | Compare supplied stamps; no generator or compiler runs during judgment. |
170
+ | \`producer-owned\` | \`actor\` | \`empty\`, \`nonEmpty\` | Requires host-provided actor evidence, not an artifact's self-reported producer. |
171
+ | \`self-absolution-ban\` | \`change\` | \`unchanged\`, \`empty\` | Extract protected fields or path changes; choose creation/deletion supply explicitly. |
172
+ | \`actor-scope\` | \`actor\` | \`empty\`, \`nonEmpty\` | Requires a proven actor; a missing actor is not proof of the main session. |
173
+ | \`precedent\` | \`history\`, \`world\` | \`nonEmpty\` | Requires an observed earlier call in a transcript or supplied channel. |
174
+ | \`phase-order\` | \`history\` | \`ordered\` | Compare observed call ordinals; missing phases need a separate presence promise. |
175
+ | \`turn-locality\` | \`history\` | \`nonEmpty\` | Requires observed turns and time or ordinal boundaries. |
176
+ | \`stated-ground\` | \`history\` | \`nonEmpty\` | Can require recorded text, not establish whether its reasoning is sound. |
177
+ | \`controlled-vocabulary\` | \`change\`, \`world\` | \`subset\` | Extract values and an explicit allowed set. |
178
+ | \`naming\` | \`change\` | \`empty\`, \`nonEmpty\` | Scope must read \`target.path\`; match the intended name pattern. |
179
+ | \`added-only\` | \`change\` | \`empty\` | Compare pre/post extractions and judge only newly added matches. |
180
+ | \`one-way-marker\` | \`change\` | \`subset\` | Existing markers must remain in the extracted post-change set. |
181
+ | \`delegated-scope\` | — | — | Reserved for a definition-time evaluator; not accepted in current declarations. |
182
+ | \`scoped-valve\` | \`change\`, \`actor\`, \`world\`, \`history\` | \`empty\`, \`nonEmpty\`, \`equal\`, \`subset\`, \`implies\`, \`ordered\`, \`unchanged\` | Requires a \`witness\` block expressing the exception condition. |
183
+ | \`forbidden-command\` | \`change\` | \`empty\` | Scope must read \`command\`; a text pattern is not shell semantic analysis. |
184
+
185
+ These four requests illustrate the classification boundary:
186
+
187
+ | Request | Classification | Proof |
188
+ | --- | --- | --- |
189
+ | The English and Korean locale files must carry identical keys. | \`pairing\`, with two supplied files. | An unmatched key breaks; translated values may differ. |
190
+ | Every status must belong to an allowed list. | \`controlled-vocabulary\`, with a supplied allowed set. | An unknown status breaks; an allowed status passes. |
191
+ | A successful package lookup must precede a manifest edit. | \`precedent\`, with observed session history. | Failed or absent lookups break; an unavailable transcript is a supply case. |
192
+ | A fresh benchmark must execute during judgment to prove a performance claim. | \`draft\`: the engine does not execute benchmarks. | Comparing an existing report would be a different promise. |
193
+
194
+ Run \`pdks docs show write-disciplines\` for the key-pairing walkthrough and
195
+ \`pdks docs show configuration --section disciplines\` for the declaration grammar.
196
+ Use \`--lang ko\` for Korean; these commands read the installed version offline.
163
197
 
164
- Existing occurrences are forgiven by the delta family — only new additions break the promise.
165
- That is usually what you want: a discipline adopted today should not indict yesterday's code.
198
+ An \`added-only\` declaration forgives existing occurrences — only what the edit adds breaks
199
+ the promise. That is usually what you want: a discipline adopted today should not indict
200
+ yesterday's code.
166
201
 
167
- Two path-shaped promises take no \`disciplines:\` entry at all. A path nobody may touch
168
- belongs in the top-level \`protectedPaths:\` list — its own config block, never an entry
169
- key. And a path that must never be CREATED is not expressible today: \`immutable\` allows
170
- creation by design, so register that promise as a draft (step 4b).
202
+ One path-shaped promise takes no \`disciplines:\` entry at all: a path nobody may touch
203
+ belongs in the top-level \`protectedPaths:\` list — its own config block, never an entry key.
171
204
 
172
205
  ### 3. Check the observation boundary
173
206
 
174
- Two kinds of promise cannot be judged here, whatever their shape:
207
+ Do not confuse an expressible relation with available evidence:
175
208
 
176
- - **Destruction outside the repository** — judgment observes the project root only. Register
177
- nothing; use the agent's own permission deny policy for commands like \`rm -rf ~\`.
178
- - **Writes by child processes** — a test runner or script writing files is invisible to the
179
- session surface, which judges declared tool calls only. Say so to the user; the commit
180
- surface will still see the result as a staged diff.
209
+ - **Files outside the repository** — file-change protection observes the project root.
210
+ Use the host's permission policy for comprehensive protection outside it. A command-text
211
+ pattern may recognize a particular string, but does not observe all resulting writes.
212
+ - **Writes by child processes** arbitrary writes inside a test runner or script are not
213
+ individually observed by the session surface. A commit comparison can observe the resulting
214
+ files when they enter its selected diff; it does not recover the originating tool history.
215
+ - **Missing history or actor channels** — choose the declaration's supply policy explicitly.
216
+ Commit observations have no session transcript; \`supply: pass\` records a skip, not success.
217
+ - **Fresh execution or semantic proof** — the engine compares supplied evidence. It does not
218
+ run a new benchmark or prove that a written explanation is true. Preserve that unmet promise
219
+ as a draft rather than silently replacing it with a weaker text check.
181
220
 
182
221
  ### 4a. Expressible now — register a judged entry
183
222
 
@@ -198,24 +237,156 @@ languages:
198
237
  disciplines:
199
238
  - id: 'no-focused-tests'
200
239
  why: 'a committed .only silently shrinks the suite to one test'
201
- forbid: '\\.only\\('
240
+ declare:
241
+ mechanism: 'added-only'
242
+ scope: { source: 'target.path', include: ['^src/'] }
243
+ supply: { pre: 'empty', post: 'empty' }
244
+ extract:
245
+ before:
246
+ - { op: 'source', of: 'pre' }
247
+ - { op: 'lines' }
248
+ - { op: 'keyByPattern', re: '(\\.only\\()' }
249
+ after:
250
+ - { op: 'source', of: 'post' }
251
+ - { op: 'lines' }
252
+ - { op: 'keyByPattern', re: '(\\.only\\()' }
253
+ added:
254
+ - { op: 'onlyIn', of: 'after', notIn: 'before' }
255
+ relate:
256
+ - id: 'nothing-added'
257
+ relation: { op: 'empty', of: 'added' }
258
+ message: 'adds {key}: {value}'
259
+ enforce: advise
260
+ \`\`\`
261
+
262
+ A command-line ban reads the fixed source \`command\` and scopes on it — the scope is part of
263
+ the mechanism's shape, so a \`forbidden-command\` entry without it is refused at load time:
264
+
265
+ \`\`\`yaml
266
+ languages:
267
+ placeholder:
268
+ productionGlob: 'src/**'
269
+ testCmd: 'echo "set a verification command for {scope}"'
270
+ disciplines:
271
+ - id: 'no-force-push'
272
+ why: 'a force push rewrites history nobody reviewed'
273
+ declare:
274
+ mechanism: 'forbidden-command'
275
+ scope: { source: 'command' }
276
+ extract:
277
+ hits:
278
+ - { op: 'source', of: 'command' }
279
+ - { op: 'lines' }
280
+ - { op: 'matches', re: 'git push\\b.*--force(?![\\w-])' }
281
+ relate:
282
+ - { id: 'no-force', relation: { op: 'empty', of: 'hits' }, message: '{value}' }
283
+ enforce: advise
284
+ \`\`\`
285
+
286
+ The following examples implement the first three classification cases. Both locale files and
287
+ the allowed-status file must exist and contain valid JSON. File bindings use the proposed
288
+ contents for a file changed by the current observation, not a second stale disk read.
289
+
290
+ \`\`\`yaml
291
+ languages:
292
+ json:
293
+ productionGlob: 'locales/**/*.json'
294
+ testCmd: 'pnpm test'
295
+ disciplines:
296
+ - id: 'locale-key-parity'
297
+ why: 'the ko and en locales must carry the same keys'
298
+ declare:
299
+ mechanism: 'pairing'
300
+ scope: { source: 'target.path', include: ['^locales/(ko|en)[.]json$'] }
301
+ sources:
302
+ ko: { file: 'locales/ko.json' }
303
+ en: { file: 'locales/en.json' }
304
+ supply: { ko: 'error', en: 'error' }
305
+ extract:
306
+ koKeys: [{ op: 'source', of: 'ko' }, { op: 'json' }, { op: 'flattenKeys' }]
307
+ enKeys: [{ op: 'source', of: 'en' }, { op: 'json' }, { op: 'flattenKeys' }]
308
+ relate:
309
+ - id: 'parity'
310
+ relation: { op: 'equal', of: ['koKeys', 'enKeys'] }
311
+ messageBySide:
312
+ left: '{key} is in ko only'
313
+ right: '{key} is in en only'
202
314
  enforce: advise
203
315
  \`\`\`
204
316
 
317
+ \`\`\`yaml
318
+ languages:
319
+ json:
320
+ productionGlob: '*.json'
321
+ testCmd: 'pnpm test'
322
+ disciplines:
323
+ - id: 'status-vocabulary'
324
+ why: 'statuses.json may contain only values listed in allowed-statuses.json'
325
+ declare:
326
+ mechanism: 'controlled-vocabulary'
327
+ scope: { source: 'target.path', include: ['^statuses[.]json$'] }
328
+ sources: { allowed: { file: 'allowed-statuses.json' } }
329
+ supply: { post: 'error', allowed: 'error' }
330
+ extract:
331
+ selected: [{ op: 'source', of: 'post' }, { op: 'json' }, { op: 'items' }]
332
+ permitted: [{ op: 'source', of: 'allowed' }, { op: 'json' }, { op: 'items' }]
333
+ relate:
334
+ - id: 'allowed-status'
335
+ relation: { op: 'subset', of: 'selected', in: 'permitted' }
336
+ message: 'unknown status: {value}'
337
+ enforce: advise
338
+ \`\`\`
339
+
340
+ Here both status files are JSON arrays of strings. This declaration scopes on statuses.json;
341
+ editing only the allowed list does not trigger it. Broaden the observation deliberately if
342
+ changes to that list must recheck all dependent files.
343
+
344
+ \`\`\`yaml
345
+ languages:
346
+ typescript:
347
+ productionGlob: 'src/**'
348
+ testCmd: 'pnpm test'
349
+ disciplines:
350
+ - id: 'manifest-needs-npm-view'
351
+ why: 'a successful package lookup must precede a manifest edit'
352
+ declare:
353
+ mechanism: 'precedent'
354
+ scope: { source: 'target.path', include: ['^(packages/[^/]+/)?package[.]json$'] }
355
+ sources: { session: { transcript: true } }
356
+ supply: { session: 'pass' }
357
+ extract:
358
+ npmView:
359
+ - { op: 'source', of: 'session' }
360
+ - { op: 'toolUses', names: ['Bash'] }
361
+ - { op: 'filter', when: [{ field: 'succeeded', eq: true }] }
362
+ - { op: 'select', path: 'args.command' }
363
+ - { op: 'matches', re: '^npm view ' }
364
+ relate:
365
+ - id: 'npm-view'
366
+ relation: { op: 'nonEmpty', of: 'npmView' }
367
+ message: 'no successful npm view precedes this edit'
368
+ enforce: advise
369
+ \`\`\`
370
+
371
+ The precedent example proves only that an observed successful Bash call starts with npm view;
372
+ it does not prove that the lookup concerns the dependency being edited. The commit surface has
373
+ no transcript and therefore skips this example by its explicit supply policy.
374
+
205
375
  **Write the regex yourself — the user states the promise, you author the pattern.** The
206
376
  pattern is the part users find hardest, so never hand the prose back and ask for one. Three
207
377
  authoring traps, each measured on a live config:
208
378
 
209
379
  - **A pattern answers a syntactic question only.** "Is this string a forbidden word" is
210
380
  syntax; "is this a new dependency version" is meaning, and a regex leaks both ways on a
211
- semantic question. When the question is semantic, narrow \`in:\` to the files where any
212
- match IS a break (\`in:\`/\`except:\` scope \`forbid\` and \`requirePrecedent\` only), or
213
- accept "editing this file at all" as the trigger.
214
- - **\`^\` silently disarms on the delta axis.** \`forbid\` scans whole file content as one
215
- string, so a line-start anchor matches the first line only write \`(^|\\n)\` there.
216
- \`forbidCommand\` judges per line and the whole string, so \`^\` is safe on that axis.
381
+ semantic question. When the question is semantic, narrow the declaration's own \`scope\`
382
+ block to the files where any match IS a break, or accept "editing this file at all" as
383
+ the trigger.
384
+ - **\`^\` means what the preceding step left.** After a \`lines\` step a declaration's
385
+ pattern sees one line at a time, so \`^\` anchors to that line; over an unsplit source it
386
+ anchors to the whole text and matches the first line only. A ban over the command line
387
+ puts \`lines\` before its \`matches\` for exactly that reason.
217
388
  - **Author both directions.** Before registering, write down one string the pattern must
218
- match and one nearby string it must not (\`forbid\` vs \`forbidden\`, a flag vs its
389
+ match and one nearby string it must not (\`only(\` vs \`only_helper(\`, a flag vs its
219
390
  substring). A pattern checked in only the breaking direction over-fires in review-proof
220
391
  ways.
221
392
 
@@ -223,27 +394,10 @@ authoring traps, each measured on a live config:
223
394
 
224
395
  A draft is prose with a handle: \`id\`, \`why\`, and the literal marker \`draft: true\` — no other
225
396
  keys. It produces no judgment and no telemetry; \`pdks explain\` lists it as unpromoted.
226
- Record the SHAPE of the promise inside \`why\`, so the promotion destination is already
227
- written down when a later engine can express it. Name the shape in these terms:
228
-
229
- | Shape | The promise reads like |
230
- | --- | --- |
231
- | pairing | every element of set A has a counterpart in set B (translation keys, i18n) |
232
- | companion | if X appears in a unit, Y must appear with it |
233
- | ordered | a sequence must keep its order (migration journals, version ladders) |
234
- | fingerprint | a derived artifact must match the hash/stamp of its source |
235
- | producer-owned | only a designated generator may write this artifact |
236
- | self-absolution | the party being judged must not write its own verdict field |
237
- | actor-scope | the same action is fine for one actor and a break for another |
238
- | phase-order | several precedents, in a fixed order |
239
- | turn-locality | the evidence must be in the same turn or time window |
240
- | stated-ground | the reason must be written down before the action |
241
- | controlled-vocabulary | only an enumerated set of words/values is allowed |
242
- | naming-convention | names must match a pattern per kind |
243
- | irreversible-marker | once present, a marker may never be removed |
244
- | delegation-scope | a delegated task may touch only its granted scope |
245
- | scope-valve | a defined exception valve, judged rather than ad hoc |
246
- | claim-verification | the claim must be re-run/measured, not trusted |
397
+ Record the intended promise and the exact missing capability inside \`why\`. Do not classify
398
+ pairing, vocabulary, or history promises as drafts merely because they are absent from a short
399
+ example list. Check the catalogue, extraction steps, and observation channel first. A reserved
400
+ \`delegated-scope\` declaration cannot be registered as a judged entry.
247
401
 
248
402
  \`\`\`yaml
249
403
  languages:
@@ -251,30 +405,38 @@ languages:
251
405
  productionGlob: 'src/**'
252
406
  testCmd: 'echo "set a verification command for {scope}"'
253
407
  disciplines:
254
- - id: 'locale-files-move-together'
255
- why: 'pairing en.json and ko.json must change in the same commit; one side alone is a break'
408
+ - id: 'benchmark-supports-performance-claim'
409
+ why: 'a performance claim needs a fresh benchmark run during judgment; the engine cannot execute it'
256
410
  draft: true
257
411
  \`\`\`
258
412
 
259
413
  ### 5. Prove it fires, then close
260
414
 
261
- Run \`pdks explain\` and confirm the new entry is listed (a judged entry with its family and
262
- surfaces; a draft as unpromoted).
415
+ Run \`pdks explain\` and confirm the new entry is listed (a judged entry with its mechanism
416
+ and surfaces; a draft as unpromoted).
263
417
 
264
418
  For a judged entry, registration is not the finish — a pattern that never fires protects
265
- nothing while looking installed. Fire it once for real, with the proof run its family can
266
- actually reach:
419
+ nothing while looking installed. Fire it once for real, with the proof run the declaration's
420
+ own mechanism can actually reach:
267
421
 
268
- | Family | Break it once | The entry's id shows up in |
422
+ | Mechanism | Break it once | The entry's id shows up in |
269
423
  | --- | --- | --- |
270
- | \`forbid\` / \`immutable\` | one scratch edit matching the must-match direction | \`pdks covenant check --worktree\` output — the exit stays 0 at advise, the id is the proof |
271
- | \`forbidCommand\` | run one harmless command matching the pattern | the telemetry log tail — at advise the call proceeds and its row records the id |
272
- | \`requirePrecedent\` | one in-scope edit made without the required precedent | the telemetry log tail — this family judges on the session surface only (the commit surface records it \`skipped\`) |
424
+ | a file-reading one (\`added-only\`, \`naming\`, …) | one scratch edit matching the must-match direction | \`pdks covenant check --worktree\` output — the exit stays 0 at advise, the id is the proof |
425
+ | \`forbidden-command\` | run one harmless command matching the pattern | the telemetry log tail — at advise the call proceeds and its row records the id |
426
+ | \`precedent\` | one in-scope edit made without the required precedent | the telemetry log tail — a declaration reading the session judges on the session surface only (the commit surface has none, so its \`supply\` policy records it \`skipped\`) |
273
427
 
274
- Then undo the scratch break, repeat the same run, and confirm silence on the
275
- must-NOT-match direction. Close by telling the user which rung the entry landed on and
428
+ Then undo the scratch break, repeat the same observation, and confirm a passing row for the
429
+ must-NOT-match case. Silence alone may mean a scope miss, unchanged files, or unavailable evidence;
430
+ check \`pdks explain\` and telemetry for \`config-fault\`, \`no-observation\`, or \`supply-pass\`. Close by telling the user which rung the entry landed on and
276
431
  that \`enforce: block\` is theirs to add later if the advise record earns it.
277
432
 
433
+ ## Updating this skill without losing local edits
434
+
435
+ An upgrade does not overwrite an existing skill; rerunning \`pdks init claude-code\` reports it
436
+ skipped. Generate a fresh copy in a disposable project using the installed package, compare it
437
+ with this file, and merge the changes you want. Keep a backup of local additions. Do not delete
438
+ the existing skill to force regeneration in the working project.
439
+
278
440
  ## Reading the advise record
279
441
 
280
442
  An \`advised\` row means a promise was broken and the call went through anyway. Rows land in
@@ -419,5 +581,6 @@ export function initClaudeCode(spec) {
419
581
  // a failure costs least.
420
582
  writeIfAbsent(spec.projectRoot, DISCOVERY_RELATIVE, GENERATED_DISCOVERY, report);
421
583
  writeIfAbsent(spec.projectRoot, SKILL_RELATIVE, GENERATED_SKILL, report);
584
+ retargetGrokHookCommandToClaude(spec.projectRoot);
422
585
  return report;
423
586
  }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * `initGrok` — the Grok session-surface installer.
3
+ *
4
+ * Preflight first, then the shared project-side scaffold, then the generated hook and the
5
+ * `.grok/hooks` JSON registration. When a Claude delegator is already on disk, the JSON
6
+ * command names that file instead of planting a second one — two command strings would
7
+ * spawn two judges per call.
8
+ *
9
+ * Nothing existing is overwritten, with one command-field exception: a grok JSON whose
10
+ * `command` is still the grok-mjs string is rewritten to the Claude-hook command when that
11
+ * Claude file is on disk — otherwise grok-then-claude leaves two command strings. Any entry
12
+ * naming the Claude-hook command also takes the matcher of the `.claude/settings.json` entry
13
+ * carrying that command, because the host collapses the two registrations only when command
14
+ * and matcher are byte-identical; with no such entry the matcher stays. Timeout stays either
15
+ * way, and a command that is not the grok-mjs string is left byte-identical.
16
+ *
17
+ * Rules and skills are not copied; `.claude/settings.json` is not written.
18
+ */
19
+ import { type ScaffoldReport } from './scaffold-project.ts';
20
+ /** `initGrok` input — the target tree and the preflight seam. */
21
+ export type InitGrokSpec = {
22
+ /** Project root to install into — every write below is relative to it. */
23
+ projectRoot: string;
24
+ /**
25
+ * Preflight seam: throws when the package cannot be resolved from the given root.
26
+ * ABSENT uses the real resolution, anchored at that root and nowhere else — anchoring it
27
+ * at the installer's own module would answer for the installer's install graph rather
28
+ * than the target project's, which is precisely the case that must fail.
29
+ */
30
+ resolvePolydeukes?: (projectRoot: string) => void;
31
+ };
32
+ /**
33
+ * Rewrite the grok JSON `command` from the grok-mjs string to the Claude-hook string.
34
+ *
35
+ * The Claude installer calls this after writing its delegator; this installer calls it on
36
+ * re-run when that file is already on disk. Only the installer-generated grok-mjs command
37
+ * is rewritten; any other string is the consumer's spawn target and the file is not touched.
38
+ * Every entry naming the Claude-hook command — rewritten now or by an earlier install — takes
39
+ * the matcher the Claude settings file registered that command under, so the host sees one
40
+ * pair rather than two and a re-run converges. Parse failure leaves the file as it was —
41
+ * existence is presence, not parse success.
42
+ */
43
+ export declare function retargetGrokHookCommandToClaude(projectRoot: string): void;
44
+ /**
45
+ * Install the Grok session surface into `spec.projectRoot`, skipping whatever is already
46
+ * there and reporting both halves per artifact.
47
+ *
48
+ * Throws before any write when the package cannot be resolved from that root — that leaves
49
+ * zero files. Translating a throw into exit 2 with the install command is the bin's job.
50
+ */
51
+ export declare function initGrok(spec: InitGrokSpec): ScaffoldReport;