aw-ecc 1.4.23 → 1.4.31

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 (73) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.cursor/INSTALL.md +9 -0
  3. package/.cursor/hooks/adapter.js +34 -3
  4. package/.cursor/hooks/aw-phase-definitions.js +11 -0
  5. package/.cursor/hooks/before-submit-prompt.sh +17 -0
  6. package/.cursor/hooks/session-start.sh +36 -0
  7. package/.cursor/hooks/shared/aw-phase-definitions.js +1 -19
  8. package/.cursor/hooks/shared/aw-phase-runner.js +38 -2
  9. package/.cursor/hooks/shared/user-prompt-submit.sh +40 -60
  10. package/.cursor/hooks.json +15 -15
  11. package/.cursor/rules/common-aw-routing.md +43 -0
  12. package/.cursor/skills/api-and-interface-design/SKILL.md +75 -0
  13. package/.cursor/skills/aw-brainstorm/SKILL.md +115 -0
  14. package/.cursor/skills/aw-build/SKILL.md +152 -0
  15. package/.cursor/skills/aw-build/evals/build-stage-cases.json +28 -0
  16. package/.cursor/skills/aw-debug/SKILL.md +49 -0
  17. package/.cursor/skills/aw-deploy/SKILL.md +101 -0
  18. package/.cursor/skills/aw-deploy/evals/deploy-stage-cases.json +32 -0
  19. package/.cursor/skills/aw-execute/SKILL.md +47 -0
  20. package/.cursor/skills/aw-execute/references/mode-code.md +47 -0
  21. package/.cursor/skills/aw-execute/references/mode-docs.md +28 -0
  22. package/.cursor/skills/aw-execute/references/mode-infra.md +44 -0
  23. package/.cursor/skills/aw-execute/references/mode-migration.md +58 -0
  24. package/.cursor/skills/aw-execute/references/worker-implementer.md +26 -0
  25. package/.cursor/skills/aw-execute/references/worker-parallel-worker.md +23 -0
  26. package/.cursor/skills/aw-execute/references/worker-quality-reviewer.md +23 -0
  27. package/.cursor/skills/aw-execute/references/worker-spec-reviewer.md +23 -0
  28. package/.cursor/skills/aw-execute/scripts/build-worker-bundle.js +229 -0
  29. package/.cursor/skills/aw-finish/SKILL.md +111 -0
  30. package/.cursor/skills/aw-investigate/SKILL.md +109 -0
  31. package/.cursor/skills/aw-plan/SKILL.md +368 -0
  32. package/.cursor/skills/aw-prepare/SKILL.md +118 -0
  33. package/.cursor/skills/aw-review/SKILL.md +118 -0
  34. package/.cursor/skills/aw-ship/SKILL.md +115 -0
  35. package/.cursor/skills/aw-spec/SKILL.md +104 -0
  36. package/.cursor/skills/aw-tasks/SKILL.md +138 -0
  37. package/.cursor/skills/aw-test/SKILL.md +118 -0
  38. package/.cursor/skills/aw-verify/SKILL.md +51 -0
  39. package/.cursor/skills/aw-yolo/SKILL.md +111 -0
  40. package/.cursor/skills/browser-testing-with-devtools/SKILL.md +81 -0
  41. package/.cursor/skills/ci-cd-and-automation/SKILL.md +71 -0
  42. package/.cursor/skills/code-simplification/SKILL.md +74 -0
  43. package/.cursor/skills/context-engineering/SKILL.md +74 -0
  44. package/.cursor/skills/deprecation-and-migration/SKILL.md +75 -0
  45. package/.cursor/skills/documentation-and-adrs/SKILL.md +75 -0
  46. package/.cursor/skills/frontend-ui-engineering/SKILL.md +68 -0
  47. package/.cursor/skills/git-workflow-and-versioning/SKILL.md +75 -0
  48. package/.cursor/skills/idea-refine/SKILL.md +84 -0
  49. package/.cursor/skills/incremental-implementation/SKILL.md +75 -0
  50. package/.cursor/skills/performance-optimization/SKILL.md +77 -0
  51. package/.cursor/skills/security-and-hardening/SKILL.md +70 -0
  52. package/.cursor/skills/using-aw-skills/SKILL.md +290 -0
  53. package/.cursor/skills/using-aw-skills/evals/skill-trigger-cases.tsv +25 -0
  54. package/.cursor/skills/using-aw-skills/evals/test-skill-triggers.sh +171 -0
  55. package/.cursor/skills/using-aw-skills/hooks/hooks.json +9 -0
  56. package/.cursor/skills/using-aw-skills/hooks/session-start.sh +67 -0
  57. package/.cursor/skills/using-platform-skills/SKILL.md +163 -0
  58. package/.cursor/skills/using-platform-skills/evals/platform-selection-cases.json +52 -0
  59. package/.opencode/package.json +1 -1
  60. package/package.json +5 -1
  61. package/scripts/cursor-aw-home/hooks.json +15 -15
  62. package/scripts/cursor-aw-hooks/adapter.js +34 -3
  63. package/scripts/cursor-aw-hooks/aw-phase-definitions.js +11 -0
  64. package/scripts/cursor-aw-hooks/before-submit-prompt.sh +17 -0
  65. package/scripts/cursor-aw-hooks/session-start.sh +36 -0
  66. package/scripts/hooks/session-start-rules-context.sh +8 -1
  67. package/scripts/hooks/shared/aw-phase-definitions.js +1 -19
  68. package/scripts/hooks/shared/aw-phase-runner.js +38 -2
  69. package/scripts/hooks/shared/user-prompt-submit.sh +40 -60
  70. package/scripts/lib/cursor-aw-hook-files.js +2 -0
  71. package/scripts/lib/cursor-hook-config.js +47 -15
  72. package/scripts/lib/install-executor.js +7 -0
  73. package/scripts/lib/install-targets/cursor-project.js +7 -0
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  const { execFileSync } = require('child_process');
9
+ const fs = require('fs');
9
10
  const path = require('path');
