sequant 1.20.2 → 2.0.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 (137) hide show
  1. package/.claude-plugin/marketplace.json +2 -4
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +29 -9
  4. package/dist/bin/cli.js +25 -2
  5. package/dist/src/commands/doctor.js +42 -9
  6. package/dist/src/commands/init.d.ts +1 -0
  7. package/dist/src/commands/init.js +52 -0
  8. package/dist/src/commands/logs.d.ts +1 -0
  9. package/dist/src/commands/logs.js +18 -2
  10. package/dist/src/commands/run.d.ts +7 -0
  11. package/dist/src/commands/run.js +235 -68
  12. package/dist/src/commands/serve.d.ts +13 -0
  13. package/dist/src/commands/serve.js +131 -0
  14. package/dist/src/commands/stats.d.ts +1 -0
  15. package/dist/src/commands/stats.js +185 -26
  16. package/dist/src/commands/status.d.ts +2 -0
  17. package/dist/src/commands/status.js +99 -50
  18. package/dist/src/index.d.ts +2 -2
  19. package/dist/src/index.js +4 -1
  20. package/dist/src/lib/ac-parser.d.ts +2 -0
  21. package/dist/src/lib/ac-parser.js +12 -2
  22. package/dist/src/lib/assess-comment-parser.d.ts +137 -0
  23. package/dist/src/lib/assess-comment-parser.js +344 -0
  24. package/dist/src/lib/ci/config.d.ts +22 -0
  25. package/dist/src/lib/ci/config.js +134 -0
  26. package/dist/src/lib/ci/index.d.ts +12 -0
  27. package/dist/src/lib/ci/index.js +10 -0
  28. package/dist/src/lib/ci/inputs.d.ts +29 -0
  29. package/dist/src/lib/ci/inputs.js +103 -0
  30. package/dist/src/lib/ci/labels.d.ts +34 -0
  31. package/dist/src/lib/ci/labels.js +101 -0
  32. package/dist/src/lib/ci/outputs.d.ts +25 -0
  33. package/dist/src/lib/ci/outputs.js +84 -0
  34. package/dist/src/lib/ci/triggers.d.ts +9 -0
  35. package/dist/src/lib/ci/triggers.js +86 -0
  36. package/dist/src/lib/ci/types.d.ts +131 -0
  37. package/dist/src/lib/ci/types.js +47 -0
  38. package/dist/src/lib/mcp-config.d.ts +54 -0
  39. package/dist/src/lib/mcp-config.js +172 -0
  40. package/dist/src/lib/merge-check/index.js +6 -12
  41. package/dist/src/lib/merge-check/types.d.ts +20 -7
  42. package/dist/src/lib/merge-check/types.js +11 -0
  43. package/dist/src/lib/phase-signal.d.ts +3 -3
  44. package/dist/src/lib/phase-signal.js +5 -3
  45. package/dist/src/lib/settings.d.ts +52 -0
  46. package/dist/src/lib/settings.js +41 -0
  47. package/dist/src/lib/shutdown.d.ts +16 -5
  48. package/dist/src/lib/shutdown.js +32 -12
  49. package/dist/src/lib/solve-comment-parser.d.ts +9 -102
  50. package/dist/src/lib/solve-comment-parser.js +13 -248
  51. package/dist/src/lib/stacks.d.ts +8 -0
  52. package/dist/src/lib/stacks.js +34 -0
  53. package/dist/src/lib/system.js +3 -7
  54. package/dist/src/lib/test-tautology-detector.d.ts +10 -0
  55. package/dist/src/lib/test-tautology-detector.js +43 -4
  56. package/dist/src/lib/upstream/assessment.js +9 -59
  57. package/dist/src/lib/upstream/issues.js +12 -75
  58. package/dist/src/lib/version-check.d.ts +2 -2
  59. package/dist/src/lib/version-check.js +6 -3
  60. package/dist/src/lib/version.d.ts +4 -0
  61. package/dist/src/lib/version.js +25 -0
  62. package/dist/src/lib/workflow/batch-executor.d.ts +18 -86
  63. package/dist/src/lib/workflow/batch-executor.js +232 -55
  64. package/dist/src/lib/workflow/drivers/agent-driver.d.ts +56 -0
  65. package/dist/src/lib/workflow/drivers/agent-driver.js +8 -0
  66. package/dist/src/lib/workflow/drivers/aider.d.ts +18 -0
  67. package/dist/src/lib/workflow/drivers/aider.js +160 -0
  68. package/dist/src/lib/workflow/drivers/claude-code.d.ts +17 -0
  69. package/dist/src/lib/workflow/drivers/claude-code.js +165 -0
  70. package/dist/src/lib/workflow/drivers/index.d.ts +20 -0
  71. package/dist/src/lib/workflow/drivers/index.js +27 -0
  72. package/dist/src/lib/workflow/error-classifier.d.ts +16 -0
  73. package/dist/src/lib/workflow/error-classifier.js +90 -0
  74. package/dist/src/lib/workflow/log-writer.d.ts +6 -3
  75. package/dist/src/lib/workflow/log-writer.js +57 -27
  76. package/dist/src/lib/workflow/metrics-schema.d.ts +9 -9
  77. package/dist/src/lib/workflow/phase-detection.d.ts +23 -0
  78. package/dist/src/lib/workflow/phase-detection.js +45 -29
  79. package/dist/src/lib/workflow/phase-executor.d.ts +42 -3
  80. package/dist/src/lib/workflow/phase-executor.js +345 -220
  81. package/dist/src/lib/workflow/phase-mapper.d.ts +1 -1
  82. package/dist/src/lib/workflow/phase-mapper.js +7 -7
  83. package/dist/src/lib/workflow/platforms/github.d.ts +157 -0
  84. package/dist/src/lib/workflow/platforms/github.js +466 -0
  85. package/dist/src/lib/workflow/platforms/index.d.ts +17 -0
  86. package/dist/src/lib/workflow/platforms/index.js +25 -0
  87. package/dist/src/lib/workflow/platforms/platform-provider.d.ts +67 -0
  88. package/dist/src/lib/workflow/platforms/platform-provider.js +8 -0
  89. package/dist/src/lib/workflow/pr-status.d.ts +2 -4
  90. package/dist/src/lib/workflow/pr-status.js +3 -16
  91. package/dist/src/lib/workflow/qa-cache.d.ts +58 -0
  92. package/dist/src/lib/workflow/qa-cache.js +88 -0
  93. package/dist/src/lib/workflow/reconcile.d.ts +69 -0
  94. package/dist/src/lib/workflow/reconcile.js +290 -0
  95. package/dist/src/lib/workflow/ring-buffer.d.ts +17 -0
  96. package/dist/src/lib/workflow/ring-buffer.js +37 -0
  97. package/dist/src/lib/workflow/run-log-schema.d.ts +115 -24
  98. package/dist/src/lib/workflow/run-log-schema.js +47 -12
  99. package/dist/src/lib/workflow/run-reflect.js +1 -1
  100. package/dist/src/lib/workflow/state-cleanup.js +21 -0
  101. package/dist/src/lib/workflow/state-manager.d.ts +34 -3
  102. package/dist/src/lib/workflow/state-manager.js +278 -126
  103. package/dist/src/lib/workflow/state-schema.d.ts +34 -30
  104. package/dist/src/lib/workflow/state-schema.js +35 -25
  105. package/dist/src/lib/workflow/state-utils.d.ts +3 -1
  106. package/dist/src/lib/workflow/state-utils.js +1 -0
  107. package/dist/src/lib/workflow/types.d.ts +208 -6
  108. package/dist/src/lib/workflow/types.js +20 -1
  109. package/dist/src/lib/workflow/worktree-discovery.d.ts +1 -1
  110. package/dist/src/lib/workflow/worktree-discovery.js +6 -14
  111. package/dist/src/lib/workflow/worktree-manager.js +33 -51
  112. package/dist/src/mcp/index.d.ts +4 -0
  113. package/dist/src/mcp/index.js +4 -0
  114. package/dist/src/mcp/resources.d.ts +7 -0
  115. package/dist/src/mcp/resources.js +111 -0
  116. package/dist/src/mcp/run-registry.d.ts +34 -0
  117. package/dist/src/mcp/run-registry.js +42 -0
  118. package/dist/src/mcp/server.d.ts +12 -0
  119. package/dist/src/mcp/server.js +50 -0
  120. package/dist/src/mcp/tools/logs.d.ts +7 -0
  121. package/dist/src/mcp/tools/logs.js +149 -0
  122. package/dist/src/mcp/tools/run.d.ts +121 -0
  123. package/dist/src/mcp/tools/run.js +591 -0
  124. package/dist/src/mcp/tools/status.d.ts +7 -0
  125. package/dist/src/mcp/tools/status.js +127 -0
  126. package/package.json +10 -1
  127. package/templates/hooks/post-tool.sh +19 -8
  128. package/templates/hooks/pre-tool.sh +36 -49
  129. package/templates/mcp.json +6 -0
  130. package/templates/skills/assess/SKILL.md +354 -352
  131. package/templates/skills/exec/SKILL.md +64 -1
  132. package/templates/skills/fullsolve/SKILL.md +35 -4
  133. package/templates/skills/qa/SKILL.md +486 -9
  134. package/templates/skills/qa/scripts/quality-checks.sh +1 -1
  135. package/templates/skills/setup/SKILL.md +386 -0
  136. package/templates/skills/solve/SKILL.md +38 -664
  137. package/templates/skills/spec/SKILL.md +90 -31
