@devrik-tools/claude-gates 0.2.1 → 0.3.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 (97) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/README.es.md +7 -6
  3. package/README.md +16 -7
  4. package/cli/index.mjs +4 -0
  5. package/cli/init.mjs +18 -1
  6. package/cli/install.mjs +26 -1
  7. package/package.json +5 -2
  8. package/plugins/gates/.claude-plugin/plugin.json +1 -1
  9. package/plugins/gates/hooks/gates/autonomous-mode/stop.mjs +78 -0
  10. package/plugins/gates/hooks/gates/bash-commands/index.mjs +11 -81
  11. package/plugins/gates/hooks/gates/block-remote-publish/index.mjs +140 -0
  12. package/plugins/gates/hooks/gates/dependency-skills/index.mjs +130 -126
  13. package/plugins/gates/hooks/gates/neutral-spanish/index.mjs +104 -79
  14. package/plugins/gates/hooks/gates/no-memory-dependency/index.mjs +138 -123
  15. package/plugins/gates/hooks/gates/stop-pending/index.mjs +6 -1
  16. package/plugins/gates/hooks/gates/test-after-implementation/index.mjs +124 -101
  17. package/plugins/gates/hooks/hooks.json +19 -0
  18. package/plugins/tasks/.claude-plugin/plugin.json +1 -1
  19. package/plugins/tasks/hooks/register-requests.mjs +7 -3
  20. package/registry.json +738 -793
  21. package/cli/__tests__/config.test.mjs +0 -113
  22. package/cli/__tests__/init-flags.test.mjs +0 -111
  23. package/cli/__tests__/install.test.mjs +0 -102
  24. package/cli/__tests__/materialize.test.mjs +0 -95
  25. package/cli/__tests__/registry-gates-consistency.test.mjs +0 -52
  26. package/cli/__tests__/selection.test.mjs +0 -200
  27. package/cli/__tests__/task.test.mjs +0 -124
  28. package/cli/__tests__/version-consistency.test.mjs +0 -44
  29. package/plugins/gates/hooks/__tests__/ask-adoption.test.mjs +0 -83
  30. package/plugins/gates/hooks/__tests__/doctor.test.mjs +0 -85
  31. package/plugins/gates/hooks/__tests__/wiring-check.test.mjs +0 -65
  32. package/plugins/gates/hooks/gates/audit-before-build/audit-before-build.edge.test.mjs +0 -83
  33. package/plugins/gates/hooks/gates/audit-before-build/test.mjs +0 -117
  34. package/plugins/gates/hooks/gates/autonomous-mode/autonomous.edge.test.mjs +0 -104
  35. package/plugins/gates/hooks/gates/autonomous-mode/test.mjs +0 -58
  36. package/plugins/gates/hooks/gates/bash-commands/bash-commands.edge.test.mjs +0 -165
  37. package/plugins/gates/hooks/gates/bash-commands/test.mjs +0 -126
  38. package/plugins/gates/hooks/gates/brief-before-delegate/brief-before-delegate.edge.test.mjs +0 -151
  39. package/plugins/gates/hooks/gates/brief-before-delegate/test.mjs +0 -111
  40. package/plugins/gates/hooks/gates/circuit-breaker/circuit-breaker.edge.test.mjs +0 -207
  41. package/plugins/gates/hooks/gates/circuit-breaker/test.mjs +0 -161
  42. package/plugins/gates/hooks/gates/dependency-skills/dependency-skills.edge.test.mjs +0 -69
  43. package/plugins/gates/hooks/gates/dependency-skills/test.mjs +0 -109
  44. package/plugins/gates/hooks/gates/diagnosis-before-patch/diagnosis-before-patch.edge.test.mjs +0 -68
  45. package/plugins/gates/hooks/gates/diagnosis-before-patch/test.mjs +0 -68
  46. package/plugins/gates/hooks/gates/feature-catalog/edge.edge.test.mjs +0 -83
  47. package/plugins/gates/hooks/gates/feature-catalog/test.mjs +0 -97
  48. package/plugins/gates/hooks/gates/force-parallel/test.mjs +0 -88
  49. package/plugins/gates/hooks/gates/forge-flow/forge-flow.edge.test.mjs +0 -155
  50. package/plugins/gates/hooks/gates/forge-flow/test.mjs +0 -135
  51. package/plugins/gates/hooks/gates/implementation-pipeline/edge.edge.test.mjs +0 -66
  52. package/plugins/gates/hooks/gates/implementation-pipeline/test.mjs +0 -86
  53. package/plugins/gates/hooks/gates/intent-flow/intent-flow.edge.test.mjs +0 -96
  54. package/plugins/gates/hooks/gates/intent-flow/test.mjs +0 -165
  55. package/plugins/gates/hooks/gates/lint-commit/test.mjs +0 -85
  56. package/plugins/gates/hooks/gates/mandatory-flow/edge.edge.test.mjs +0 -100
  57. package/plugins/gates/hooks/gates/mandatory-flow/test.mjs +0 -119
  58. package/plugins/gates/hooks/gates/neutral-spanish/neutral-spanish.edge.test.mjs +0 -80
  59. package/plugins/gates/hooks/gates/neutral-spanish/test.mjs +0 -65
  60. package/plugins/gates/hooks/gates/never-assume/never-assume.edge.test.mjs +0 -71
  61. package/plugins/gates/hooks/gates/never-assume/test.mjs +0 -95
  62. package/plugins/gates/hooks/gates/no-blocking/no-blocking.edge.test.mjs +0 -109
  63. package/plugins/gates/hooks/gates/no-blocking/test.mjs +0 -108
  64. package/plugins/gates/hooks/gates/no-memory-dependency/no-memory-dependency.edge.test.mjs +0 -83
  65. package/plugins/gates/hooks/gates/no-memory-dependency/test.mjs +0 -106
  66. package/plugins/gates/hooks/gates/no-reconfirm/no-reconfirm.edge.test.mjs +0 -91
  67. package/plugins/gates/hooks/gates/no-reconfirm/test.mjs +0 -131
  68. package/plugins/gates/hooks/gates/protected-paths/protected-paths.edge.test.mjs +0 -126
  69. package/plugins/gates/hooks/gates/protected-paths/test.mjs +0 -75
  70. package/plugins/gates/hooks/gates/recurrence-lock/recurrence-lock.edge.test.mjs +0 -102
  71. package/plugins/gates/hooks/gates/recurrence-lock/test.mjs +0 -99
  72. package/plugins/gates/hooks/gates/reuse-before-build/edge.edge.test.mjs +0 -94
  73. package/plugins/gates/hooks/gates/reuse-before-build/test.mjs +0 -101
  74. package/plugins/gates/hooks/gates/risk-level/risk-level.edge.test.mjs +0 -116
  75. package/plugins/gates/hooks/gates/risk-level/test.mjs +0 -153
  76. package/plugins/gates/hooks/gates/root-cause-first/root-cause-first.edge.test.mjs +0 -58
  77. package/plugins/gates/hooks/gates/root-cause-first/test.mjs +0 -73
  78. package/plugins/gates/hooks/gates/root-whitelist/root-whitelist.edge.test.mjs +0 -124
  79. package/plugins/gates/hooks/gates/root-whitelist/test.mjs +0 -80
  80. package/plugins/gates/hooks/gates/rule-skill-autodiscovery/rule-skill-autodiscovery.edge.test.mjs +0 -126
  81. package/plugins/gates/hooks/gates/rule-skill-autodiscovery/test.mjs +0 -78
  82. package/plugins/gates/hooks/gates/sdd-specs/edge.edge.test.mjs +0 -130
  83. package/plugins/gates/hooks/gates/sdd-specs/test.mjs +0 -163
  84. package/plugins/gates/hooks/gates/stop-pending/test.mjs +0 -114
  85. package/plugins/gates/hooks/gates/test-after-implementation/test-after-implementation.edge.test.mjs +0 -72
  86. package/plugins/gates/hooks/gates/test-after-implementation/test.mjs +0 -81
  87. package/plugins/gates/hooks/gates/test-matrix/edge.edge.test.mjs +0 -63
  88. package/plugins/gates/hooks/gates/test-matrix/test.mjs +0 -114
  89. package/plugins/gates/hooks/gates/tool-map/edge.edge.test.mjs +0 -62
  90. package/plugins/gates/hooks/gates/tool-map/test.mjs +0 -87
  91. package/plugins/gates/hooks/lib/__tests__/config.test.mjs +0 -173
  92. package/plugins/gates/hooks/lib/__tests__/hook-io.test.mjs +0 -223
  93. package/plugins/gates/hooks/lib/__tests__/signals.test.mjs +0 -114
  94. package/plugins/gates/hooks/lib/hook-io-config.edge.test.mjs +0 -189
  95. package/plugins/tasks/hooks/__tests__/register-requests.test.mjs +0 -100
  96. package/plugins/tasks/hooks/__tests__/session-tasks.test.mjs +0 -95
  97. package/plugins/tasks/hooks/lib/__tests__/task-store.test.mjs +0 -180