10
11
 
11
12
  const MAX_STDIN = 1024 * 1024;
@@ -22,7 +23,18 @@ function readStdin() {
22
23
  }
23
24
 
24
25
  function getPluginRoot() {
25
- return path.resolve(__dirname, '..', '..');
26
+ const homeStyleRoot = path.resolve(__dirname, '..');
27
+ const repoStyleRoot = path.resolve(__dirname, '..', '..');
28
+
29
+ if (
30
+ fs.existsSync(path.join(repoStyleRoot, 'package.json'))
31
+ && fs.existsSync(path.join(repoStyleRoot, 'scripts', 'hooks'))
32
+ && fs.existsSync(path.join(repoStyleRoot, '.cursor', 'hooks'))
33
+ ) {
34
+ return repoStyleRoot;
35
+ }
36
+
37
+ return homeStyleRoot;
26
38
  }
27
39
 
28
40
  function transformToClaude(cursorInput, overrides = {}) {
@@ -83,12 +95,31 @@ function runManagedCommand(command, args, stdinData) {
83
95
  }
84
96
 
85
97
  function runExistingHook(scriptName, stdinData) {
86
- const scriptPath = path.join(getPluginRoot(), 'scripts', 'hooks', scriptName);
98
+ const root = getPluginRoot();
99
+ const candidates = [
100
+ path.join(root, 'scripts', 'hooks', scriptName),
101
+ path.join(root, 'hooks', scriptName),
102
+ ];
103
+ const scriptPath = candidates.find(candidate => fs.existsSync(candidate)) || candidates[0];
87
104
  return runManagedCommand('node', [scriptPath], stdinData);
88
105
  }
89
106
 
90
107
  function runManagedShellHook(relativeScriptPath, stdinData) {
91
- const scriptPath = path.join(getPluginRoot(), relativeScriptPath);
108
+ const root = getPluginRoot();
109
+ const normalized = String(relativeScriptPath || '').replace(/\\/g, '/');
110
+ const fallbackCandidates = [
111
+ normalized,
112
+ normalized.replace(/^\.cursor\//, ''),
113
+ normalized.replace(/^scripts\//, ''),
114
+ normalized.replace(/^hooks\//, ''),
115
+ path.posix.join('.cursor', normalized),
116
+ path.posix.join('scripts', normalized),
117
+ path.posix.join('hooks', normalized),
118
+ ];
119
+ const scriptPath = fallbackCandidates
120
+ .map(candidate => path.join(root, candidate))
121
+ .find(candidate => fs.existsSync(candidate))
122
+ || path.join(root, normalized);
92
123
  return runManagedCommand('bash', [scriptPath], stdinData);
93
124
  }
94
125
 
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+
3
+ const {
4
+ SHARED_AW_PHASE_STEPS: CURSOR_AW_PHASE_STEPS,
5
+ getSharedAwPhaseSteps: getCursorAwPhaseSteps,
6
+ } = require('./shared/aw-phase-definitions');
7
+
8
+ module.exports = {
9
+ CURSOR_AW_PHASE_STEPS,
10
+ getCursorAwPhaseSteps,
11
+ };
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ RAW="$(cat || true)"
5
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
6
+
7
+ RESULT="$(printf '%s' "$RAW" | node "$SCRIPT_DIR/before-submit-prompt.js" 2>&1 1>/tmp/aw_cursor_before_submit_stdout.$$)"
8
+ STATUS=$?
9
+ STDOUT_CONTENT="$(cat /tmp/aw_cursor_before_submit_stdout.$$ 2>/dev/null || true)"
10
+ rm -f /tmp/aw_cursor_before_submit_stdout.$$
11
+
12
+ if [ -n "$RESULT" ]; then
13
+ printf '%s\n' "$RESULT" >&2
14
+ fi
15
+
16
+ printf '%s' "${STDOUT_CONTENT:-$RAW}"
17
+ exit "$STATUS"
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ RAW="$(cat || true)"
5
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
6
+
7
+ RESULT="$(printf '%s' "$RAW" | bash "$SCRIPT_DIR/shared/session-start.sh")"
8
+
9
+ if [ -z "$RESULT" ]; then
10
+ printf '%s' "$RAW"
11
+ exit 0
12
+ fi
13
+
14
+ JSON_CONTEXT="$(
15
+ printf '%s' "$RESULT" | node -e '
16
+ let input = "";
17
+ process.stdin.setEncoding("utf8");
18
+ process.stdin.on("data", chunk => input += chunk);
19
+ process.stdin.on("end", () => {
20
+ try {
21
+ const parsed = JSON.parse(input || "{}");
22
+ const context =
23
+ (parsed && parsed.hookSpecificOutput && parsed.hookSpecificOutput.additionalContext) ||
24
+ parsed.additional_context ||
25
+ "";
26
+ if (typeof context === "string" && context.trim()) {
27
+ process.stdout.write(JSON.stringify({ additional_context: context }, null, 2) + "\n");
28
+ return;
29
+ }
30
+ } catch {}
31
+ process.stdout.write(process.env.ECC_FALLBACK_RAW || "");
32
+ });
33
+ ' ECC_FALLBACK_RAW="$RAW"
34
+ )"
35
+
36
+ printf '%s' "$JSON_CONTEXT"
@@ -2,4 +2,11 @@
2
2
  set -euo pipefail
3
3
 
4
4
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
5
- exec bash "$SCRIPT_DIR/shared/user-prompt-submit.sh"
5
+ REMINDER="$(bash "$SCRIPT_DIR/shared/user-prompt-submit.sh" || true)"
6
+
7
+ if [[ -z "${REMINDER}" ]]; then
8
+ exit 0
9
+ fi
10
+
11
+ JSON_REMINDER="$(printf '%s' "$REMINDER" | python3 -c 'import json, sys; print(json.dumps(sys.stdin.read()))')"
12
+ printf '{"hookSpecificOutput":{"hookEventName":"UserPromptSubmit","additionalContext":%s}}\n' "$JSON_REMINDER"
@@ -6,6 +6,7 @@ const SHARED_AW_PHASE_STEPS = Object.freeze({
6
6
  runner: 'shell',
7
7
  relativeScriptPath: '.cursor/hooks/shared/session-start.sh',
8
8
  payloadMode: 'raw',
9
+ outputMode: 'cursor-session-start',
9
10
  },
10
11
  ],
11
12
  'user-prompt-submit': [
@@ -157,24 +158,6 @@ const SHARED_AW_PHASE_STEPS = Object.freeze({
157
158
  ],
158
159
  });
159
160
 
160
- /**
161
- * Canonical string constants for all AW hook phase names.
162
- * Use these instead of raw string literals to get typo-safety and IDE
163
- * auto-complete in files that call runNamedCursorAwPhase() or equivalent.
164
- */
165
- const PHASE_NAMES = Object.freeze({
166
- SESSION_START: 'session-start',
167
- USER_PROMPT_SUBMIT: 'user-prompt-submit',
168
- PRE_TOOL_USE_SHELL: 'pre-tool-use-shell',
169
- PRE_TOOL_USE_MCP: 'pre-tool-use-mcp',
170
- POST_TOOL_USE_SHELL: 'post-tool-use-shell',
171
- POST_TOOL_USE_FILE_EDIT: 'post-tool-use-file-edit',
172
- POST_TOOL_USE_MCP: 'post-tool-use-mcp',
173
- PRE_COMPACT: 'pre-compact',
174
- SESSION_END: 'session-end',
175
- STOP: 'stop',
176
- });
177
-
178
161
  function getSharedAwPhaseSteps(phaseName) {
179
162
  const steps = SHARED_AW_PHASE_STEPS[phaseName];
180
163
  if (!steps) {
@@ -184,7 +167,6 @@ function getSharedAwPhaseSteps(phaseName) {
184
167
  }
185
168
 
186
169
  module.exports = {
187
- PHASE_NAMES,
188
170
  SHARED_AW_PHASE_STEPS,
189
171
  getSharedAwPhaseSteps,
190
172
  };
@@ -26,6 +26,37 @@ function executeStep(runtime, step, payload) {
26
26
  return runtime.runManagedNodeHook(step.relativeScriptPath, payload);
27
27
  }
28
28
 
29
+ function formatCursorSessionStartOutput(stdout, fallbackRaw) {
30
+ if (typeof stdout !== 'string' || stdout.trim() === '') {
31
+ return fallbackRaw;
32
+ }
33
+
34
+ try {
35
+ const payload = JSON.parse(stdout);
36
+ const additionalContext = payload?.hookSpecificOutput?.additionalContext
37
+ || payload?.additional_context;
38
+
39
+ if (typeof additionalContext === 'string' && additionalContext.trim() !== '') {
40
+ return `${JSON.stringify({ additional_context: additionalContext }, null, 2)}\n`;
41
+ }
42
+ } catch {}
43
+
44
+ return fallbackRaw;
45
+ }
46
+
47
+ function resolveStepOutput(step, execution, fallbackRaw) {
48
+ if (!execution) {
49
+ return null;
50
+ }
51
+
52
+ switch (step.outputMode) {
53
+ case 'cursor-session-start':
54
+ return formatCursorSessionStartOutput(execution.stdout, fallbackRaw);
55
+ default:
56
+ return null;
57
+ }
58
+ }
59
+
29
60
  async function runSharedAwPhase({ raw, steps, deps = {} }) {
30
61
  const runtime = {
31
62
  transformToClaude: deps.transformToClaude,
@@ -43,6 +74,7 @@ async function runSharedAwPhase({ raw, steps, deps = {} }) {
43
74
 
44
75
  const needsClaudeInput = steps.some(step => (step.payloadMode || 'claude') === 'claude');
45
76
  const claudeInput = needsClaudeInput ? runtime.transformToClaude(parsedInput) : null;
77
+ let result = raw;
46
78
 
47
79
  for (const step of steps) {
48
80
  if (!shouldRunStep(step, runtime)) {
@@ -50,10 +82,14 @@ async function runSharedAwPhase({ raw, steps, deps = {} }) {
50
82
  }
51
83
 
52
84
  const payload = resolveStepPayload(step, raw, parsedInput, claudeInput);
53
- await executeStep(runtime, step, payload);
85
+ const execution = await executeStep(runtime, step, payload);
86
+ const nextResult = resolveStepOutput(step, execution, raw);
87
+ if (typeof nextResult === 'string') {
88
+ result = nextResult;
89
+ }
54
90
  }
55
91
 
56
- return raw;
92
+ return result;
57
93
  }
58
94
 
59
95
  module.exports = {
@@ -1,71 +1,51 @@
1
1
  #!/usr/bin/env bash
2
- # UserPromptSubmit hook — emit a small, reliable AW routing reminder.
3
- #
4
- # This hook intentionally stays simple:
5
- # - drain stdin so the harness can always finish writing payloads
6
- # - remind the model to re-apply using-aw-skills
7
- # - point at AGENTS.md and the canonical .aw_rules/platform tree when present
8
-
9
2
  set -euo pipefail
10
3
 
11
- cat >/dev/null || true
12
-
13
- # On Windows (Git Bash / MSYS), pwd returns POSIX paths (/tmp/...)
14
- # but callers use native Windows paths (C:\Users\...).
15
- # Convert via cygpath when available so paths match the caller's format.
16
- if command -v cygpath >/dev/null 2>&1; then
17
- CWD="$(cygpath -w "$(pwd)")"
18
- else
19
- CWD="$(pwd)"
20
- fi
21
- AGENTS_PATH="$CWD/AGENTS.md"
22
- RULES_ROOT=""
23
-
24
- for candidate in \
25
- "$CWD/.aw_rules/platform" \
26
- "$HOME/.aw_rules/platform" \
27
- "$HOME/.aw/.aw_rules/platform"
28
- do
29
- if [ -d "$candidate" ]; then
30
- RULES_ROOT="$candidate"
31
- break
32
- fi
33
- done
34
-
35
- RULE_REFS=()
4
+ RAW="$(cat || true)"
5
+
6
+ extract_workspace_root() {
7
+ printf '%s' "$1" | sed -n 's/.*"workspace_roots"[[:space:]]*:[[:space:]]*\[[[:space:]]*"\([^"]*\)".*/\1/p' | head -n 1
8
+ }
9
+
10
+ extract_cwd() {
11
+ printf '%s' "$1" | sed -n 's/.*"cwd"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n 1
12
+ }
13
+
14
+ resolve_rules_root() {
15
+ local root="${1:-}"
16
+ local cwd_dir
17
+ cwd_dir="$(pwd)"
18
+ local candidate
19
+
20
+ for candidate in \
21
+ "$root/.aw/.aw_rules/platform" \
22
+ "$root/.aw_rules/platform" \
23
+ "$cwd_dir/.aw/.aw_rules/platform" \
24
+ "$cwd_dir/.aw_rules/platform" \
25
+ "$HOME/.aw/.aw_rules/platform" \
26
+ "$HOME/.aw_rules/platform" \
27
+ "$HOME/.aw/.aw_registry/.aw_rules/platform"
28
+ do
29
+ if [ -n "$candidate" ] && [ -d "$candidate" ]; then
30
+ printf '%s' "$candidate"
31
+ return 0
32
+ fi
33
+ done
36
34
 
37
- if [ -f "$AGENTS_PATH" ]; then
38
- RULE_REFS+=("$AGENTS_PATH")
39
- fi
35
+ return 1
36
+ }
40
37
 
41
- if [ -n "$RULES_ROOT" ]; then
42
- if [ -f "$RULES_ROOT/universal/AGENTS.md" ]; then
43
- RULE_REFS+=("$RULES_ROOT/universal/AGENTS.md")
44
- fi
45
- if [ -f "$RULES_ROOT/security/AGENTS.md" ]; then
46
- RULE_REFS+=("$RULES_ROOT/security/AGENTS.md")
47
- fi
48
- RULE_SCOPE="Applicable domain rules live under $RULES_ROOT."
49
- else
50
- RULE_SCOPE="Applicable domain rules live under .aw_rules/platform when synced."
38
+ WORKSPACE_ROOT="$(extract_workspace_root "$RAW")"
39
+ if [ -z "$WORKSPACE_ROOT" ]; then
40
+ WORKSPACE_ROOT="$(extract_cwd "$RAW")"
51
41
  fi
52
42
 
53
- if [ "${#RULE_REFS[@]}" -gt 0 ]; then
54
- RULE_PATHS=""
55
- while IFS= read -r line; do
56
- if [ -z "$RULE_PATHS" ]; then
57
- RULE_PATHS="$line"
58
- else
59
- RULE_PATHS="$RULE_PATHS, $line"
60
- fi
61
- done < <(printf '%s\n' "${RULE_REFS[@]}" | awk '!seen[$0]++')
62
- else
63
- RULE_PATHS="AGENTS.md"
43
+ RULES_ROOT="$(resolve_rules_root "$WORKSPACE_ROOT" || true)"
44
+ if [ -z "$RULES_ROOT" ]; then
45
+ RULES_ROOT="$HOME/.aw_rules/platform"
64
46
  fi
65
47
 
66
48
  cat <<EOF
67
- [AW Router reminder] Re-apply using-aw-skills for this prompt and select the smallest correct AW route and stage skill before substantive work.
68
- [Rules reminder] Start with $RULE_PATHS. Universal and security rules always apply. $RULE_SCOPE
49
+ [AW Router reminder] Re-apply using-aw-skills and select the smallest correct AW route before substantive work.
50
+ [Rule reminder] Read ${RULES_ROOT}/universal/AGENTS.md and ${RULES_ROOT}/security/AGENTS.md, then the touched domain AGENTS.md plus references/ on demand.
69
51
  EOF
70
-
71
- exit 0
@@ -9,9 +9,11 @@ const CURSOR_AW_HOOK_FILES = Object.freeze([
9
9
  'before-mcp-execution.js',
10
10
  'before-shell-execution.js',
11
11
  'before-submit-prompt.js',
12
+ 'before-submit-prompt.sh',
12
13
  'pre-compact.js',
13
14
  'session-end.js',
14
15
  'session-start.js',
16
+ 'session-start.sh',
15
17
  'stop.js',
16
18
  ]);
17
19
 
@@ -1,16 +1,46 @@
1
1
  const { getCursorMappedEventNames } = require('./aw-hook-contract');
2
2
 
3
+ function buildManagedCursorHookCommand(scriptFileName) {
4
+ const scriptName = String(scriptFileName || '').replace(/\\/g, '/');
5
+ const launcher = [
6
+ "const fs=require('fs')",
7
+ "const path=require('path')",
8
+ "const os=require('os')",
9
+ `const scriptName=${JSON.stringify(scriptName)}`,
10
+ "const candidates=[path.join(process.cwd(), '.cursor', 'hooks', scriptName),path.join(os.homedir(), '.cursor', 'hooks', scriptName)]",
11
+ "const target=candidates.find(candidate => fs.existsSync(candidate))",
12
+ "if(!target) process.exit(0)",
13
+ "require(target)",
14
+ ].join(';');
15
+
16
+ return `node -e ${JSON.stringify(launcher)}`;
17
+ }
18
+
19
+ function buildManagedCursorShellCommand(scriptFileName) {
20
+ const scriptName = String(scriptFileName || '').replace(/\\/g, '/');
21
+ const command = [
22
+ 'bash -lc',
23
+ JSON.stringify(
24
+ `for candidate in "$PWD/.cursor/hooks/${scriptName}" "$HOME/.cursor/hooks/${scriptName}"; do ` +
25
+ `if [ -f "$candidate" ]; then exec bash "$candidate"; fi; ` +
26
+ 'done; exit 0'
27
+ ),
28
+ ].join(' ');
29
+
30
+ return command;
31
+ }
32
+
3
33
  const CURSOR_HOOK_ENTRIES = Object.freeze({
4
34
  sessionStart: [
5
35
  {
6
- command: 'node .cursor/hooks/session-start.js',
36
+ command: buildManagedCursorShellCommand('session-start.sh'),
7
37
  event: 'sessionStart',
8
38
  description: 'Load previous context and detect environment',
9
39
  },
10
40
  ],
11
41
  sessionEnd: [
12
42
  {
13
- command: 'node .cursor/hooks/session-end.js',
43
+ command: buildManagedCursorHookCommand('session-end.js'),
14
44
  event: 'sessionEnd',
15
45
  description: 'Persist session state and evaluate patterns',
16
46
  },
@@ -22,91 +52,91 @@ const CURSOR_HOOK_ENTRIES = Object.freeze({
22
52
  description: 'Block git hook-bypass flag to protect pre-commit, commit-msg, and pre-push hooks from being skipped',
23
53
  },
24
54
  {
25
- command: 'node .cursor/hooks/before-shell-execution.js',
55
+ command: buildManagedCursorHookCommand('before-shell-execution.js'),
26
56
  event: 'beforeShellExecution',
27
57
  description: 'Tmux dev server blocker, tmux reminder, git push review',
28
58
  },
29
59
  ],
30
60
  afterShellExecution: [
31
61
  {
32
- command: 'node .cursor/hooks/after-shell-execution.js',
62
+ command: buildManagedCursorHookCommand('after-shell-execution.js'),
33
63
  event: 'afterShellExecution',
34
64
  description: 'PR URL logging, build analysis',
35
65
  },
36
66
  ],
37
67
  afterFileEdit: [
38
68
  {
39
- command: 'node .cursor/hooks/after-file-edit.js',
69
+ command: buildManagedCursorHookCommand('after-file-edit.js'),
40
70
  event: 'afterFileEdit',
41
71
  description: 'Auto-format, TypeScript check, console.log warning',
42
72
  },
43
73
  ],
44
74
  beforeMCPExecution: [
45
75
  {
46
- command: 'node .cursor/hooks/before-mcp-execution.js',
76
+ command: buildManagedCursorHookCommand('before-mcp-execution.js'),
47
77
  event: 'beforeMCPExecution',
48
78
  description: 'MCP audit logging and untrusted server warning',
49
79
  },
50
80
  ],
51
81
  afterMCPExecution: [
52
82
  {
53
- command: 'node .cursor/hooks/after-mcp-execution.js',
83
+ command: buildManagedCursorHookCommand('after-mcp-execution.js'),
54
84
  event: 'afterMCPExecution',
55
85
  description: 'MCP result logging',
56
86
  },
57
87
  ],
58
88
  beforeReadFile: [
59
89
  {
60
- command: 'node .cursor/hooks/before-read-file.js',
90
+ command: buildManagedCursorHookCommand('before-read-file.js'),
61
91
  event: 'beforeReadFile',
62
92
  description: 'Warn when reading sensitive files (.env, .key, .pem)',
63
93
  },
64
94
  ],
65
95
  beforeSubmitPrompt: [
66
96
  {
67
- command: 'node .cursor/hooks/before-submit-prompt.js',
97
+ command: buildManagedCursorShellCommand('before-submit-prompt.sh'),
68
98
  event: 'beforeSubmitPrompt',
69
99
  description: 'Detect secrets in prompts (sk-, ghp_, AKIA patterns)',
70
100
  },
71
101
  ],
72
102
  subagentStart: [
73
103
  {
74
- command: 'node .cursor/hooks/subagent-start.js',
104
+ command: buildManagedCursorHookCommand('subagent-start.js'),
75
105
  event: 'subagentStart',
76
106
  description: 'Log agent spawning for observability',
77
107
  },
78
108
  ],
79
109
  subagentStop: [
80
110
  {
81
- command: 'node .cursor/hooks/subagent-stop.js',
111
+ command: buildManagedCursorHookCommand('subagent-stop.js'),
82
112
  event: 'subagentStop',
83
113
  description: 'Log agent completion',
84
114
  },
85
115
  ],
86
116
  beforeTabFileRead: [
87
117
  {
88
- command: 'node .cursor/hooks/before-tab-file-read.js',
118
+ command: buildManagedCursorHookCommand('before-tab-file-read.js'),
89
119
  event: 'beforeTabFileRead',
90
120
  description: 'Block Tab from reading secrets (.env, .key, .pem, credentials)',
91
121
  },
92
122
  ],
93
123
  afterTabFileEdit: [
94
124
  {
95
- command: 'node .cursor/hooks/after-tab-file-edit.js',
125
+ command: buildManagedCursorHookCommand('after-tab-file-edit.js'),
96
126
  event: 'afterTabFileEdit',
97
127
  description: 'Auto-format Tab edits',
98
128
  },
99
129
  ],
100
130
  preCompact: [
101
131
  {
102
- command: 'node .cursor/hooks/pre-compact.js',
132
+ command: buildManagedCursorHookCommand('pre-compact.js'),
103
133
  event: 'preCompact',
104
134
  description: 'Save state before context compaction',
105
135
  },
106
136
  ],
107
137
  stop: [
108
138
  {
109
- command: 'node .cursor/hooks/stop.js',
139
+ command: buildManagedCursorHookCommand('stop.js'),
110
140
  event: 'stop',
111
141
  description: 'Console.log audit on all modified files',
112
142
  },
@@ -144,5 +174,7 @@ module.exports = {
144
174
  CURSOR_HOOK_ENTRIES,
145
175
  buildCursorHookEntries,
146
176
  buildCursorHookConfig,
177
+ buildManagedCursorHookCommand,
178
+ buildManagedCursorShellCommand,
147
179
  serializeCursorHookConfig,
148
180
  };
@@ -418,6 +418,13 @@ function planCursorLegacyInstall(context) {
418
418
  sourceRelativeDir: path.join('.cursor', 'commands'),
419
419
  destinationDir: path.join(targetRoot, 'commands'),
420
420
  });
421
+ addRecursiveCopyOperations(operations, {
422
+ moduleId: 'legacy-cursor-install',
423
+ sourceRoot: context.sourceRoot,
424
+ sourceRelativeDir: path.join('scripts', 'hooks'),
425
+ destinationDir: path.join(targetRoot, 'scripts', 'hooks'),
426
+ strategy: 'sync-root-children',
427
+ });
421
428
  addRecursiveCopyOperations(operations, {
422
429
  moduleId: 'legacy-cursor-install',
423
430
  sourceRoot: context.sourceRoot,
@@ -49,6 +49,13 @@ module.exports = createInstallTargetAdapter({
49
49
  if (sourceRelativePath === '.cursor') {
50
50
  return [
51
51
  adapter.createScaffoldOperation(module.id, sourceRelativePath, planningInput),
52
+ createRemappedOperation(
53
+ adapter,
54
+ module.id,
55
+ path.join('scripts', 'hooks'),
56
+ path.join(targetRoot, 'scripts', 'hooks'),
57
+ { strategy: 'sync-root-children' }
58
+ ),
52
59
  createRemappedOperation(
53
60
  adapter,
54
61
  module.id,