@@ -0,0 +1,127 @@
1
+ /**
2
+ * sequant_status MCP tool
3
+ *
4
+ * Get current workflow state for an issue.
5
+ */
6
+ import { z } from "zod";
7
+ import { StateManager } from "../../lib/workflow/state-manager.js";
8
+ import { isRunning } from "../run-registry.js";
9
+ import { reconcileState, getNextActionHint, } from "../../lib/workflow/reconcile.js";
10
+ import { isExpired } from "../../lib/workflow/state-schema.js";
11
+ import { getSettings } from "../../lib/settings.js";
12
+ export function registerStatusTool(server) {
13
+ server.registerTool("sequant_status", {
14
+ title: "Sequant Status",
15
+ description: "Get the current workflow state, phase progress, and QA verdict for a tracked issue. " +
16
+ "Reconciles with GitHub on every call for accurate status. " +
17
+ "Use this to check whether an issue needs work before calling sequant_run. " +
18
+ "Returns isRunning: true when a sequant_run is actively executing — " +
19
+ "poll every 5-10 seconds during active runs for phase-level progress updates.",
20
+ annotations: {
21
+ readOnlyHint: true,
22
+ idempotentHint: true,
23
+ },
24
+ inputSchema: {
25
+ issue: z.number().describe("GitHub issue number to check status for"),
26
+ },
27
+ }, async ({ issue }) => {
28
+ if (!issue || issue <= 0) {
29
+ return {
30
+ content: [
31
+ {
32
+ type: "text",
33
+ text: JSON.stringify({
34
+ error: "INVALID_INPUT",
35
+ message: "A valid issue number is required",
36
+ }),
37
+ },
38
+ ],
39
+ isError: true,
40
+ };
41
+ }
42
+ try {
43
+ const stateManager = new StateManager();
44
+ // Reconcile state with GitHub before reading
45
+ const reconcileResult = await reconcileState({ stateManager });
46
+ stateManager.clearCache();
47
+ const issueState = await stateManager.getIssueState(issue);
48
+ // Respect TTL: treat expired resolved issues as not tracked
49
+ if (issueState) {
50
+ const settings = await getSettings();
51
+ const ttlDays = settings.run.resolvedIssueTTL ?? 7;
52
+ if (isExpired(issueState, ttlDays)) {
53
+ return {
54
+ content: [
55
+ {
56
+ type: "text",
57
+ text: JSON.stringify({
58
+ issue,
59
+ status: "not_tracked",
60
+ isRunning: isRunning(issue),
61
+ lastSynced: reconcileResult.lastSynced,
62
+ githubReachable: reconcileResult.githubReachable,
63
+ message: `Issue #${issue} was resolved and has expired (TTL: ${ttlDays} days)`,
64
+ }),
65
+ },
66
+ ],
67
+ };
68
+ }
69
+ }
70
+ if (!issueState) {
71
+ return {
72
+ content: [
73
+ {
74
+ type: "text",
75
+ text: JSON.stringify({
76
+ issue,
77
+ status: "not_tracked",
78
+ isRunning: isRunning(issue),
79
+ lastSynced: reconcileResult.lastSynced,
80
+ githubReachable: reconcileResult.githubReachable,
81
+ message: `Issue #${issue} is not currently tracked in workflow state`,
82
+ }),
83
+ },
84
+ ],
85
+ };
86
+ }
87
+ return {
88
+ content: [
89
+ {
90
+ type: "text",
91
+ text: JSON.stringify({
92
+ issue,
93
+ title: issueState.title,
94
+ status: issueState.status,
95
+ isRunning: isRunning(issue),
96
+ currentPhase: issueState.currentPhase,
97
+ phases: issueState.phases,
98
+ worktree: issueState.worktree,
99
+ pr: issueState.pr,
100
+ lastActivity: issueState.lastActivity,
101
+ nextAction: getNextActionHint(issueState),
102
+ lastSynced: reconcileResult.lastSynced,
103
+ githubReachable: reconcileResult.githubReachable,
104
+ warnings: reconcileResult.warnings
105
+ .filter((w) => w.issueNumber === issue)
106
+ .map((w) => w.description),
107
+ }),
108
+ },
109
+ ],
110
+ };
111
+ }
112
+ catch (error) {
113
+ return {
114
+ content: [
115
+ {
116
+ type: "text",
117
+ text: JSON.stringify({
118
+ error: "STATE_ERROR",
119
+ message: error instanceof Error ? error.message : String(error),
120
+ }),
121
+ },
122
+ ],
123
+ isError: true,
124
+ };
125
+ }
126
+ });
127
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sequant",
3
- "version": "1.20.2",
3
+ "version": "2.0.0",
4
4
  "description": "Quantize your development workflow - Sequential AI phases with quality gates",
5
5
  "type": "module",
6
6
  "bin": {
@@ -56,6 +56,14 @@
56
56
  "engines": {
57
57
  "node": ">=20.0.0"
58
58
  },
59
+ "peerDependencies": {
60
+ "@modelcontextprotocol/sdk": "^1.27.1"
61
+ },
62
+ "peerDependenciesMeta": {
63
+ "@modelcontextprotocol/sdk": {
64
+ "optional": true
65
+ }
66
+ },
59
67
  "dependencies": {
60
68
  "@anthropic-ai/claude-agent-sdk": "^0.2.11",
61
69
  "@hono/node-server": "^1.19.9",
@@ -70,6 +78,7 @@
70
78
  "inquirer": "^13.3.0",
71
79
  "open": "^11.0.0",
72
80
  "ora": "^9.3.0",
81
+ "p-limit": "^7.3.0",
73
82
  "yaml": "^2.7.0",
74
83
  "zod": "^4.3.5"
75
84
  },
@@ -38,24 +38,35 @@ else
38
38
  TOOL_OUTPUT=$(echo "$INPUT_JSON" | grep -oE '"tool_response"\s*:\s*\{[^}]+\}' | head -1)
39
39
  fi
40
40
 
41
- TIMING_LOG="/tmp/claude-timing.log"
42
- QUALITY_LOG="/tmp/claude-quality.log"
43
- TESTS_LOG="/tmp/claude-tests.log"
44
- PARALLEL_MARKER_PREFIX="/tmp/claude-parallel-"
41
+ _TMPDIR="${TMPDIR:-/tmp}"
42
+
43
+ # Use CLAUDE_PLUGIN_DATA for persistent logs (survives plugin updates)
44
+ if [[ -n "${CLAUDE_PLUGIN_DATA}" ]]; then
45
+ _LOG_DIR="${CLAUDE_PLUGIN_DATA}/logs"
46
+ mkdir -p "$_LOG_DIR"
47
+ else
48
+ _LOG_DIR="${_TMPDIR}"
49
+ fi
50
+
51
+ TIMING_LOG="${_LOG_DIR}/claude-timing.log"
52
+ QUALITY_LOG="${_LOG_DIR}/claude-quality.log"
53
+ TESTS_LOG="${_LOG_DIR}/claude-tests.log"
54
+ PARALLEL_MARKER_PREFIX="${_TMPDIR}/claude-parallel-"
45
55
 
46
56
  # === AGENT ID DETECTION ===
47
57
  # For parallel agents, detect group ID from marker files
48
- # Format: /tmp/claude-parallel-<group-id>.marker
58
+ # Format: ${_TMPDIR}/claude-parallel-<group-id>.marker
49
59
  AGENT_ID=""
50
60
  IS_PARALLEL_AGENT="false"
51
- for marker in "${PARALLEL_MARKER_PREFIX}"*.marker; do
52
- if [[ -f "$marker" ]]; then
61
+ # Find marker files using find (works in both bash and zsh)
62
+ while IFS= read -r marker; do
63
+ if [[ -n "$marker" && -f "$marker" ]]; then
53
64
  # Extract group ID from marker filename
54
65
  AGENT_ID=$(basename "$marker" | sed 's/claude-parallel-//' | sed 's/\.marker//')
55
66
  IS_PARALLEL_AGENT="true"
56
67
  break
57
68
  fi
58
- done
69
+ done < <(find "${_TMPDIR}" -maxdepth 1 -name "claude-parallel-*.marker" 2>/dev/null)
59
70
 
60
71
  # === TIMING END ===
61
72
  # Include agent ID in log format if available (AC-4)
@@ -33,20 +33,32 @@ else
33
33
  fi
34
34
  fi
35
35
 
36
- TIMING_LOG="/tmp/claude-timing.log"
37
- PARALLEL_MARKER_PREFIX="/tmp/claude-parallel-"
36
+ _TMPDIR="${TMPDIR:-/tmp}"
37
+
38
+ # Use CLAUDE_PLUGIN_DATA for persistent logs (survives plugin updates)
39
+ if [[ -n "${CLAUDE_PLUGIN_DATA}" ]]; then
40
+ _LOG_DIR="${CLAUDE_PLUGIN_DATA}/logs"
41
+ mkdir -p "$_LOG_DIR"
42
+ else
43
+ _LOG_DIR="${_TMPDIR}"
44
+ fi
45
+
46
+ TIMING_LOG="${_LOG_DIR}/claude-timing.log"
47
+ HOOK_LOG="${_LOG_DIR}/claude-hook.log"
48
+ PARALLEL_MARKER_PREFIX="${_TMPDIR}/claude-parallel-"
38
49
 
39
50
  # === AGENT ID DETECTION ===
40
51
  # For parallel agents, detect group ID from marker files
41
- # Format: /tmp/claude-parallel-<group-id>.marker
52
+ # Format: ${_TMPDIR}/claude-parallel-<group-id>.marker
42
53
  AGENT_ID=""
43
- for marker in "${PARALLEL_MARKER_PREFIX}"*.marker; do
44
- if [[ -f "$marker" ]]; then
54
+ # Find marker files using find (works in both bash and zsh)
55
+ while IFS= read -r marker; do
56
+ if [[ -n "$marker" && -f "$marker" ]]; then
45
57
  # Extract group ID from marker filename
46
58
  AGENT_ID=$(basename "$marker" | sed 's/claude-parallel-//' | sed 's/\.marker//')
47
59
  break
48
60
  fi
49
- done
61
+ done < <(find "${_TMPDIR}" -maxdepth 1 -name "claude-parallel-*.marker" 2>/dev/null)
50
62
 
51
63
  # === TIMING START ===
52
64
  # Include agent ID in log format if available (AC-4)
@@ -75,38 +87,38 @@ if [[ "$TOOL_NAME" == "Bash" ]]; then
75
87
  if ! echo "$TOOL_INPUT" | grep -qE '^gh (issue|pr) '; then
76
88
  # Pattern requires command to START with file reader (not match in quoted strings)
77
89
  if echo "$TOOL_INPUT" | grep -qE '^(cat|less|head|tail|more) .*\.(env|pem|key)'; then
78
- echo "HOOK_BLOCKED: Reading secret file" | tee -a /tmp/claude-hook.log >&2
90
+ echo "HOOK_BLOCKED: Reading secret file" | tee -a "$HOOK_LOG" >&2
79
91
  exit 2
80
92
  fi
81
93
 
82
94
  if echo "$TOOL_INPUT" | grep -qE '^(cat|less) .*~/\.(ssh|aws|gnupg|config/gh)'; then
83
- echo "HOOK_BLOCKED: Reading credential directory" | tee -a /tmp/claude-hook.log >&2
95
+ echo "HOOK_BLOCKED: Reading credential directory" | tee -a "$HOOK_LOG" >&2
84
96
  exit 2
85
97
  fi
86
98
  fi
87
99
 
88
100
  # Bare environment dump
89
101
  if echo "$TOOL_INPUT" | grep -qE '^(env|printenv|export)$'; then
90
- echo "HOOK_BLOCKED: Environment dump" | tee -a /tmp/claude-hook.log >&2
102
+ echo "HOOK_BLOCKED: Environment dump" | tee -a "$HOOK_LOG" >&2
91
103
  exit 2
92
104
  fi
93
105
 
94
106
  # Destructive system commands
95
107
  if echo "$TOOL_INPUT" | grep -qE 'sudo|rm -rf /|rm -rf ~|rm -rf \$HOME'; then
96
- echo "HOOK_BLOCKED: Destructive system command" | tee -a /tmp/claude-hook.log >&2
108
+ echo "HOOK_BLOCKED: Destructive system command" | tee -a "$HOOK_LOG" >&2
97
109
  exit 2
98
110
  fi
99
111
 
100
112
  # Deployment (should never happen in issue automation)
101
113
  if echo "$TOOL_INPUT" | grep -qE 'vercel (deploy|--prod)|terraform (apply|destroy)|kubectl (apply|delete)'; then
102
- echo "HOOK_BLOCKED: Deployment command" | tee -a /tmp/claude-hook.log >&2
114
+ echo "HOOK_BLOCKED: Deployment command" | tee -a "$HOOK_LOG" >&2
103
115
  exit 2
104
116
  fi
105
117
 
106
118
  # Force push
107
119
  # Pattern requires -f to be a standalone flag (not part of branch name like -fix)
108
120
  if echo "$TOOL_INPUT" | grep -qE 'git push.*(--force| -f($| ))'; then
109
- echo "HOOK_BLOCKED: Force push" | tee -a /tmp/claude-hook.log >&2
121
+ echo "HOOK_BLOCKED: Force push" | tee -a "$HOOK_LOG" >&2
110
122
  exit 2
111
123
  fi
112
124
 
@@ -149,14 +161,14 @@ if echo "$TOOL_INPUT" | grep -qE 'git reset.*(--hard|origin)'; then
149
161
  echo " git stash # save changes"
150
162
  echo " git merge --abort # cancel merge"
151
163
  echo " Or run directly in terminal (outside Claude Code) to bypass"
152
- } | tee -a /tmp/claude-hook.log >&2
164
+ } | tee -a "$HOOK_LOG" >&2
153
165
  exit 2
