@dewtech/dare-cli 3.5.0 → 3.7.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 (202) hide show
  1. package/dist/__tests__/ide-adapters.test.d.ts +2 -0
  2. package/dist/__tests__/ide-adapters.test.d.ts.map +1 -0
  3. package/dist/__tests__/ide-adapters.test.js +39 -0
  4. package/dist/__tests__/ide-adapters.test.js.map +1 -0
  5. package/dist/__tests__/ide-command-parity.test.js +3 -0
  6. package/dist/__tests__/ide-command-parity.test.js.map +1 -1
  7. package/dist/__tests__/patterns-no-llm.test.d.ts +2 -0
  8. package/dist/__tests__/patterns-no-llm.test.d.ts.map +1 -0
  9. package/dist/__tests__/patterns-no-llm.test.js +31 -0
  10. package/dist/__tests__/patterns-no-llm.test.js.map +1 -0
  11. package/dist/__tests__/patterns-path-confinement.test.d.ts +2 -0
  12. package/dist/__tests__/patterns-path-confinement.test.d.ts.map +1 -0
  13. package/dist/__tests__/patterns-path-confinement.test.js +58 -0
  14. package/dist/__tests__/patterns-path-confinement.test.js.map +1 -0
  15. package/dist/__tests__/patterns.test.d.ts +2 -0
  16. package/dist/__tests__/patterns.test.d.ts.map +1 -0
  17. package/dist/__tests__/patterns.test.js +102 -0
  18. package/dist/__tests__/patterns.test.js.map +1 -0
  19. package/dist/bin/dare.js +6 -0
  20. package/dist/bin/dare.js.map +1 -1
  21. package/dist/commands/__tests__/design-interactive.test.d.ts +2 -0
  22. package/dist/commands/__tests__/design-interactive.test.d.ts.map +1 -0
  23. package/dist/commands/__tests__/design-interactive.test.js +44 -0
  24. package/dist/commands/__tests__/design-interactive.test.js.map +1 -0
  25. package/dist/commands/__tests__/hooks.test.d.ts +2 -0
  26. package/dist/commands/__tests__/hooks.test.d.ts.map +1 -0
  27. package/dist/commands/__tests__/hooks.test.js +109 -0
  28. package/dist/commands/__tests__/hooks.test.js.map +1 -0
  29. package/dist/commands/__tests__/patterns.test.d.ts +2 -0
  30. package/dist/commands/__tests__/patterns.test.d.ts.map +1 -0
  31. package/dist/commands/__tests__/patterns.test.js +55 -0
  32. package/dist/commands/__tests__/patterns.test.js.map +1 -0
  33. package/dist/commands/__tests__/steering.test.d.ts +2 -0
  34. package/dist/commands/__tests__/steering.test.d.ts.map +1 -0
  35. package/dist/commands/__tests__/steering.test.js +69 -0
  36. package/dist/commands/__tests__/steering.test.js.map +1 -0
  37. package/dist/commands/design.d.ts.map +1 -1
  38. package/dist/commands/design.js +38 -8
  39. package/dist/commands/design.js.map +1 -1
  40. package/dist/commands/graph.d.ts.map +1 -1
  41. package/dist/commands/graph.js +1 -0
  42. package/dist/commands/graph.js.map +1 -1
  43. package/dist/commands/hooks.d.ts +3 -0
  44. package/dist/commands/hooks.d.ts.map +1 -0
  45. package/dist/commands/hooks.js +163 -0
  46. package/dist/commands/hooks.js.map +1 -0
  47. package/dist/commands/patterns.d.ts +3 -0
  48. package/dist/commands/patterns.d.ts.map +1 -0
  49. package/dist/commands/patterns.js +124 -0
  50. package/dist/commands/patterns.js.map +1 -0
  51. package/dist/commands/steering.d.ts +3 -0
  52. package/dist/commands/steering.d.ts.map +1 -0
  53. package/dist/commands/steering.js +70 -0
  54. package/dist/commands/steering.js.map +1 -0
  55. package/dist/graphrag/__tests__/pattern-ingest.test.d.ts +2 -0
  56. package/dist/graphrag/__tests__/pattern-ingest.test.d.ts.map +1 -0
  57. package/dist/graphrag/__tests__/pattern-ingest.test.js +49 -0
  58. package/dist/graphrag/__tests__/pattern-ingest.test.js.map +1 -0
  59. package/dist/graphrag/__tests__/types.test.js +5 -2
  60. package/dist/graphrag/__tests__/types.test.js.map +1 -1
  61. package/dist/graphrag/pattern-ingest.d.ts +10 -0
  62. package/dist/graphrag/pattern-ingest.d.ts.map +1 -0
  63. package/dist/graphrag/pattern-ingest.js +77 -0
  64. package/dist/graphrag/pattern-ingest.js.map +1 -0
  65. package/dist/graphrag/types.d.ts +15 -4
  66. package/dist/graphrag/types.d.ts.map +1 -1
  67. package/dist/graphrag/types.js +3 -0
  68. package/dist/graphrag/types.js.map +1 -1
  69. package/dist/hooks/__tests__/allowlist.test.d.ts +2 -0
  70. package/dist/hooks/__tests__/allowlist.test.d.ts.map +1 -0
  71. package/dist/hooks/__tests__/allowlist.test.js +29 -0
  72. package/dist/hooks/__tests__/allowlist.test.js.map +1 -0
  73. package/dist/hooks/__tests__/config.test.d.ts +2 -0
  74. package/dist/hooks/__tests__/config.test.d.ts.map +1 -0
  75. package/dist/hooks/__tests__/config.test.js +57 -0
  76. package/dist/hooks/__tests__/config.test.js.map +1 -0
  77. package/dist/hooks/__tests__/dispatcher.security.test.d.ts +2 -0
  78. package/dist/hooks/__tests__/dispatcher.security.test.d.ts.map +1 -0
  79. package/dist/hooks/__tests__/dispatcher.security.test.js +86 -0
  80. package/dist/hooks/__tests__/dispatcher.security.test.js.map +1 -0
  81. package/dist/hooks/__tests__/dispatcher.test.d.ts +2 -0
  82. package/dist/hooks/__tests__/dispatcher.test.d.ts.map +1 -0
  83. package/dist/hooks/__tests__/dispatcher.test.js +69 -0
  84. package/dist/hooks/__tests__/dispatcher.test.js.map +1 -0
  85. package/dist/hooks/__tests__/idempotency.test.d.ts +2 -0
  86. package/dist/hooks/__tests__/idempotency.test.d.ts.map +1 -0
  87. package/dist/hooks/__tests__/idempotency.test.js +45 -0
  88. package/dist/hooks/__tests__/idempotency.test.js.map +1 -0
  89. package/dist/hooks/__tests__/telemetry.test.d.ts +2 -0
  90. package/dist/hooks/__tests__/telemetry.test.d.ts.map +1 -0
  91. package/dist/hooks/__tests__/telemetry.test.js +52 -0
  92. package/dist/hooks/__tests__/telemetry.test.js.map +1 -0
  93. package/dist/hooks/allowlist.d.ts +24 -0
  94. package/dist/hooks/allowlist.d.ts.map +1 -0
  95. package/dist/hooks/allowlist.js +61 -0
  96. package/dist/hooks/allowlist.js.map +1 -0
  97. package/dist/hooks/config.d.ts +24 -0
  98. package/dist/hooks/config.d.ts.map +1 -0
  99. package/dist/hooks/config.js +82 -0
  100. package/dist/hooks/config.js.map +1 -0
  101. package/dist/hooks/dispatcher.d.ts +17 -0
  102. package/dist/hooks/dispatcher.d.ts.map +1 -0
  103. package/dist/hooks/dispatcher.js +157 -0
  104. package/dist/hooks/dispatcher.js.map +1 -0
  105. package/dist/hooks/idempotency.d.ts +14 -0
  106. package/dist/hooks/idempotency.d.ts.map +1 -0
  107. package/dist/hooks/idempotency.js +64 -0
  108. package/dist/hooks/idempotency.js.map +1 -0
  109. package/dist/hooks/telemetry.d.ts +12 -0
  110. package/dist/hooks/telemetry.d.ts.map +1 -0
  111. package/dist/hooks/telemetry.js +66 -0
  112. package/dist/hooks/telemetry.js.map +1 -0
  113. package/dist/hooks/types.d.ts +36 -0
  114. package/dist/hooks/types.d.ts.map +1 -0
  115. package/dist/hooks/types.js +7 -0
  116. package/dist/hooks/types.js.map +1 -0
  117. package/dist/index.d.ts +3 -0
  118. package/dist/index.d.ts.map +1 -1
  119. package/dist/index.js +3 -0
  120. package/dist/index.js.map +1 -1
  121. package/dist/mcp-server/__tests__/mcp-steering.test.d.ts +2 -0
  122. package/dist/mcp-server/__tests__/mcp-steering.test.d.ts.map +1 -0
  123. package/dist/mcp-server/__tests__/mcp-steering.test.js +90 -0
  124. package/dist/mcp-server/__tests__/mcp-steering.test.js.map +1 -0
  125. package/dist/mcp-server/server.d.ts.map +1 -1
  126. package/dist/mcp-server/server.js +30 -0
  127. package/dist/mcp-server/server.js.map +1 -1
  128. package/dist/steering/__tests__/loader.test.d.ts +2 -0
  129. package/dist/steering/__tests__/loader.test.d.ts.map +1 -0
  130. package/dist/steering/__tests__/loader.test.js +95 -0
  131. package/dist/steering/__tests__/loader.test.js.map +1 -0
  132. package/dist/steering/__tests__/resolver.security.test.d.ts +2 -0
  133. package/dist/steering/__tests__/resolver.security.test.d.ts.map +1 -0
  134. package/dist/steering/__tests__/resolver.security.test.js +42 -0
  135. package/dist/steering/__tests__/resolver.security.test.js.map +1 -0
  136. package/dist/steering/__tests__/resolver.test.d.ts +2 -0
  137. package/dist/steering/__tests__/resolver.test.d.ts.map +1 -0
  138. package/dist/steering/__tests__/resolver.test.js +75 -0
  139. package/dist/steering/__tests__/resolver.test.js.map +1 -0
  140. package/dist/steering/loader.d.ts +8 -0
  141. package/dist/steering/loader.d.ts.map +1 -0
  142. package/dist/steering/loader.js +109 -0
  143. package/dist/steering/loader.js.map +1 -0
  144. package/dist/steering/resolver.d.ts +6 -0
  145. package/dist/steering/resolver.d.ts.map +1 -0
  146. package/dist/steering/resolver.js +59 -0
  147. package/dist/steering/resolver.js.map +1 -0
  148. package/dist/steering/types.d.ts +22 -0
  149. package/dist/steering/types.d.ts.map +1 -0
  150. package/dist/steering/types.js +2 -0
  151. package/dist/steering/types.js.map +1 -0
  152. package/dist/utils/UpdateApplier.d.ts.map +1 -1
  153. package/dist/utils/UpdateApplier.js +2 -0
  154. package/dist/utils/UpdateApplier.js.map +1 -1
  155. package/dist/utils/__tests__/design-questionnaire.test.d.ts +2 -0
  156. package/dist/utils/__tests__/design-questionnaire.test.d.ts.map +1 -0
  157. package/dist/utils/__tests__/design-questionnaire.test.js +72 -0
  158. package/dist/utils/__tests__/design-questionnaire.test.js.map +1 -0
  159. package/dist/utils/__tests__/pattern-detector.test.d.ts +2 -0
  160. package/dist/utils/__tests__/pattern-detector.test.d.ts.map +1 -0
  161. package/dist/utils/__tests__/pattern-detector.test.js +81 -0
  162. package/dist/utils/__tests__/pattern-detector.test.js.map +1 -0
  163. package/dist/utils/__tests__/pattern-facts.test.d.ts +2 -0
  164. package/dist/utils/__tests__/pattern-facts.test.d.ts.map +1 -0
  165. package/dist/utils/__tests__/pattern-facts.test.js +55 -0
  166. package/dist/utils/__tests__/pattern-facts.test.js.map +1 -0
  167. package/dist/utils/design-questionnaire.d.ts +22 -0
  168. package/dist/utils/design-questionnaire.d.ts.map +1 -0
  169. package/dist/utils/design-questionnaire.js +151 -0
  170. package/dist/utils/design-questionnaire.js.map +1 -0
  171. package/dist/utils/pattern-detector.d.ts +39 -0
  172. package/dist/utils/pattern-detector.d.ts.map +1 -0
  173. package/dist/utils/pattern-detector.js +291 -0
  174. package/dist/utils/pattern-detector.js.map +1 -0
  175. package/dist/utils/pattern-facts.d.ts +7 -0
  176. package/dist/utils/pattern-facts.d.ts.map +1 -0
  177. package/dist/utils/pattern-facts.js +86 -0
  178. package/dist/utils/pattern-facts.js.map +1 -0
  179. package/dist/utils/project-generator.d.ts.map +1 -1
  180. package/dist/utils/project-generator.js +2 -0
  181. package/dist/utils/project-generator.js.map +1 -1
  182. package/package.json +1 -1
  183. package/templates/hooks/dare.config.hooks.example.json +12 -0
  184. package/templates/hooks/pre-commit-dare-validate +2 -2
  185. package/templates/ide/antigravity/.agents/skills/dare-blueprint/SKILL.md +4 -0
  186. package/templates/ide/antigravity/.agents/skills/dare-design/SKILL.md +11 -0
  187. package/templates/ide/antigravity/.agents/skills/dare-hooks/SKILL.md +13 -0
  188. package/templates/ide/antigravity/.agents/skills/dare-patterns/SKILL.md +9 -0
  189. package/templates/ide/antigravity/.agents/skills/dare-steering/SKILL.md +15 -0
  190. package/templates/ide/antigravity/templates/HOOKS-ADAPTER.md +14 -0
  191. package/templates/ide/claude/.claude/commands/dare-blueprint.md +4 -0
  192. package/templates/ide/claude/.claude/commands/dare-design.md +10 -0
  193. package/templates/ide/claude/.claude/commands/dare-hooks.md +17 -0
  194. package/templates/ide/claude/.claude/commands/dare-patterns.md +12 -0
  195. package/templates/ide/claude/.claude/commands/dare-steering.md +19 -0
  196. package/templates/ide/claude/.claude/settings.example.json +1 -1
  197. package/templates/ide/cursor/.cursor/commands/dare-blueprint.md +4 -2
  198. package/templates/ide/cursor/.cursor/commands/dare-design.md +9 -3
  199. package/templates/ide/cursor/.cursor/commands/dare-hooks.md +17 -0
  200. package/templates/ide/cursor/.cursor/commands/dare-patterns.md +12 -0
  201. package/templates/ide/cursor/.cursor/commands/dare-steering.md +19 -0
  202. package/templates/ide/cursor/templates/HOOKS-ADAPTER.md +14 -0