@@ -1,95 +0,0 @@
1
- // session-tasks — SessionStart hook. Spawned as a real child process with a JSON payload on
2
- // stdin, exactly as Claude Code invokes it, against a temp project (never this repo's own
3
- // .ai/ state).
4
-
5
- import assert from 'node:assert/strict';
6
- import { execFileSync } from 'node:child_process';
7
- import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs';
8
- import { tmpdir } from 'node:os';
9
- import { dirname, join } from 'node:path';
10
- import { test } from 'node:test';
11
- import { fileURLToPath } from 'node:url';
12
- import { openTaskStore, STATUS } from '../lib/task-store.mjs';
13
-
14
- const HOOK_PATH = join(
15
- dirname(fileURLToPath(import.meta.url)),
16
- '..',
17
- 'session-tasks.mjs',
18
- );
19
-
20
- function makeProject() {
21
- const project = mkdtempSync(join(tmpdir(), 'session-tasks-'));
22
- mkdirSync(join(project, '.git'));
23
- return project;
24
- }
25
-
26
- function runHook(cwd) {
27
- return execFileSync(process.execPath, [HOOK_PATH], {
28
- input: JSON.stringify({ cwd }),
29
- encoding: 'utf8',
30
- });
31
- }
32
-
33
- function sampleTask(id, status = STATUS.OPEN) {
34
- return {
35
- id,
36
- title: `task ${id}`,
37
- description: '',
38
- status,
39
- size: 'trivial',
40
- createdAt: new Date().toISOString(),
41
- messages: [],
42
- };
43
- }
44
-
45
- test('stays silent (empty stdout) when there are no active tasks', () => {
46
- const project = makeProject();
47
- const stdout = runHook(project);
48
- assert.equal(stdout, '');
49
- });
50
-
51
- test('lists active tasks (including in_forge, marked) when some exist', () => {
52
- const project = makeProject();
53
- const store = openTaskStore(project);
54
- store.add(sampleTask('t1'));
55
- store.add(sampleTask('t2', STATUS.IN_FORGE));
56
-
57
- const stdout = runHook(project);
58
- assert.notEqual(stdout, '');
59
- const payload = JSON.parse(stdout);
60
- const context = payload.hookSpecificOutput.additionalContext;
61
- assert.match(context, /t1/);
62
- assert.match(context, /t2/);
63
- assert.match(context, /in_forge/);
64
- assert.equal(payload.hookSpecificOutput.hookEventName, 'SessionStart');
65
- });
66
-
67
- test('a task closed (done/abandoned) does not appear in the session-start list', () => {
68
- const project = makeProject();
69
- const store = openTaskStore(project);
70
- store.add(sampleTask('t1'));
71
- store.add(sampleTask('t2'));
72
- store.close('t2', STATUS.ABANDONED, { reason: 'dropped' });
73
-
74
- const stdout = runHook(project);
75
- const context = JSON.parse(stdout).hookSpecificOutput.additionalContext;
76
- assert.match(context, /t1/);
77
- assert.doesNotMatch(context, /t2/);
78
- });
79
-
80
- test('listTasksOnSessionStart:false in project config silences the hook even with active tasks', () => {
81
- const project = makeProject();
82
- const store = openTaskStore(project);
83
- store.add(sampleTask('t1'));
84
-
85
- mkdirSync(join(project, '.ai'), { recursive: true });
86
- writeFileSync(
87
- join(project, '.ai', 'config.json'),
88
- JSON.stringify({
89
- gates: { listTasksOnSessionStart: false },
90
- }),
91
- );
92
-
93
- const stdout = runHook(project);
94
- assert.equal(stdout, '');
95
- });
@@ -1,180 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import {
3
- mkdtempSync,
4
- mkdirSync,
5
- existsSync,
6
- readFileSync,
7
- writeFileSync,
8
- } from 'node:fs';
9
- import { tmpdir } from 'node:os';
10
- import { join } from 'node:path';
11
- import { test } from 'node:test';
12
- import { openTaskStore, STATUS } from '../task-store.mjs';
13
-
14
- // A temp project with a .git marker, so the store anchors its .ai/tasks/ there.
15
- function makeProject() {
16
- const project = mkdtempSync(join(tmpdir(), 'task-store-'));
17
- mkdirSync(join(project, '.git'));
18
- return project;
19
- }
20
-
21
- function sampleTask(id) {
22
- return {
23
- id,
24
- title: `task ${id}`,
25
- description: 'desc',
26
- status: STATUS.OPEN,
27
- size: 'trivial',
28
- createdAt: new Date().toISOString(),
29
- messages: [],
30
- };
31
- }
32
-
33
- test('openTaskStore anchors at the nearest project marker (.ai/ counts, not only .git)', () => {
34
- // A project rooted only by .ai/ (no .git) must still resolve — the repo-less case.
35
- const project = mkdtempSync(join(tmpdir(), 'ai-only-'));
36
- mkdirSync(join(project, '.ai'));
37
- const nested = join(project, 'src', 'deep');
38
- mkdirSync(nested, { recursive: true });
39
- const store = openTaskStore(nested);
40
- assert.ok(
41
- store,
42
- 'store resolves from a nested dir under an .ai/-rooted project',
43
- );
44
- assert.equal(store.root, project);
45
- });
46
-
47
- test('add and active: a new task shows up in active', () => {
48
- const store = openTaskStore(makeProject());
49
- store.add(sampleTask('t1'));
50
- const active = store.active();
51
- assert.equal(active.length, 1);
52
- assert.equal(active[0].id, 't1');
53
- assert.deepEqual(store.history(), []);
54
- });
55
-
56
- test('update merges fields into the active task', () => {
57
- const store = openTaskStore(makeProject());
58
- store.add(sampleTask('t1'));
59
- store.update('t1', { status: STATUS.BLOCKED, note: 'waiting' });
60
- const task = store.active()[0];
61
- assert.equal(task.status, STATUS.BLOCKED);
62
- assert.equal(task.note, 'waiting');
63
- assert.equal(store.update('missing', {}), null);
64
- });
65
-
66
- test('close MOVES a task from active to history, never deletes', () => {
67
- const store = openTaskStore(makeProject());
68
- store.add(sampleTask('t1'));
69
- store.add(sampleTask('t2'));
70
-
71
- const { task: closed } = store.close('t1', STATUS.DONE, {
72
- reason: 'finished',
73
- evidence: 'npm test -> 12 passing',
74
- });
75
- assert.equal(closed.status, STATUS.DONE);
76
- assert.equal(closed.closeReason, 'finished');
77
- assert.equal(closed.evidence, 'npm test -> 12 passing');
78
- assert.ok(closed.closedAt);
79
-
80
- assert.deepEqual(
81
- store.active().map((task) => task.id),
82
- ['t2'],
83
- );
84
- assert.deepEqual(
85
- store.history().map((task) => task.id),
86
- ['t1'],
87
- );
88
- });
89
-
90
- test('closing as done WITHOUT evidence is refused; the task stays active', () => {
91
- const store = openTaskStore(makeProject());
92
- store.add(sampleTask('t1'));
93
- const result = store.close('t1', STATUS.DONE, { reason: 'trust me' });
94
- assert.ok(result.error, 'done without evidence must return an error');
95
- assert.match(result.error, /evidence/i);
96
- assert.equal(store.active().length, 1, 'the task is not moved to history');
97
- });
98
-
99
- test('abandon is a terminal close: moves to history with reason, no evidence needed', () => {
100
- const store = openTaskStore(makeProject());
101
- store.add(sampleTask('t1'));
102
- const { task } = store.close('t1', STATUS.ABANDONED, { reason: 'no longer needed' });
103
- assert.equal(task.status, STATUS.ABANDONED);
104
- assert.equal(store.active().length, 0);
105
- assert.equal(store.history()[0].status, STATUS.ABANDONED);
106
- assert.equal(store.history()[0].closeReason, 'no longer needed');
107
- });
108
-
109
- test('promoteToForge links a run and keeps the task active as in_forge', () => {
110
- const store = openTaskStore(makeProject());
111
- store.add(sampleTask('t1'));
112
- const promoted = store.promoteToForge('t1', 'run-42');
113
- assert.equal(promoted.status, STATUS.IN_FORGE);
114
- assert.equal(promoted.forgeRunId, 'run-42');
115
- assert.equal(store.active().length, 1, 'in_forge is still active, not closed');
116
- });
117
-
118
- test('close rejects a non-terminal status and an unknown id', () => {
119
- const store = openTaskStore(makeProject());
120
- store.add(sampleTask('t1'));
121
- assert.ok(store.close('t1', STATUS.OPEN, { reason: 'x' }).error);
122
- assert.ok(store.close('missing', STATUS.DONE, { evidence: 'x' }).error);
123
- assert.equal(store.active().length, 1);
124
- });
125
-
126
- test('counter persists and reads back', () => {
127
- const store = openTaskStore(makeProject());
128
- assert.equal(store.counter(), 0);
129
- store.setCounter(7);
130
- assert.equal(store.counter(), 7);
131
- });
132
-
133
- test('history is append-only across multiple closes', () => {
134
- const store = openTaskStore(makeProject());
135
- store.add(sampleTask('t1'));
136
- store.add(sampleTask('t2'));
137
- store.close('t1', STATUS.DONE, { reason: 'a', evidence: 'tests green' });
138
- store.close('t2', STATUS.ABANDONED, { reason: 'b' });
139
- assert.deepEqual(
140
- store.history().map((task) => task.id),
141
- ['t1', 't2'],
142
- );
143
- });
144
-
145
- test('a corrupt active.json is treated as empty, then healed by a write', () => {
146
- const project = makeProject();
147
- const store = openTaskStore(project);
148
- mkdirSync(join(project, '.ai', 'tasks'), { recursive: true });
149
- const path = join(project, '.ai', 'tasks', 'active.json');
150
- writeFileSync(path, '{broken');
151
- assert.deepEqual(store.active(), []);
152
- store.add(sampleTask('t1'));
153
- assert.ok(existsSync(path));
154
- assert.equal(JSON.parse(readFileSync(path, 'utf8')).tasks.length, 1);
155
- });
156
-
157
- test('a UTF-8 BOM on active.json/counter.json does not make the store treat them as empty/corrupt', () => {
158
- const project = makeProject();
159
- const tasksDir = join(project, '.ai', 'tasks');
160
- mkdirSync(tasksDir, { recursive: true });
161
-
162
- const bom = '';
163
- writeFileSync(
164
- join(tasksDir, 'active.json'),
165
- bom + JSON.stringify({ tasks: [sampleTask('t1')] }),
166
- );
167
- writeFileSync(join(tasksDir, 'counter.json'), bom + JSON.stringify({ count: 3 }));
168
-
169
- const store = openTaskStore(project);
170
- assert.deepEqual(
171
- store.active().map((task) => task.id),
172
- ['t1'],
173
- 'a BOM-prefixed active.json must still be read as its real content, not as empty',
174
- );
175
- assert.equal(
176
- store.counter(),
177
- 3,
178
- 'a BOM-prefixed counter.json must still be read as its real value, not 0',
179
- );
180
- });