154
166
  fi
155
167
  fi
156
168
 
157
169
  # CI/CD triggers (automation shouldn't trigger more automation)
158
170
  if echo "$TOOL_INPUT" | grep -qE 'gh workflow run'; then
159
- echo "HOOK_BLOCKED: Workflow trigger" | tee -a /tmp/claude-hook.log >&2
171
+ echo "HOOK_BLOCKED: Workflow trigger" | tee -a "$HOOK_LOG" >&2
160
172
  exit 2
161
173
  fi
162
174
 
@@ -222,7 +234,7 @@ if [[ "${CLAUDE_HOOKS_SECURITY:-true}" != "false" ]]; then
222
234
  {
223
235
  echo "HOOK_BLOCKED: Hardcoded secret detected in staged changes"
224
236
  echo " Use 'git commit --no-verify' to bypass if this is a false positive"
225
- } | tee -a /tmp/claude-hook.log >&2
237
+ } | tee -a "$HOOK_LOG" >&2
226
238
  exit 2
227
239
  fi
228
240
 
@@ -233,7 +245,7 @@ if [[ "${CLAUDE_HOOKS_SECURITY:-true}" != "false" ]]; then
233
245
  echo "HOOK_BLOCKED: Sensitive file in commit (${STAGED_FILES})"