@@ -0,0 +1,69 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import fs from 'fs-extra';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { dispatchHook, TrustRequiredError, ActionNotAllowedError, } from '../dispatcher.js';
6
+ const spawnMock = vi.fn();
7
+ vi.mock('../../exec/safe-spawn.js', () => ({
8
+ safeSpawn: (...args) => spawnMock(...args),
9
+ }));
10
+ describe('hooks dispatcher', () => {
11
+ let projectRoot;
12
+ beforeEach(async () => {
13
+ spawnMock.mockReset();
14
+ spawnMock.mockResolvedValue({ code: 0, stdout: '', stderr: '', timedOut: false });
15
+ projectRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'hook-dispatch-'));
16
+ await fs.writeJson(path.join(projectRoot, 'dare.config.json'), {
17
+ structure: 'mcp-server',
18
+ mcpLanguage: 'node-ts',
19
+ });
20
+ await fs.writeFile(path.join(projectRoot, 'dare-graph.yml'), 'backend: json\njson:\n path: .dare/graph.json\n');
21
+ });
22
+ afterEach(async () => {
23
+ await fs.remove(projectRoot).catch(() => undefined);
24
+ });
25
+ it('rejects untrusted config without trustOverride', async () => {
26
+ await expect(dispatchHook({ on: { 'on-save': [{ action: 'lint' }] }, trusted: false }, { event: 'on-save' }, { projectRoot })).rejects.toThrow(TrustRequiredError);
27
+ expect(spawnMock).not.toHaveBeenCalled();
28
+ });
29
+ it('dispatches dare-review via safeSpawn with argv (shell:false path)', async () => {
30
+ const results = await dispatchHook({
31
+ on: { 'on-task-complete': [{ action: 'dare-review' }] },
32
+ trusted: true,
33
+ }, { event: 'on-task-complete', taskId: 'task-101' }, { projectRoot });
34
+ expect(spawnMock).toHaveBeenCalledTimes(1);
35
+ const [cmd, argv, opts] = spawnMock.mock.calls[0];
36
+ expect(cmd).toBe('dare');
37
+ expect(argv).toEqual(['review', 'task-101', '--strict', '--format', 'json']);
38
+ expect(opts).toMatchObject({ cwd: projectRoot, timeoutSeconds: 600 });
39
+ expect(results[0]).toMatchObject({
40
+ action: 'dare-review',
41
+ exitCode: 0,
42
+ skipped: false,
43
+ verdict: 'pass',
44
+ });
45
+ });
46
+ it('skips duplicate dispatch via idempotency', async () => {
47
+ const config = {
48
+ on: { 'on-save': [{ action: 'dare-validate' }] },
49
+ trusted: true,
50
+ };
51
+ const payload = { event: 'on-save' };
52
+ await dispatchHook(config, payload, { projectRoot });
53
+ const second = await dispatchHook(config, payload, { projectRoot });
54
+ expect(spawnMock).toHaveBeenCalledTimes(1);
55
+ expect(second[0]?.skipped).toBe(true);
56
+ });
57
+ it('rejects disallowed action keys at resolve time', async () => {
58
+ await expect(dispatchHook({
59
+ on: { 'on-save': [{ action: 'rm-rf' }] },
60
+ trusted: true,
61
+ }, { event: 'on-save' }, { projectRoot })).rejects.toThrow(ActionNotAllowedError);
62
+ });
63
+ it('returns empty array when no actions configured', async () => {
64
+ const results = await dispatchHook({ on: {}, trusted: true }, { event: 'pre-commit' }, { projectRoot });
65
+ expect(results).toEqual([]);
66
+ expect(spawnMock).not.toHaveBeenCalled();
67
+ });
68
+ });
69
+ //# sourceMappingURL=dispatcher.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatcher.test.js","sourceRoot":"","sources":["../../../src/hooks/__tests__/dispatcher.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=idempotency.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idempotency.test.d.ts","sourceRoot":"","sources":["../../../src/hooks/__tests__/idempotency.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,45 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import fs from 'fs-extra';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { markSeen, shouldSkip, stateKey } from '../idempotency.js';
6
+ describe('hooks idempotency', () => {
7
+ let projectRoot;
8
+ const ctx = () => ({
9
+ projectRoot,
10
+ statePath: '.dare/hooks-state.json',
11
+ });
12
+ beforeEach(async () => {
13
+ projectRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'hooks-idem-'));
14
+ await fs.ensureDir(path.join(projectRoot, '.dare'));
15
+ });
16
+ afterEach(async () => {
17
+ await fs.remove(projectRoot).catch(() => undefined);
18
+ });
19
+ it('skips after markSeen for same file event', async () => {
20
+ const payload = { event: 'on-save', file: 'src/a.ts' };
21
+ expect(await shouldSkip('on-save', 'lint', payload, ctx())).toBe(false);
22
+ await markSeen('on-save', 'lint', payload, ctx());
23
+ expect(await shouldSkip('on-save', 'lint', payload, ctx())).toBe(true);
24
+ });
25
+ it('does not skip for different files', async () => {
26
+ const a = { event: 'on-save', file: 'src/a.ts' };
27
+ const b = { event: 'on-save', file: 'src/b.ts' };
28
+ await markSeen('on-save', 'lint', a, ctx());
29
+ expect(await shouldSkip('on-save', 'lint', b, ctx())).toBe(false);
30
+ });
31
+ it('on-task-complete uses stable key regardless of file order', () => {
32
+ const payload = { event: 'on-task-complete', taskId: 'task-101' };
33
+ const k1 = stateKey('on-task-complete', 'dare-review', payload, {
34
+ touchedFiles: ['src/b.ts', 'src/a.ts'],
35
+ });
36
+ const k2 = stateKey('on-task-complete', 'dare-review', payload, {
37
+ touchedFiles: ['src/a.ts', 'src/b.ts'],
38
+ });
39
+ expect(k1).toBe(k2);
40
+ });
41
+ it('rejects unsafe paths', async () => {
42
+ await expect(shouldSkip('on-save', 'lint', { event: 'on-save', file: '../escape.ts' }, ctx())).rejects.toThrow();
43
+ });
44
+ });
45
+ //# sourceMappingURL=idempotency.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idempotency.test.js","sourceRoot":"","sources":["../../../src/hooks/__tests__/idempotency.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=telemetry.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry.test.d.ts","sourceRoot":"","sources":["../../../src/hooks/__tests__/telemetry.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,52 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { JsonGraph } from '../../graphrag/json-graph.js';
5
+ import { recordHookTrigger } from '../telemetry.js';
6
+ describe('hooks telemetry', () => {
7
+ let graph;
8
+ beforeEach(async () => {
9
+ const file = path.join(os.tmpdir(), `hook-tel-${Date.now()}.json`);
10
+ graph = new JsonGraph(file);
11
+ await graph.init();
12
+ });
13
+ afterEach(() => graph.close());
14
+ it('records hook node and triggered_by edge', () => {
15
+ recordHookTrigger(graph, {
16
+ event: 'on-save',
17
+ action: 'lint',
18
+ exitCode: 0,
19
+ skipped: false,
20
+ triggeredAt: '2026-06-07T12:00:00.000Z',
21
+ });
22
+ const edges = graph.getEdges('concept:hook:on-save:lint:2026-06-07T12:00:00.000Z', 'out');
23
+ expect(edges.some((e) => e.type === 'references' && e.metadata?.relation === 'triggered_by')).toBe(true);
24
+ });
25
+ it('records produced edge when verdict is present', () => {
26
+ recordHookTrigger(graph, {
27
+ event: 'on-task-complete',
28
+ action: 'dare-review',
29
+ exitCode: 0,
30
+ skipped: false,
31
+ verdict: 'pass',
32
+ triggeredAt: '2026-06-07T12:00:01.000Z',
33
+ });
34
+ const hookId = 'concept:hook:on-task-complete:dare-review:2026-06-07T12:00:01.000Z';
35
+ const edges = graph.getEdges(hookId, 'out');
36
+ expect(edges.some((e) => e.type === 'related_to' && e.metadata?.relation === 'produced')).toBe(true);
37
+ });
38
+ it('upserts without duplicating nodes on re-register', () => {
39
+ const record = {
40
+ event: 'pre-commit',
41
+ action: 'dare-validate',
42
+ exitCode: 0,
43
+ skipped: false,
44
+ triggeredAt: '2026-06-07T12:00:02.000Z',
45
+ };
46
+ recordHookTrigger(graph, record);
47
+ recordHookTrigger(graph, record);
48
+ const stats = graph.getStatistics();
49
+ expect(stats.totalNodes).toBeLessThanOrEqual(3);
50
+ });
51
+ });
52
+ //# sourceMappingURL=telemetry.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry.test.js","sourceRoot":"","sources":["../../../src/hooks/__tests__/telemetry.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACrE,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEpD,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,IAAI,KAAgB,CAAC;IAErB,UAAU,CAAC,KAAK,IAAI,EAAE;QACpB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,YAAY,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QACnE,KAAK,GAAG,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC;QAC5B,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC;IACrB,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;IAE/B,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,iBAAiB,CAAC,KAAK,EAAE;YACvB,KAAK,EAAE,SAAS;YAChB,MAAM,EAAE,MAAM;YACd,QAAQ,EAAE,CAAC;YACX,OAAO,EAAE,KAAK;YACd,WAAW,EAAE,0BAA0B;SACxC,CAAC,CAAC;QACH,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,oDAAoD,EAAE,KAAK,CAAC,CAAC;QAC1F,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,YAAY,IAAI,CAAC,CAAC,QAAQ,EAAE,QAAQ,KAAK,cAAc,CAAC,CAAC,CAAC,IAAI,CAChG,IAAI,CACL,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,iBAAiB,CAAC,KAAK,EAAE;YACvB,KAAK,EAAE,kBAAkB;YACzB,MAAM,EAAE,aAAa;YACrB,QAAQ,EAAE,CAAC;YACX,OAAO,EAAE,KAAK;YACd,OAAO,EAAE,MAAM;YACf,WAAW,EAAE,0BAA0B;SACxC,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,oEAAoE,CAAC;QACpF,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAC5C,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,YAAY,IAAI,CAAC,CAAC,QAAQ,EAAE,QAAQ,KAAK,UAAU,CAAC,CAAC,CAAC,IAAI,CAC5F,IAAI,CACL,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,MAAM,GAAG;YACb,KAAK,EAAE,YAAqB;YAC5B,MAAM,EAAE,eAAwB;YAChC,QAAQ,EAAE,CAAC;YACX,OAAO,EAAE,KAAK;YACd,WAAW,EAAE,0BAA0B;SACxC,CAAC;QACF,iBAAiB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QACjC,iBAAiB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QACjC,MAAM,KAAK,GAAG,KAAK,CAAC,aAAa,EAAE,CAAC;QACpC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,24 @@
1
+ import type { HookEventPayload } from './types.js';
2
+ /** Chaves FECHADAS de ação (A-3 / RS-01 / RS-06). Editável só via diff versionado. */
3
+ export type AllowedActionKey = 'dare-validate' | 'dare-review' | 'graph-register' | 'lint' | 'test';
4
+ /** Conjunto fechado materializado — fonte única para validação Zod (task-303). */
5
+ export declare const ALLOWED_ACTION_KEYS: readonly AllowedActionKey[];
6
+ export interface ResolvedCommand {
7
+ readonly cmd: string;
8
+ readonly argv: readonly string[];
9
+ }
10
+ /** Veredito de que a ação é "interna" (não spawna) — graph-register (RF-12). */
11
+ export declare const INTERNAL_ACTIONS: readonly AllowedActionKey[];
12
+ export declare class ActionNotAllowedError extends Error {
13
+ readonly code: "ACTION_NOT_ALLOWED";
14
+ constructor(key: string);
15
+ }
16
+ /**
17
+ * Resolve uma ação da allowlist para (cmd, argv). Único ponto que decide o que roda.
18
+ * Determinístico; payload entra como argv por elemento, nunca interpolado (RS-02).
19
+ */
20
+ export declare function resolveAction(action: AllowedActionKey, payload: HookEventPayload, stack: {
21
+ lint?: string;
22
+ test?: string;
23
+ }): ResolvedCommand;
24
+ //# sourceMappingURL=allowlist.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"allowlist.d.ts","sourceRoot":"","sources":["../../src/hooks/allowlist.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,sFAAsF;AACtF,MAAM,MAAM,gBAAgB,GACxB,eAAe,GACf,aAAa,GACb,gBAAgB,GAChB,MAAM,GACN,MAAM,CAAC;AAEX,kFAAkF;AAClF,eAAO,MAAM,mBAAmB,EAAE,SAAS,gBAAgB,EAMjD,CAAC;AAEX,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAED,gFAAgF;AAChF,eAAO,MAAM,gBAAgB,EAAE,SAAS,gBAAgB,EAAgC,CAAC;AAEzF,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,IAAI,EAAG,oBAAoB,CAAU;gBAClC,GAAG,EAAE,MAAM;CAIxB;AAWD;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,gBAAgB,EACxB,OAAO,EAAE,gBAAgB,EACzB,KAAK,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GACtC,eAAe,CA4BjB"}
@@ -0,0 +1,61 @@
1
+ /** Conjunto fechado materializado — fonte única para validação Zod (task-303). */
2
+ export const ALLOWED_ACTION_KEYS = [
3
+ 'dare-validate',
4
+ 'dare-review',
5
+ 'graph-register',
6
+ 'lint',
7
+ 'test',
8
+ ];
9
+ /** Veredito de que a ação é "interna" (não spawna) — graph-register (RF-12). */
10
+ export const INTERNAL_ACTIONS = ['graph-register'];
11
+ export class ActionNotAllowedError extends Error {
12
+ constructor(key) {
13
+ super(`Hook action '${key}' is not in the allowlist`);
14
+ this.code = 'ACTION_NOT_ALLOWED';
15
+ this.name = 'ActionNotAllowedError';
16
+ }
17
+ }
18
+ function splitStackCommand(command) {
19
+ const parts = command.trim().split(/\s+/).filter(Boolean);
20
+ if (parts.length === 0) {
21
+ throw new ActionNotAllowedError('lint/test (empty stack command)');
22
+ }
23
+ const [cmd, ...argv] = parts;
24
+ return { cmd: cmd, argv };
25
+ }
26
+ /**
27
+ * Resolve uma ação da allowlist para (cmd, argv). Único ponto que decide o que roda.
28
+ * Determinístico; payload entra como argv por elemento, nunca interpolado (RS-02).
29
+ */
30
+ export function resolveAction(action, payload, stack) {
31
+ switch (action) {
32
+ case 'dare-validate':
33
+ return { cmd: 'dare', argv: ['validate', '--strict'] };
34
+ case 'dare-review': {
35
+ if (!payload.taskId) {
36
+ throw new ActionNotAllowedError('dare-review');
37
+ }
38
+ return {
39
+ cmd: 'dare',
40
+ argv: ['review', payload.taskId, '--strict', '--format', 'json'],
41
+ };
42
+ }
43
+ case 'graph-register':
44
+ return { cmd: '', argv: [] };
45
+ case 'lint': {
46
+ const cmd = stack.lint;
47
+ if (!cmd)
48
+ throw new ActionNotAllowedError('lint');
49
+ return splitStackCommand(cmd);
50
+ }
51
+ case 'test': {
52
+ const cmd = stack.test;
53
+ if (!cmd)
54
+ throw new ActionNotAllowedError('test');
55
+ return splitStackCommand(cmd);
56
+ }
57
+ default:
58
+ throw new ActionNotAllowedError(String(action));
59
+ }
60
+ }
61
+ //# sourceMappingURL=allowlist.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"allowlist.js","sourceRoot":"","sources":["../../src/hooks/allowlist.ts"],"names":[],"mappings":"AAUA,kFAAkF;AAClF,MAAM,CAAC,MAAM,mBAAmB,GAAgC;IAC9D,eAAe;IACf,aAAa;IACb,gBAAgB;IAChB,MAAM;IACN,MAAM;CACE,CAAC;AAOX,gFAAgF;AAChF,MAAM,CAAC,MAAM,gBAAgB,GAAgC,CAAC,gBAAgB,CAAU,CAAC;AAEzF,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IAE9C,YAAY,GAAW;QACrB,KAAK,CAAC,gBAAgB,GAAG,2BAA2B,CAAC,CAAC;QAF/C,SAAI,GAAG,oBAA6B,CAAC;QAG5C,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;IACtC,CAAC;CACF;AAED,SAAS,iBAAiB,CAAC,OAAe;IACxC,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC1D,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,qBAAqB,CAAC,iCAAiC,CAAC,CAAC;IACrE,CAAC;IACD,MAAM,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,KAAK,CAAC;IAC7B,OAAO,EAAE,GAAG,EAAE,GAAI,EAAE,IAAI,EAAE,CAAC;AAC7B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAC3B,MAAwB,EACxB,OAAyB,EACzB,KAAuC;IAEvC,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,eAAe;YAClB,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,CAAC;QACzD,KAAK,aAAa,CAAC,CAAC,CAAC;YACnB,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;gBACpB,MAAM,IAAI,qBAAqB,CAAC,aAAa,CAAC,CAAC;YACjD,CAAC;YACD,OAAO;gBACL,GAAG,EAAE,MAAM;gBACX,IAAI,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,CAAC;aACjE,CAAC;QACJ,CAAC;QACD,KAAK,gBAAgB;YACnB,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;QAC/B,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC;YACvB,IAAI,CAAC,GAAG;gBAAE,MAAM,IAAI,qBAAqB,CAAC,MAAM,CAAC,CAAC;YAClD,OAAO,iBAAiB,CAAC,GAAG,CAAC,CAAC;QAChC,CAAC;QACD,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC;YACvB,IAAI,CAAC,GAAG;gBAAE,MAAM,IAAI,qBAAqB,CAAC,MAAM,CAAC,CAAC;YAClD,OAAO,iBAAiB,CAAC,GAAG,CAAC,CAAC;QAChC,CAAC;QACD;YACE,MAAM,IAAI,qBAAqB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IACpD,CAAC;AACH,CAAC"}
@@ -0,0 +1,24 @@
1
+ import type { HookConfig } from './types.js';
2
+ export declare const HOOK_DEFAULTS: HookConfig;
3
+ export declare class HookConfigError extends Error {
4
+ readonly issues: ReadonlyArray<{
5
+ path: string;
6
+ message: string;
7
+ }>;
8
+ constructor(issues: ReadonlyArray<{
9
+ path: string;
10
+ message: string;
11
+ }>);
12
+ }
13
+ /**
14
+ * Parse and validate `hooks` from an already-parsed dare.config.json object.
15
+ */
16
+ export declare function parseHookConfig(raw: unknown): HookConfig;
17
+ /** Serializable defaults for dare.config.json (new projects + migrations). */
18
+ export declare function defaultHookConfigForProject(): HookConfig;
19
+ /**
20
+ * Inserts the hooks block when absent (opt-in: trusted stays false).
21
+ * Returns true when the block was added.
22
+ */
23
+ export declare function seedHooksDefaultsIfAbsent(cfg: Record<string, unknown>): boolean;
24
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/hooks/config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAI7C,eAAO,MAAM,aAAa,EAAE,UAAuC,CAAC;AAyBpE,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;gBAEtD,MAAM,EAAE,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CAOrE;AAsBD;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,GAAG,UAAU,CAoBxD;AAED,8EAA8E;AAC9E,wBAAgB,2BAA2B,IAAI,UAAU,CAExD;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC3B,OAAO,CAIT"}
@@ -0,0 +1,82 @@
1
+ import { z } from 'zod';
2
+ import { assertRelativeSafe } from '../utils/path-safety.js';
3
+ import { HOOK_EVENTS } from './types.js';
4
+ import { ALLOWED_ACTION_KEYS } from './allowlist.js';
5
+ export const HOOK_DEFAULTS = { on: {}, trusted: false };
6
+ const hookEventEnum = z.enum(HOOK_EVENTS);
7
+ const actionKeyEnum = z.enum(ALLOWED_ACTION_KEYS);
8
+ const hookActionSchema = z
9
+ .object({
10
+ action: actionKeyEnum,
11
+ args: z.array(z.string()).optional(),
12
+ })
13
+ .strict();
14
+ const hookConfigSchema = z
15
+ .object({
16
+ on: z
17
+ .record(hookEventEnum, z.array(hookActionSchema))
18
+ .default({}),
19
+ trusted: z.boolean().default(false),
20
+ })
21
+ .strict();
22
+ export class HookConfigError extends Error {
23
+ constructor(issues) {
24
+ super(`Invalid hooks config: ${issues.map((i) => `${i.path}: ${i.message}`).join('; ')}`);
25
+ this.name = 'HookConfigError';
26
+ this.issues = issues;
27
+ }
28
+ }
29
+ function isHooksBlockAbsent(raw) {
30
+ if (raw === undefined || raw === null)
31
+ return true;
32
+ if (typeof raw !== 'object')
33
+ return false;
34
+ const rec = raw;
35
+ return !('hooks' in rec) || rec.hooks === undefined;
36
+ }
37
+ function zodIssues(error) {
38
+ return error.issues.map((issue) => ({
39
+ path: issue.path.length > 0 ? issue.path.join('.') : '(root)',
40
+ message: issue.message,
41
+ }));
42
+ }
43
+ function looksLikePath(arg) {
44
+ return arg.includes('/') || arg.includes('\\') || arg.includes('..');
45
+ }
46
+ /**
47
+ * Parse and validate `hooks` from an already-parsed dare.config.json object.
48
+ */
49
+ export function parseHookConfig(raw) {
50
+ if (isHooksBlockAbsent(raw)) {
51
+ return { on: {}, trusted: false };
52
+ }
53
+ const block = raw.hooks;
54
+ const result = hookConfigSchema.safeParse(block);
55
+ if (!result.success) {
56
+ throw new HookConfigError(zodIssues(result.error));
57
+ }
58
+ for (const actions of Object.values(result.data.on)) {
59
+ for (const a of actions ?? []) {
60
+ for (const arg of a.args ?? []) {
61
+ if (looksLikePath(arg))
62
+ assertRelativeSafe(arg);
63
+ }
64
+ }
65
+ }
66
+ return result.data;
67
+ }
68
+ /** Serializable defaults for dare.config.json (new projects + migrations). */
69
+ export function defaultHookConfigForProject() {
70
+ return structuredClone(HOOK_DEFAULTS);
71
+ }
72
+ /**
73
+ * Inserts the hooks block when absent (opt-in: trusted stays false).
74
+ * Returns true when the block was added.
75
+ */
76
+ export function seedHooksDefaultsIfAbsent(cfg) {
77
+ if (cfg.hooks !== undefined)
78
+ return false;
79
+ cfg.hooks = defaultHookConfigForProject();
80
+ return true;
81
+ }
82
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/hooks/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import { ActionNotAllowedError } from './allowlist.js';
2
+ import { PathEscapeError } from '../utils/path-safety.js';
3
+ import type { HookConfig, HookEventPayload, HookResult } from './types.js';
4
+ export declare class TrustRequiredError extends Error {
5
+ readonly code: "TRUST_REQUIRED";
6
+ constructor(message?: string);
7
+ }
8
+ export declare class InvalidHookEventError extends Error {
9
+ readonly code: "INVALID_HOOK_EVENT";
10
+ constructor(event: string);
11
+ }
12
+ export declare function dispatchHook(config: HookConfig, payload: HookEventPayload, ctx: {
13
+ projectRoot: string;
14
+ trustOverride?: boolean;
15
+ }): Promise<HookResult[]>;
16
+ export { ActionNotAllowedError, PathEscapeError };
17
+ //# sourceMappingURL=dispatcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/hooks/dispatcher.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,qBAAqB,EAEtB,MAAM,gBAAgB,CAAC;AAIxB,OAAO,EAAsB,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE9E,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,UAAU,EAAa,MAAM,YAAY,CAAC;AAWtF,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,IAAI,EAAG,gBAAgB,CAAU;gBAC9B,OAAO,SAAyC;CAI7D;AAED,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,IAAI,EAAG,oBAAoB,CAAU;gBAClC,KAAK,EAAE,MAAM;CAI1B;AAyDD,wBAAsB,YAAY,CAChC,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,gBAAgB,EACzB,GAAG,EAAE;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAE,GACpD,OAAO,CAAC,UAAU,EAAE,CAAC,CAuGvB;AAED,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,CAAC"}
@@ -0,0 +1,157 @@
1
+ import pino from 'pino';
2
+ import { safeSpawn } from '../exec/safe-spawn.js';
3
+ import { resolveAction, ActionNotAllowedError, INTERNAL_ACTIONS, } from './allowlist.js';
4
+ import { shouldSkip, markSeen } from './idempotency.js';
5
+ import { recordHookTrigger } from './telemetry.js';
6
+ import { assertRelativeSafe, PathEscapeError } from '../utils/path-safety.js';
7
+ import { HOOK_EVENTS } from './types.js';
8
+ import { gatesFor, resolveStackFromConfig, formatGateCommand, } from '../dag-runner/ralph-loop.js';
9
+ import { createGraph, loadGraphConfig } from '../graphrag/index.js';
10
+ const logger = pino({ level: process.env.DARE_HOOKS_LOG_LEVEL ?? 'info' });
11
+ export class TrustRequiredError extends Error {
12
+ constructor(message = 'hooks are untrusted for this project') {
13
+ super(message);
14
+ this.code = 'TRUST_REQUIRED';
15
+ this.name = 'TrustRequiredError';
16
+ }
17
+ }
18
+ export class InvalidHookEventError extends Error {
19
+ constructor(event) {
20
+ super(`Invalid hook event: ${event}`);
21
+ this.code = 'INVALID_HOOK_EVENT';
22
+ this.name = 'InvalidHookEventError';
23
+ }
24
+ }
25
+ const TASK_ID_RE = /^task-[0-9a-z-]+$/;
26
+ const VERDICT_ACTIONS = new Set([
27
+ 'dare-validate',
28
+ 'dare-review',
29
+ 'lint',
30
+ 'test',
31
+ ]);
32
+ function isHookEvent(value) {
33
+ return HOOK_EVENTS.includes(value);
34
+ }
35
+ async function resolveHookStack(projectRoot) {
36
+ try {
37
+ const stack = await resolveStackFromConfig(projectRoot);
38
+ const gates = gatesFor(stack, projectRoot);
39
+ const lintGate = gates.find((g) => g.name === 'lint');
40
+ const testGate = gates.find((g) => g.name === 'test');
41
+ return {
42
+ lint: lintGate ? formatGateCommand(lintGate) : undefined,
43
+ test: testGate ? formatGateCommand(testGate) : undefined,
44
+ };
45
+ }
46
+ catch {
47
+ return {};
48
+ }
49
+ }
50
+ async function withProjectGraph(projectRoot, fn) {
51
+ const config = await loadGraphConfig({ cwd: projectRoot });
52
+ const graph = await createGraph(config, { cwd: projectRoot });
53
+ try {
54
+ return await fn(graph);
55
+ }
56
+ finally {
57
+ await Promise.resolve(graph.close());
58
+ }
59
+ }
60
+ function verdictFor(action, exitCode) {
61
+ if (!VERDICT_ACTIONS.has(action))
62
+ return undefined;
63
+ return exitCode === 0 ? 'pass' : 'fail';
64
+ }
65
+ function isInternalAction(action) {
66
+ return INTERNAL_ACTIONS.includes(action);
67
+ }
68
+ export async function dispatchHook(config, payload, ctx) {
69
+ if (config.trusted !== true && ctx.trustOverride !== true) {
70
+ throw new TrustRequiredError();
71
+ }
72
+ if (!isHookEvent(payload.event)) {
73
+ throw new InvalidHookEventError(String(payload.event));
74
+ }
75
+ if (payload.file) {
76
+ try {
77
+ assertRelativeSafe(payload.file);
78
+ }
79
+ catch {
80
+ throw new PathEscapeError();
81
+ }
82
+ }
83
+ if (payload.taskId && !TASK_ID_RE.test(payload.taskId)) {
84
+ throw new Error(`Invalid taskId: ${payload.taskId}`);
85
+ }
86
+ const actions = config.on[payload.event] ?? [];
87
+ if (actions.length === 0) {
88
+ return [];
89
+ }
90
+ const stack = await resolveHookStack(ctx.projectRoot);
91
+ const results = [];
92
+ await withProjectGraph(ctx.projectRoot, async (graph) => {
93
+ for (const hookAction of actions) {
94
+ const action = hookAction.action;
95
+ const start = performance.now();
96
+ if (await shouldSkip(payload.event, action, payload, {
97
+ projectRoot: ctx.projectRoot,
98
+ })) {
99
+ const durationMs = Math.round(performance.now() - start);
100
+ results.push({
101
+ event: payload.event,
102
+ action,
103
+ exitCode: 0,
104
+ skipped: true,
105
+ durationMs,
106
+ });
107
+ continue;
108
+ }
109
+ const { cmd, argv } = resolveAction(action, payload, stack);
110
+ const triggeredAt = new Date().toISOString();
111
+ let exitCode = 0;
112
+ let skipped = false;
113
+ if (isInternalAction(action)) {
114
+ recordHookTrigger(graph, {
115
+ event: payload.event,
116
+ action,
117
+ exitCode: 0,
118
+ skipped: false,
119
+ triggeredAt,
120
+ });
121
+ }
122
+ else {
123
+ const res = await safeSpawn(cmd, [...argv, ...(hookAction.args ?? [])], {
124
+ cwd: ctx.projectRoot,
125
+ timeoutSeconds: 600,
126
+ });
127
+ exitCode = res.code;
128
+ const verdict = verdictFor(action, exitCode);
129
+ recordHookTrigger(graph, {
130
+ event: payload.event,
131
+ action,
132
+ exitCode,
133
+ skipped: false,
134
+ verdict,
135
+ triggeredAt,
136
+ });
137
+ }
138
+ await markSeen(payload.event, action, payload, {
139
+ projectRoot: ctx.projectRoot,
140
+ });
141
+ const durationMs = Math.round(performance.now() - start);
142
+ const verdict = verdictFor(action, exitCode);
143
+ logger.info({ event: payload.event, action, exitCode, durationMs, skipped }, 'hook dispatched');
144
+ results.push({
145
+ event: payload.event,
146
+ action,
147
+ exitCode,
148
+ skipped,
149
+ verdict,
150
+ durationMs,
151
+ });
152
+ }
153
+ });
154
+ return results;
155
+ }
156
+ export { ActionNotAllowedError, PathEscapeError };
157
+ //# sourceMappingURL=dispatcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatcher.js","sourceRoot":"","sources":["../../src/hooks/dispatcher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { HookEvent, HookEventPayload } from './types.js';
2
+ import type { AllowedActionKey } from './allowlist.js';
3
+ export interface IdempotencyContext {
4
+ readonly projectRoot: string;
5
+ readonly statePath?: string;
6
+ /** For on-task-complete: touched files (sorted internally for hashing). */
7
+ readonly touchedFiles?: readonly string[];
8
+ }
9
+ export declare function stateKey(event: HookEvent, action: AllowedActionKey, payload: HookEventPayload, opts?: {
10
+ touchedFiles?: readonly string[];
11
+ }): string;
12
+ export declare function shouldSkip(event: HookEvent, action: AllowedActionKey, payload: HookEventPayload, ctx: IdempotencyContext): Promise<boolean>;
13
+ export declare function markSeen(event: HookEvent, action: AllowedActionKey, payload: HookEventPayload, ctx: IdempotencyContext): Promise<void>;
14
+ //# sourceMappingURL=idempotency.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idempotency.d.ts","sourceRoot":"","sources":["../../src/hooks/idempotency.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,2EAA2E;IAC3E,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3C;AAyCD,wBAAgB,QAAQ,CACtB,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,gBAAgB,EACxB,OAAO,EAAE,gBAAgB,EACzB,IAAI,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAC1C,MAAM,CAGR;AAiBD,wBAAsB,UAAU,CAC9B,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,gBAAgB,EACxB,OAAO,EAAE,gBAAgB,EACzB,GAAG,EAAE,kBAAkB,GACtB,OAAO,CAAC,OAAO,CAAC,CAIlB;AAED,wBAAsB,QAAQ,CAC5B,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,gBAAgB,EACxB,OAAO,EAAE,gBAAgB,EACzB,GAAG,EAAE,kBAAkB,GACtB,OAAO,CAAC,IAAI,CAAC,CAMf"}