intentdna 1.2.2 → 1.3.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 (184) hide show
  1. package/.claude-plugin/hooks/hooks.json +12 -0
  2. package/.claude-plugin/plugin.json +5 -0
  3. package/dist/audit/index.d.ts +0 -1
  4. package/dist/audit/index.js +0 -1
  5. package/dist/cli/commands/compile.d.ts +0 -1
  6. package/dist/cli/commands/compile.js +0 -1
  7. package/dist/cli/commands/epigenetic.d.ts +0 -1
  8. package/dist/cli/commands/epigenetic.js +0 -1
  9. package/dist/cli/commands/evolve.d.ts +0 -1
  10. package/dist/cli/commands/evolve.js +0 -1
  11. package/dist/cli/commands/guard.d.ts +0 -1
  12. package/dist/cli/commands/guard.js +0 -3
  13. package/dist/cli/commands/import.d.ts +0 -1
  14. package/dist/cli/commands/import.js +0 -1
  15. package/dist/cli/commands/init.d.ts +0 -1
  16. package/dist/cli/commands/init.js +0 -1
  17. package/dist/cli/commands/run.d.ts +0 -1
  18. package/dist/cli/commands/run.js +4 -27
  19. package/dist/cli/commands/setup.d.ts +9 -0
  20. package/dist/cli/commands/setup.js +42 -0
  21. package/dist/cli/commands/show.d.ts +0 -1
  22. package/dist/cli/commands/show.js +0 -1
  23. package/dist/cli/commands/sync.d.ts +13 -1
  24. package/dist/cli/commands/sync.js +158 -33
  25. package/dist/cli/commands/validate.d.ts +0 -1
  26. package/dist/cli/commands/validate.js +0 -1
  27. package/dist/cli/commands/verify.d.ts +0 -1
  28. package/dist/cli/commands/verify.js +0 -1
  29. package/dist/cli/index.d.ts +0 -1
  30. package/dist/cli/index.js +9 -1
  31. package/dist/cli/util/format.d.ts +0 -1
  32. package/dist/cli/util/format.js +0 -1
  33. package/dist/cli/util/prompt.d.ts +0 -1
  34. package/dist/cli/util/prompt.js +0 -1
  35. package/dist/compiler/activate.d.ts +0 -1
  36. package/dist/compiler/activate.js +0 -1
  37. package/dist/compiler/cascade.d.ts +1 -3
  38. package/dist/compiler/cascade.js +1 -10
  39. package/dist/compiler/compile.d.ts +0 -1
  40. package/dist/compiler/compile.js +33 -17
  41. package/dist/compiler/index.d.ts +0 -1
  42. package/dist/compiler/index.js +0 -1
  43. package/dist/compiler/workflow.d.ts +0 -1
  44. package/dist/compiler/workflow.js +0 -2
  45. package/dist/evolution/collector.d.ts +0 -1
  46. package/dist/evolution/collector.js +0 -1
  47. package/dist/evolution/fitness.d.ts +0 -1
  48. package/dist/evolution/fitness.js +0 -1
  49. package/dist/evolution/index.d.ts +0 -1
  50. package/dist/evolution/index.js +0 -1
  51. package/dist/evolution/marker-gen.d.ts +0 -1
  52. package/dist/evolution/marker-gen.js +0 -1
  53. package/dist/evolution/store.d.ts +0 -1
  54. package/dist/evolution/store.js +0 -1
  55. package/dist/evolution/types.d.ts +0 -1
  56. package/dist/evolution/types.js +0 -1
  57. package/dist/hooks/cli.d.ts +17 -0
  58. package/dist/hooks/cli.js +156 -0
  59. package/dist/hooks/enforce.d.ts +106 -0
  60. package/dist/hooks/enforce.js +431 -0
  61. package/dist/hooks/index.d.ts +9 -0
  62. package/dist/hooks/index.js +12 -0
  63. package/dist/hooks/protocol.d.ts +73 -0
  64. package/dist/hooks/protocol.js +91 -0
  65. package/dist/hooks/state.d.ts +53 -0
  66. package/dist/hooks/state.js +93 -0
  67. package/dist/index.d.ts +0 -1
  68. package/dist/index.js +0 -1
  69. package/dist/log/logger.d.ts +0 -1
  70. package/dist/log/logger.js +0 -1
  71. package/dist/runtime/agent-md.d.ts +0 -1
  72. package/dist/runtime/agent-md.js +95 -105
  73. package/dist/runtime/claude-sdk.d.ts +0 -1
  74. package/dist/runtime/claude-sdk.js +0 -1
  75. package/dist/runtime/index.d.ts +3 -3
  76. package/dist/runtime/index.js +2 -2
  77. package/dist/runtime/markdown.d.ts +0 -1
  78. package/dist/runtime/markdown.js +0 -1
  79. package/dist/runtime/plugin-adapter.d.ts +42 -0
  80. package/dist/runtime/plugin-adapter.js +77 -0
  81. package/dist/runtime/settings-adapter.d.ts +8 -9
  82. package/dist/runtime/settings-adapter.js +57 -46
  83. package/dist/runtime/skill-adapter.d.ts +0 -1
  84. package/dist/runtime/skill-adapter.js +94 -71
  85. package/dist/runtime/workflow-runner.d.ts +0 -1
  86. package/dist/runtime/workflow-runner.js +0 -14
  87. package/dist/schema/index.d.ts +0 -1
  88. package/dist/schema/index.js +0 -1
  89. package/dist/schema/types.d.ts +33 -20
  90. package/dist/schema/types.js +0 -1
  91. package/dist/schema/validate.d.ts +0 -1
  92. package/dist/schema/validate.js +32 -19
  93. package/dist/schema/yaml-parser.d.ts +0 -1
  94. package/dist/schema/yaml-parser.js +0 -1
  95. package/dist/templates/code-review-pipeline.dna.yaml +0 -11
  96. package/dist/templates/flutter-behavior-lock.dna.yaml +0 -16
  97. package/dist/templates/flutter-refactoring-rescue.dna.yaml +0 -12
  98. package/dist/templates/flutter-rewrite.dna.yaml +0 -27
  99. package/dist/templates/full-pipeline.dna.yaml +0 -25
  100. package/dist/templates/multi-perspective-review.dna.yaml +0 -12
  101. package/dist/templates/subagent-parallel.dna.yaml +0 -16
  102. package/package.json +4 -2
  103. package/dist/audit/index.d.ts.map +0 -1
  104. package/dist/audit/index.js.map +0 -1
  105. package/dist/cli/commands/compile.d.ts.map +0 -1
  106. package/dist/cli/commands/compile.js.map +0 -1
  107. package/dist/cli/commands/epigenetic.d.ts.map +0 -1
  108. package/dist/cli/commands/epigenetic.js.map +0 -1
  109. package/dist/cli/commands/evolve.d.ts.map +0 -1
  110. package/dist/cli/commands/evolve.js.map +0 -1
  111. package/dist/cli/commands/guard.d.ts.map +0 -1
  112. package/dist/cli/commands/guard.js.map +0 -1
  113. package/dist/cli/commands/import.d.ts.map +0 -1
  114. package/dist/cli/commands/import.js.map +0 -1
  115. package/dist/cli/commands/init.d.ts.map +0 -1
  116. package/dist/cli/commands/init.js.map +0 -1
  117. package/dist/cli/commands/run.d.ts.map +0 -1
  118. package/dist/cli/commands/run.js.map +0 -1
  119. package/dist/cli/commands/show.d.ts.map +0 -1
  120. package/dist/cli/commands/show.js.map +0 -1
  121. package/dist/cli/commands/sync.d.ts.map +0 -1
  122. package/dist/cli/commands/sync.js.map +0 -1
  123. package/dist/cli/commands/validate.d.ts.map +0 -1
  124. package/dist/cli/commands/validate.js.map +0 -1
  125. package/dist/cli/commands/verify.d.ts.map +0 -1
  126. package/dist/cli/commands/verify.js.map +0 -1
  127. package/dist/cli/index.d.ts.map +0 -1
  128. package/dist/cli/index.js.map +0 -1
  129. package/dist/cli/util/format.d.ts.map +0 -1
  130. package/dist/cli/util/format.js.map +0 -1
  131. package/dist/cli/util/prompt.d.ts.map +0 -1
  132. package/dist/cli/util/prompt.js.map +0 -1
  133. package/dist/compiler/activate.d.ts.map +0 -1
  134. package/dist/compiler/activate.js.map +0 -1
  135. package/dist/compiler/cascade.d.ts.map +0 -1
  136. package/dist/compiler/cascade.js.map +0 -1
  137. package/dist/compiler/compile.d.ts.map +0 -1
  138. package/dist/compiler/compile.js.map +0 -1
  139. package/dist/compiler/index.d.ts.map +0 -1
  140. package/dist/compiler/index.js.map +0 -1
  141. package/dist/compiler/workflow.d.ts.map +0 -1
  142. package/dist/compiler/workflow.js.map +0 -1
  143. package/dist/evolution/collector.d.ts.map +0 -1
  144. package/dist/evolution/collector.js.map +0 -1
  145. package/dist/evolution/fitness.d.ts.map +0 -1
  146. package/dist/evolution/fitness.js.map +0 -1
  147. package/dist/evolution/index.d.ts.map +0 -1
  148. package/dist/evolution/index.js.map +0 -1
  149. package/dist/evolution/marker-gen.d.ts.map +0 -1
  150. package/dist/evolution/marker-gen.js.map +0 -1
  151. package/dist/evolution/store.d.ts.map +0 -1
  152. package/dist/evolution/store.js.map +0 -1
  153. package/dist/evolution/types.d.ts.map +0 -1
  154. package/dist/evolution/types.js.map +0 -1
  155. package/dist/index.d.ts.map +0 -1
  156. package/dist/index.js.map +0 -1
  157. package/dist/log/logger.d.ts.map +0 -1
  158. package/dist/log/logger.js.map +0 -1
  159. package/dist/runtime/agent-md.d.ts.map +0 -1
  160. package/dist/runtime/agent-md.js.map +0 -1
  161. package/dist/runtime/claude-sdk.d.ts.map +0 -1
  162. package/dist/runtime/claude-sdk.js.map +0 -1
  163. package/dist/runtime/hooks.d.ts +0 -67
  164. package/dist/runtime/hooks.d.ts.map +0 -1
  165. package/dist/runtime/hooks.js +0 -785
  166. package/dist/runtime/hooks.js.map +0 -1
  167. package/dist/runtime/index.d.ts.map +0 -1
  168. package/dist/runtime/index.js.map +0 -1
  169. package/dist/runtime/markdown.d.ts.map +0 -1
  170. package/dist/runtime/markdown.js.map +0 -1
  171. package/dist/runtime/settings-adapter.d.ts.map +0 -1
  172. package/dist/runtime/settings-adapter.js.map +0 -1
  173. package/dist/runtime/skill-adapter.d.ts.map +0 -1
  174. package/dist/runtime/skill-adapter.js.map +0 -1
  175. package/dist/runtime/workflow-runner.d.ts.map +0 -1
  176. package/dist/runtime/workflow-runner.js.map +0 -1
  177. package/dist/schema/index.d.ts.map +0 -1
  178. package/dist/schema/index.js.map +0 -1
  179. package/dist/schema/types.d.ts.map +0 -1
  180. package/dist/schema/types.js.map +0 -1
  181. package/dist/schema/validate.d.ts.map +0 -1
  182. package/dist/schema/validate.js.map +0 -1
  183. package/dist/schema/yaml-parser.d.ts.map +0 -1
  184. package/dist/schema/yaml-parser.js.map +0 -1