234
246
  echo " Files like .env, *.pem, *.key should not be committed"
235
247
  echo " Use 'git commit --no-verify' to bypass if this is intentional"
236
- } | tee -a /tmp/claude-hook.log >&2
248
+ } | tee -a "$HOOK_LOG" >&2
237
249
  exit 2
238
250
  fi
239
251
  fi
@@ -262,7 +274,7 @@ if [[ "$TOOL_NAME" == "Bash" ]] && echo "$TOOL_INPUT" | grep -qE 'git commit'; t
262
274
  fi
263
275
 
264
276
  if [[ "$CHANGES" -eq 0 ]]; then
265
- echo "HOOK_BLOCKED: No changes to commit. Stage files with 'git add' first." | tee -a /tmp/claude-hook.log >&2
277
+ echo "HOOK_BLOCKED: No changes to commit. Stage files with 'git add' first." | tee -a "$HOOK_LOG" >&2
266
278
  exit 2
267
279
  fi
268
280
  fi
@@ -271,7 +283,7 @@ fi
271
283
  # --- Worktree Validation (AC-8) ---
272
284
  # Warn (but don't block) when committing outside a feature worktree
273
285
  # This catches accidental commits to main repo during feature work
274
- QUALITY_LOG="/tmp/claude-quality.log"
286
+ QUALITY_LOG="${_LOG_DIR}/claude-quality.log"
275
287
  if [[ "$TOOL_NAME" == "Bash" ]] && echo "$TOOL_INPUT" | grep -qE 'git commit'; then
276
288
  CWD=$(pwd)
277
289
  if ! echo "$CWD" | grep -qE 'worktrees/feature/'; then
@@ -321,7 +333,7 @@ if [[ "$TOOL_NAME" == "Bash" ]] && echo "$TOOL_INPUT" | grep -qE 'git commit'; t
321
333
  fi
322
334
  echo " Types: feat|fix|docs|style|refactor|test|chore|ci|build|perf"
323
335
  echo " Got: $MSG"
324
- } | tee -a /tmp/claude-hook.log >&2
336
+ } | tee -a "$HOOK_LOG" >&2
325
337
  exit 2
326
338
  fi
327
339
  fi
@@ -356,7 +368,7 @@ if [[ "$TOOL_NAME" == "Edit" || "$TOOL_NAME" == "Write" ]]; then
356
368
  # AC-4 (Issue #31): Check worktree directory exists before path validation
357
369
  # Prevents Write tool from creating non-existent worktree directories
358
370
  if [[ ! -d "$EXPECTED_WORKTREE" ]]; then
359
- echo "HOOK_BLOCKED: Worktree does not exist: $EXPECTED_WORKTREE" | tee -a /tmp/claude-hook.log >&2
371
+ echo "HOOK_BLOCKED: Worktree does not exist: $EXPECTED_WORKTREE" | tee -a "$HOOK_LOG" >&2
360
372
  exit 2