@@ -138,4 +138,3 @@ export function activateDNA(cascaded, contextName = null, activeRole = null) {
138
138
  source_ids: cascaded.source_ids,
139
139
  };
140
140
  }
141
- //# sourceMappingURL=activate.js.map
@@ -10,12 +10,11 @@
10
10
  * - attract/repel: same target accumulates, different targets independent
11
11
  * - amplify/suppress: factors multiply (1.5x × 2.0x = 3.0x)
12
12
  */
13
- import type { IntentDNA, Gene, ContextRegion, EpigeneticMarker, RoleDef, WorkflowDef, ModeDef } from "../schema/types.js";
13
+ import type { IntentDNA, Gene, ContextRegion, EpigeneticMarker, RoleDef, WorkflowDef } from "../schema/types.js";
14
14
  export interface CascadedDNA {
15
15
  genes: Record<string, Gene>;
16
16
  contexts: Record<string, ContextRegion>;
17
17
  roles: Record<string, RoleDef>;
18
- modes: Record<string, ModeDef>;
19
18
  workflows: Record<string, WorkflowDef>;
20
19
  epigenetic_markers: EpigeneticMarker[];
21
20
  source_ids: string[];
@@ -31,4 +30,3 @@ export interface CascadedDNA {
31
30
  * - Same namespace: higher priority replaces (expected cascade behavior)
32
31
  */
33
32
  export declare function cascadeDNA(layers: IntentDNA[]): CascadedDNA;
34
- //# sourceMappingURL=cascade.d.ts.map
@@ -81,7 +81,6 @@ function prefixWorkflowRoles(wf, ns) {
81
81
  steps: wf.steps.map((step) => ({
82
82
  ...step,
83
83
  role: `${ns}_${step.role}`,
84
- mode: step.mode ? `${ns}_${step.mode}` : undefined,
85
84
  })),
86
85
  };
87
86
  }
@@ -101,7 +100,6 @@ export function cascadeDNA(layers) {
101
100
  const mergedGenes = {};
102
101
  const mergedContexts = {};
103
102
  const mergedRoles = {};
104
- const mergedModes = {};
105
103
  const mergedWorkflows = {};
106
104
  const allMarkers = [];
107
105
  const sourceIds = [];
@@ -126,12 +124,7 @@ export function cascadeDNA(layers) {
126
124
  const key = ns ? `${ns}_${name}` : name;
127
125
  mergedRoles[key] = role;
128
126
  }
129
- // Merge modes: namespace-prefixed keys coexist, same-namespace overrides
130
- for (const [name, mode] of Object.entries(dna.modes ?? {})) {
131
- const key = ns ? `${ns}_${name}` : name;
132
- mergedModes[key] = mode;
133
- }
134
- // Merge workflows: namespace-prefixed keys + step role/mode references
127
+ // Merge workflows: namespace-prefixed keys + step role references
135
128
  const normalized = normalizeWorkflowsFromDNA(dna);
136
129
  for (const [name, wf] of Object.entries(normalized)) {
137
130
  if (ns) {
@@ -151,10 +144,8 @@ export function cascadeDNA(layers) {
151
144
  genes: mergedGenes,
152
145
  contexts: mergedContexts,
153
146
  roles: mergedRoles,
154
- modes: mergedModes,
155
147
  workflows: mergedWorkflows,
156
148
  epigenetic_markers: allMarkers,
157
149
  source_ids: sourceIds,
158
150
  };
159
151
  }
160
- //# sourceMappingURL=cascade.js.map
@@ -14,4 +14,3 @@ import type { CascadedDNA } from "./cascade.js";
14
14
  * Compile activated DNA into a Constraint IR.
15
15
  */
16
16
  export declare function compileDNA(activated: ActivatedDNA, cascaded?: CascadedDNA): ConstraintIR;
17
- //# sourceMappingURL=compile.d.ts.map
@@ -198,32 +198,49 @@ export function compileDNA(activated, cascaded) {
198
198
  }
199
199
  }
200
200
  }
201
- // Build mode_constraints from all cascaded modes
202
- const modeConstraints = [];
203
- if (cascaded?.modes) {
204
- for (const [name, mode] of Object.entries(cascaded.modes)) {
205
- modeConstraints.push({
206
- mode_name: name,
207
- description: mode.description,
208
- tool_deny: mode.tool_permissions?.deny,
209
- scope_write: mode.scope?.write,
210
- });
211
- }
212
- }
213
- // Build step_checkpoints from all workflow steps that have checkpoints + a mode
201
+ // Build step_checkpoints from all workflow steps that have checkpoints
214
202
  const stepCheckpoints = [];
215
203
  const allWorkflows = Object.values(cascaded?.workflows ?? {});
216
204
  for (const wf of allWorkflows) {
217
205
  for (const step of wf.steps ?? []) {
218
- if (step.checkpoints && step.checkpoints.length > 0 && step.mode) {
206
+ if (step.checkpoints && step.checkpoints.length > 0) {
219
207
  stepCheckpoints.push({
220
- step_mode: step.mode,
208
+ step_role: step.role,
221
209
  step_id: step.id,
222
210
  checkpoints: [...step.checkpoints],
223
211
  });
224
212
  }
225
213
  }
226
214
  }
215
+ // Build workflows_ir: per-workflow partitioned view with namespace context
216
+ const workflowsIR = [];
217
+ if (cascaded?.workflows) {
218
+ for (const [wfKey, wf] of Object.entries(cascaded.workflows)) {
219
+ const wfStepCheckpoints = [];
220
+ const activeRoles = [];
221
+ for (const step of wf.steps ?? []) {
222
+ if (!activeRoles.includes(step.role)) {
223
+ activeRoles.push(step.role);
224
+ }
225
+ if (step.checkpoints && step.checkpoints.length > 0) {
226
+ wfStepCheckpoints.push({
227
+ step_role: step.role,
228
+ step_id: step.id,
229
+ checkpoints: [...step.checkpoints],
230
+ });
231
+ }
232
+ }
233
+ // Derive namespace from workflow key: "ns_wfname" → "ns", "wfname" → ""
234
+ const underscoreIdx = wfKey.indexOf("_");
235
+ const namespace = underscoreIdx > 0 ? wfKey.slice(0, underscoreIdx) : "";
236
+ workflowsIR.push({
237
+ workflow_name: wfKey,
238
+ namespace,
239
+ step_checkpoints: wfStepCheckpoints,
240
+ active_roles: activeRoles,
241
+ });
242
+ }
243
+ }
227
244
  // Sort directives by priority
228
245
  const priorityOrder = { high: 0, medium: 1, low: 2 };
229
246
  directives.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
@@ -240,8 +257,7 @@ export function compileDNA(activated, cascaded) {
240
257
  role_tool_permissions: roleToolPermissions,
241
258
  role_scope: roleScope,
242
259
  roles_scope_map: rolesScopeMap.length > 0 ? rolesScopeMap : undefined,
243
- mode_constraints: modeConstraints.length > 0 ? modeConstraints : undefined,
244
260
  step_checkpoints: stepCheckpoints.length > 0 ? stepCheckpoints : undefined,
261
+ workflows_ir: workflowsIR.length > 0 ? workflowsIR : undefined,
245
262
  };
246
263
  }
247
- //# sourceMappingURL=compile.js.map
@@ -25,4 +25,3 @@ export interface CompileOptions {
25
25
  * Optionally injects stored epigenetic markers from the evolution engine.
26
26
  */
27
27
  export declare function compileFromFiles(filePaths: string[], contextOrOptions?: string | CompileOptions): Promise<ConstraintIR>;
28
- //# sourceMappingURL=index.d.ts.map
@@ -48,4 +48,3 @@ export async function compileFromFiles(filePaths, contextOrOptions) {
48
48
  const activated = activateDNA(cascaded, opts.context ?? null, opts.role ?? null);
49
49
  return compileDNA(activated, cascaded);
50
50
  }
51
- //# sourceMappingURL=index.js.map
@@ -34,4 +34,3 @@ export interface CompileWorkflowResult {
34
34
  * Returns a result object with either a plan or errors.
35
35
  */
36
36
  export declare function compileWorkflow(workflow: WorkflowDef, options?: CompileWorkflowOptions): CompileWorkflowResult;
37
- //# sourceMappingURL=workflow.d.ts.map
@@ -166,7 +166,6 @@ function toWorkflowStep(def) {
166
166
  return {
167
167
  id: def.id,
168
168
  role: def.role,
169
- mode: def.mode ?? null,
170
169
  description: def.description ?? def.id,
171
170
  optional: def.optional ?? false,
172
171
  run_if: resolveRunIf(def.run_if),
@@ -273,4 +272,3 @@ export function compileWorkflow(workflow, options) {
273
272
  }
274
273
  return { ok: true, plan, errors: [] };
275
274
  }
276
- //# sourceMappingURL=workflow.js.map
@@ -49,4 +49,3 @@ export interface OutcomeSummary {
49
49
  oldest: string | null;
50
50
  newest: string | null;
51
51
  }
52
- //# sourceMappingURL=collector.d.ts.map
@@ -69,4 +69,3 @@ export class OutcomeCollector {
69
69
  };
70
70
  }
71
71
  }
72
- //# sourceMappingURL=collector.js.map
@@ -30,4 +30,3 @@ export declare function computeModulationFactor(geneName: string, outcomes: Exec
30
30
  * Returns 0-1 where 0 means fully decayed.
31
31
  */
32
32
  export declare function computeMarkerStrength(marker: EpigeneticMarker): number;
33
- //# sourceMappingURL=fitness.d.ts.map
@@ -93,4 +93,3 @@ export function computeMarkerStrength(marker) {
93
93
  // Decay per 30-day period
94
94
  return Math.pow(marker.decay, markerAge / 30);
95
95
  }
96
- //# sourceMappingURL=fitness.js.map
@@ -50,4 +50,3 @@ export declare class EvolutionEngine {
50
50
  */
51
51
  summary(dnaId: string): Promise<OutcomeSummary>;
52
52
  }
53
- //# sourceMappingURL=index.d.ts.map
@@ -66,4 +66,3 @@ export class EvolutionEngine {
66
66
  return this.collector.getSummary(dnaId);
67
67
  }
68
68
  }
69
- //# sourceMappingURL=index.js.map
@@ -28,4 +28,3 @@ export interface MarkerChange {
28
28
  feedback_count: number;
29
29
  needed?: number;
30
30
  }
31
- //# sourceMappingURL=marker-gen.d.ts.map
@@ -138,4 +138,3 @@ export function describeMarkerChanges(outcomes, existingMarkers, config = {}) {
138
138
  }
139
139
  return changes;
140
140
  }
141
- //# sourceMappingURL=marker-gen.js.map
@@ -21,4 +21,3 @@ export declare class EvolutionStore {
21
21
  loadMarkers(dnaId: string): Promise<EpigeneticMarker[]>;
22
22
  saveMarkers(dnaId: string, markers: EpigeneticMarker[]): Promise<void>;
23
23
  }
24
- //# sourceMappingURL=store.d.ts.map
@@ -75,4 +75,3 @@ export class EvolutionStore {
75
75
  await writeFile(this.markersPath(dnaId), JSON.stringify(markers, null, 2), "utf-8");
76
76
  }
77
77
  }