361
373
  fi
362
374
 
@@ -385,7 +397,7 @@ if [[ "$TOOL_NAME" == "Edit" || "$TOOL_NAME" == "Write" ]]; then
385
397
  if [[ -n "${SEQUANT_ISSUE:-}" ]]; then
386
398
  echo " Issue: #$SEQUANT_ISSUE"
387
399
  fi
388
- } | tee -a /tmp/claude-hook.log >&2
400
+ } | tee -a "$HOOK_LOG" >&2
389
401
  exit 2
390
402
  fi
391
403
  fi
@@ -408,7 +420,7 @@ if [[ "${CLAUDE_HOOKS_FILE_LOCKING:-true}" == "true" ]]; then
408
420
 
409
421
  if [[ -n "$FILE_PATH" ]]; then
410
422
  # Create a lock file based on file path hash (handles special chars)
411
- LOCK_FILE="/tmp/claude-lock-$(echo "$FILE_PATH" | md5 -q 2>/dev/null || echo "$FILE_PATH" | md5sum | cut -d' ' -f1).lock"
423
+ LOCK_FILE="${_TMPDIR}/claude-lock-$(echo "$FILE_PATH" | md5 -q 2>/dev/null || echo "$FILE_PATH" | md5sum | cut -d' ' -f1).lock"
412
424
 
413
425
  # Try to acquire lock with 30 second timeout
414
426
  # Use a subshell to hold the lock during the tool execution
@@ -416,7 +428,7 @@ if [[ "${CLAUDE_HOOKS_FILE_LOCKING:-true}" == "true" ]]; then
416
428
  # macOS: use lockf
417
429
  exec 200>"$LOCK_FILE"
418
430
  if ! lockf -t 30 200 2>/dev/null; then
419
- echo "HOOK_BLOCKED: File locked by another agent: $FILE_PATH" | tee -a /tmp/claude-hook.log >&2
431
+ echo "HOOK_BLOCKED: File locked by another agent: $FILE_PATH" | tee -a "$HOOK_LOG" >&2
420
432
  exit 2
421
433
  fi
422
434
  # Lock will be released when the file descriptor closes (process exits)
@@ -424,7 +436,7 @@ if [[ "${CLAUDE_HOOKS_FILE_LOCKING:-true}" == "true" ]]; then
424
436
  # Linux: use flock
425
437
  exec 200>"$LOCK_FILE"
426
438
  if ! flock -w 30 200 2>/dev/null; then
427
- echo "HOOK_BLOCKED: File locked by another agent: $FILE_PATH" | tee -a /tmp/claude-hook.log >&2
439
+ echo "HOOK_BLOCKED: File locked by another agent: $FILE_PATH" | tee -a "$HOOK_LOG" >&2
428
440
  exit 2