78
- //# sourceMappingURL=store.js.map
@@ -70,4 +70,3 @@ export interface EvolutionConfig {
70
70
  max_markers: number;
71
71
  }
72
72
  export declare const DEFAULT_EVOLUTION_CONFIG: EvolutionConfig;
73
- //# sourceMappingURL=types.d.ts.map
@@ -11,4 +11,3 @@ export const DEFAULT_EVOLUTION_CONFIG = {
11
11
  prune_threshold: 0.01,
12
12
  max_markers: 50,
13
13
  };
14
- //# sourceMappingURL=types.js.map
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Intent DNA — Hook CLI (`dna-hook`)
4
+ *
5
+ * Claude Code invokes this binary for each hook event:
6
+ * dna-hook <event> [--ir <path>] [--state-dir <path>]
7
+ *
8
+ * Runtime flow:
9
+ * 1. Read JSON from stdin (Claude Code hook protocol)
10
+ * 2. Load compiled IR from .dna/compiled/ir.json
11
+ * 3. Read state from .dna/state/
12
+ * 4. Call enforcement engine
13
+ * 5. Write JSON result to stdout
14
+ *
15
+ * Fail-open: all errors → { continue: true, suppressOutput: true }
16
+ */
17
+ export {};
@@ -0,0 +1,156 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Intent DNA — Hook CLI (`dna-hook`)
4
+ *
5
+ * Claude Code invokes this binary for each hook event:
6
+ * dna-hook <event> [--ir <path>] [--state-dir <path>]
7
+ *
8
+ * Runtime flow:
9
+ * 1. Read JSON from stdin (Claude Code hook protocol)
10
+ * 2. Load compiled IR from .dna/compiled/ir.json
11
+ * 3. Read state from .dna/state/
12
+ * 4. Call enforcement engine
13
+ * 5. Write JSON result to stdout
14
+ *
15
+ * Fail-open: all errors → { continue: true, suppressOutput: true }
16
+ */
17
+ import { readFile } from "node:fs/promises";
18
+ import { resolve } from "node:path";
19
+ import { readStdin, writeOutput, silentOutput } from "./protocol.js";
20
+ import { enforcePreToolUse, enforcePostToolUse, enforceUserPromptSubmit, enforceSubagentStop, enforcePreCompact, enforceNotification, enforceSessionStart, enforceStop, } from "./enforce.js";
21
+ import { appendAudit, readWorkflowState } from "./state.js";
22
+ // ── Constants ──────────────────────────────────────────────
23
+ const DEFAULT_IR_PATH = ".dna/compiled/ir.json";
24
+ const VALID_EVENTS = new Set([
25
+ "PreToolUse", "PostToolUse", "UserPromptSubmit",
26
+ "SubagentStop", "PreCompact", "Notification", "Stop", "SessionStart",
27
+ ]);
28
+ // ── Main ───────────────────────────────────────────────────
29
+ async function main() {
30
+ // Parse args
31
+ const args = process.argv.slice(2);
32
+ const event = args[0];
33
+ if (!event || !VALID_EVENTS.has(event)) {
34
+ writeOutput(silentOutput());
35
+ return;
36
+ }
37
+ let irPath = DEFAULT_IR_PATH;
38
+ const irFlagIdx = args.indexOf("--ir");
39
+ if (irFlagIdx !== -1 && args[irFlagIdx + 1]) {
40
+ irPath = args[irFlagIdx + 1];
41
+ }
42
+ // Read stdin
43
+ const rawInput = await readStdin(5000);
44
+ // Resolve project directory from input or cwd
45
+ const projectDir = typeof rawInput.cwd === "string" ? rawInput.cwd : process.cwd();
46
+ const sessionId = typeof rawInput.sessionId === "string" ? rawInput.sessionId : undefined;
47
+ // Load compiled IR
48
+ const resolvedIRPath = resolve(projectDir, irPath);
49
+ const ir = await loadIR(resolvedIRPath);
50
+ if (!ir) {
51
+ writeOutput(silentOutput());
52
+ return;
53
+ }
54
+ const state = {};
55
+ // Special handling for Stop — needs async workflow state read
56
+ if (event === "Stop") {
57
+ const wfState = await readWorkflowState(projectDir, sessionId);
58
+ const stopContext = wfState ? {
59
+ active: wfState.active,
60
+ workflow: wfState.workflow,
61
+ current_step: wfState.current_step,
62
+ current_role: wfState.current_role,
63
+ started_at: wfState.started_at,
64
+ } : null;
65
+ const stopOutput = enforceStop(ir, {
66
+ cwd: typeof rawInput.cwd === "string" ? rawInput.cwd : undefined,
67
+ session_id: sessionId,
68
+ stop_reason: typeof rawInput.stop_reason === "string" ? rawInput.stop_reason : undefined,
69
+ }, stopContext);
70
+ writeOutput(stopOutput);
71
+ return;
72
+ }
73
+ // Dispatch to enforcement engine
74
+ const output = dispatch(event, ir, rawInput, state);
75
+ writeOutput(output);
76
+ // Side effect: audit log for notifications
77
+ if (event === "Notification" && output.hookSpecificOutput?.additionalContext?.includes("violation")) {
78
+ const message = typeof rawInput.message === "string" ? rawInput.message : "";
79
+ await appendAudit(projectDir, {
80
+ timestamp: new Date().toISOString(),
81
+ event: "violation",
82
+ message,
83
+ session_id: sessionId,
84
+ }).catch(() => { }); // Fail-open
85
+ }
86
+ }
87
+ // ── Dispatch ───────────────────────────────────────────────
88
+ function dispatch(event, ir, input, state) {
89
+ switch (event) {
90
+ case "PreToolUse":
91
+ return enforcePreToolUse(ir, {
92
+ tool_name: String(input.tool_name ?? ""),
93
+ tool_input: (input.tool_input ?? {}),
94
+ agent_type: typeof input.agent_type === "string" ? input.agent_type : undefined,
95
+ cwd: typeof input.cwd === "string" ? input.cwd : undefined,
96
+ sessionId: typeof input.sessionId === "string" ? input.sessionId : undefined,
97
+ }, state);
98
+ case "PostToolUse":
99
+ return enforcePostToolUse(ir, {
100
+ tool_name: String(input.tool_name ?? ""),
101
+ tool_input: (input.tool_input ?? {}),
102
+ tool_output: typeof input.tool_output === "string" ? input.tool_output : undefined,
103
+ agent_type: typeof input.agent_type === "string" ? input.agent_type : undefined,
104
+ });
105
+ case "UserPromptSubmit":
106
+ return enforceUserPromptSubmit(ir, {
107
+ prompt: typeof input.prompt === "string" ? input.prompt : undefined,
108
+ });
109
+ case "SubagentStop":
110
+ return enforceSubagentStop(ir, {
111
+ agent_name: typeof input.agent_name === "string" ? input.agent_name : undefined,
112
+ agent_type: typeof input.agent_type === "string" ? input.agent_type : undefined,
113
+ });
114
+ case "PreCompact":
115
+ return enforcePreCompact(ir);
116
+ case "Notification":
117
+ return enforceNotification(ir, {
118
+ title: typeof input.title === "string" ? input.title : undefined,
119
+ message: typeof input.message === "string" ? input.message : undefined,
120
+ });
121
+ case "Stop":
122
+ return silentOutput(); // DNA doesn't block stops
123
+ case "SessionStart":
124
+ return enforceSessionStart(ir, {
125
+ cwd: typeof input.cwd === "string" ? input.cwd : undefined,
126
+ session_id: typeof input.sessionId === "string" ? input.sessionId : undefined,
127
+ trigger: typeof input.trigger === "string" ? input.trigger : undefined,
128
+ });
129
+ default:
130
+ return silentOutput();
131
+ }
132
+ }
133
+ // ── IR Loading ─────────────────────────────────────────────
134
+ async function loadIR(irPath) {
135
+ try {
136
+ const raw = await readFile(irPath, "utf-8");
137
+ const data = JSON.parse(raw);
138
+ // Support both raw IR and wrapped CompiledIRFile format
139
+ if (data.ir_version && data.ir) {
140
+ return data.ir;
141
+ }
142
+ // Direct ConstraintIR
143
+ if (data.compiled_at && data.source_dna_ids) {
144
+ return data;
145
+ }
146
+ return null;
147
+ }
148
+ catch {
149
+ return null;
150
+ }
151
+ }
152
+ // ── Entry Point ────────────────────────────────────────────
153
+ main().catch(() => {
154
+ // Fail-open: never block Claude Code on unexpected errors
155
+ writeOutput(silentOutput());
156
+ });
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Intent DNA — Enforcement Engine
3
+ *
4
+ * Pure functions that evaluate ConstraintIR against hook inputs.
5
+ * No I/O — reads no files, runs no commands, writes no state.
6
+ * The hook CLI handles stdin/stdout, IR loading, state, and checkpoint execution.
7
+ *
8
+ * Enforcement order for PreToolUse:
9
+ * 1. Role scope (agent_type → write glob patterns)
10
+ * 2. Tool filters (block/warn/deprioritize by tool name)
11
+ * 3. Pre-execution gates (condition-based block/escalate)
12
+ * 4. Output schema (required sections for Write tool)
13
+ *
14
+ * Step checkpoints are handled by the CLI, not here.
15
+ */
16
+ import type { ConstraintIR, RoleDef } from "../schema/types.js";
17
+ import type { PreToolUseInput, PostToolUseInput, UserPromptSubmitInput, SubagentStopInput, NotificationInput, HookOutput } from "./protocol.js";
18
+ /** SessionStart input fields */
19
+ export interface SessionStartInput {
20
+ cwd?: string;
21
+ session_id?: string;
22
+ trigger?: string;
23
+ }
24
+ export interface EnforceState {
25
+ }
26
+ /**
27
+ * Enforce PreToolUse constraints.
28
+ * Returns the first blocking/escalating result, or allow if all pass.
29
+ *
30
+ * Optional `roles` parameter provides output_schema enforcement.
31
+ */
32
+ export declare function enforcePreToolUse(ir: ConstraintIR, input: PreToolUseInput, state?: EnforceState, roles?: Record<string, RoleDef>): HookOutput;
33
+ /**
34
+ * Enforce PostToolUse validators.
35
+ * Currently logs validators for audit. Returns allow with context.
36
+ */
37
+ export declare function enforcePostToolUse(ir: ConstraintIR, input: PostToolUseInput): HookOutput;
38
+ /**
39
+ * Inject DNA policy reminders on each user prompt.
40
+ * Only injects when there are high-priority directives.
41
+ */
42
+ export declare function enforceUserPromptSubmit(ir: ConstraintIR, _input: UserPromptSubmitInput): HookOutput;
43
+ /**
44
+ * Audit role scope compliance when a subagent finishes.
45
+ */
46
+ export declare function enforceSubagentStop(ir: ConstraintIR, input: SubagentStopInput): HookOutput;
47
+ /**
48
+ * Preserve DNA directive summary before context compaction.
49
+ */
50
+ export declare function enforcePreCompact(ir: ConstraintIR): HookOutput;
51
+ /**
52
+ * Check if a notification is a DNA violation and return audit info.
53
+ * The CLI handles actual audit file writes.
54
+ */
55
+ export declare function enforceNotification(ir: ConstraintIR, input: NotificationInput): HookOutput;
56
+ /**
57
+ * Initialize DNA state and inject policy summary on session start.
58
+ * Returns silent if no DNA policies are active.
59
+ */
60
+ export declare function enforceSessionStart(ir: ConstraintIR, input: SessionStartInput): HookOutput;
61
+ /** Stop enforcement input — includes workflow awareness */
62
+ export interface StopEnforceInput {
63
+ cwd?: string;
64
+ session_id?: string;
65
+ stop_reason?: string;
66
+ }
67
+ /** Workflow state context for stop enforcement */
68
+ export interface StopWorkflowContext {
69
+ active: boolean;
70
+ workflow: string;
71
+ current_step: string;
72
+ current_role: string;
73
+ started_at: string;
74
+ }
75
+ /**
76
+ * Enforce Stop hook — verify workflow checkpoint completion.
77
+ *
78
+ * Safety valves (NEVER block):
79
+ * - context_limit / context_window stops
80
+ * - Stale workflow state (>2h)
81
+ * - No active workflow
82
+ * - No checkpoints defined
83
+ *
84
+ * Block when:
85
+ * - Workflow active + unmet checkpoints for current step
86
+ */
87
+ export declare function enforceStop(ir: ConstraintIR, input: StopEnforceInput, workflowState: StopWorkflowContext | null): HookOutput;
88
+ /** Check if a file path is allowed by a list of write globs (prefix matching). */
89
+ export declare function checkWriteAllowed(filePath: string, allowedGlobs: string[]): boolean;
90
+ /**
91
+ * Extract directory prefix from a glob pattern.
92
+ * "src/**\/*.ts" → "src/"
93
+ * "test/**" → "test/"
94
+ * "*.md" → "" (root)
95
+ * "package.json" → "package.json" (exact path)
96
+ */
97
+ export declare function globToPrefix(glob: string): string;
98
+ /**
99
+ * Evaluate a gate condition against a PreToolUse input.
100
+ * Returns true if the condition triggers, false if not, null if too complex.
101
+ */
102
+ export declare function evaluateGateCondition(condition: string, input: PreToolUseInput): boolean | null;
103
+ /**
104
+ * Resolve a tool filter target to concrete Claude Code tool names.
105
+ */
106
+ export declare function resolveToolTarget(target?: string): string[] | null;