429
441
  fi
430
442
  fi
@@ -433,31 +445,6 @@ if [[ "${CLAUDE_HOOKS_FILE_LOCKING:-true}" == "true" ]]; then
433
445
  fi
434
446
  fi
435
447
 
436
- # === PRE-MERGE WORKTREE CLEANUP ===
437
- # Auto-remove worktree before `gh pr merge` to prevent --delete-branch failure
438
- # The worktree locks the branch, causing merge to partially fail
439
- if [[ "$TOOL_NAME" == "Bash" ]] && echo "$TOOL_INPUT" | grep -qE 'gh pr merge'; then
440
- # Extract PR number from command
441
- PR_NUM=$(echo "$TOOL_INPUT" | grep -oE 'gh pr merge [0-9]+' | grep -oE '[0-9]+')
442
-
443
- if [[ -n "$PR_NUM" ]]; then
444
- # Get the branch name for this PR
445
- BRANCH_NAME=$(gh pr view "$PR_NUM" --json headRefName --jq '.headRefName' 2>/dev/null || true)
446
-
447
- if [[ -n "$BRANCH_NAME" ]]; then
448
- # Check if a worktree exists for this branch
449
- # Note: worktree line is 2 lines before branch line in porcelain output
450
- WORKTREE_PATH=$(git worktree list --porcelain 2>/dev/null | grep -B2 "branch refs/heads/$BRANCH_NAME" | grep "^worktree " | sed 's/^worktree //' || true)
451
-
452
- if [[ -n "$WORKTREE_PATH" && -d "$WORKTREE_PATH" ]]; then
453
- # Remove the worktree before merge proceeds
454
- git worktree remove "$WORKTREE_PATH" --force 2>/dev/null || true
455
- echo "PRE-MERGE: Removed worktree $WORKTREE_PATH for branch $BRANCH_NAME" >> /tmp/claude-hook.log
456
- fi
457
- fi
458
- fi
459
- fi
460
-
461
448
  # === ALLOW EVERYTHING ELSE ===
462
449
  # Slash commands need: git, npm, file edits, gh pr/issue, MCP tools
463
450
  exit 0
@@ -0,0 +1,6 @@
1
+ {
2
+ "sequant": {
3
+ "command": "npx",
4
+ "args": ["-y", "sequant@latest", "serve"]
5
+ }
6
+ }