aw-ecc 1.4.21 → 1.4.25

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 (83) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex/hooks/aw-post-tool-use.sh +1 -0
  3. package/.codex/hooks/aw-pre-tool-use.sh +1 -0
  4. package/.codex/hooks/aw-session-start.sh +4 -0
  5. package/.codex/hooks/aw-stop.sh +1 -0
  6. package/.codex/hooks/aw-user-prompt-submit.sh +1 -0
  7. package/.cursor/INSTALL.md +9 -0
  8. package/.cursor/hooks/adapter.js +34 -3
  9. package/.cursor/hooks/aw-phase-definitions.js +11 -0
  10. package/.cursor/hooks/before-submit-prompt.js +1 -1
  11. package/.cursor/hooks/before-submit-prompt.sh +17 -0
  12. package/.cursor/hooks/session-start.sh +36 -0
  13. package/.cursor/hooks/shared/aw-phase-definitions.js +1 -19
  14. package/.cursor/hooks/shared/aw-phase-runner.js +38 -2
  15. package/.cursor/hooks/shared/session-start.sh +4 -0
  16. package/.cursor/hooks/shared/user-prompt-submit.sh +33 -140
  17. package/.cursor/hooks.json +15 -15
  18. package/.cursor/rules/common-aw-routing.md +43 -0
  19. package/.cursor/skills/api-and-interface-design/SKILL.md +75 -0
  20. package/.cursor/skills/aw-brainstorm/SKILL.md +115 -0
  21. package/.cursor/skills/aw-build/SKILL.md +152 -0
  22. package/.cursor/skills/aw-build/evals/build-stage-cases.json +28 -0
  23. package/.cursor/skills/aw-debug/SKILL.md +49 -0
  24. package/.cursor/skills/aw-deploy/SKILL.md +101 -0
  25. package/.cursor/skills/aw-deploy/evals/deploy-stage-cases.json +32 -0
  26. package/.cursor/skills/aw-execute/SKILL.md +47 -0
  27. package/.cursor/skills/aw-execute/references/mode-code.md +47 -0
  28. package/.cursor/skills/aw-execute/references/mode-docs.md +28 -0
  29. package/.cursor/skills/aw-execute/references/mode-infra.md +44 -0
  30. package/.cursor/skills/aw-execute/references/mode-migration.md +58 -0
  31. package/.cursor/skills/aw-execute/references/worker-implementer.md +26 -0
  32. package/.cursor/skills/aw-execute/references/worker-parallel-worker.md +23 -0
  33. package/.cursor/skills/aw-execute/references/worker-quality-reviewer.md +23 -0
  34. package/.cursor/skills/aw-execute/references/worker-spec-reviewer.md +23 -0
  35. package/.cursor/skills/aw-execute/scripts/build-worker-bundle.js +229 -0
  36. package/.cursor/skills/aw-finish/SKILL.md +111 -0
  37. package/.cursor/skills/aw-investigate/SKILL.md +109 -0
  38. package/.cursor/skills/aw-plan/SKILL.md +368 -0
  39. package/.cursor/skills/aw-prepare/SKILL.md +118 -0
  40. package/.cursor/skills/aw-review/SKILL.md +118 -0
  41. package/.cursor/skills/aw-ship/SKILL.md +115 -0
  42. package/.cursor/skills/aw-spec/SKILL.md +104 -0
  43. package/.cursor/skills/aw-tasks/SKILL.md +138 -0
  44. package/.cursor/skills/aw-test/SKILL.md +118 -0
  45. package/.cursor/skills/aw-verify/SKILL.md +51 -0
  46. package/.cursor/skills/aw-yolo/SKILL.md +111 -0
  47. package/.cursor/skills/browser-testing-with-devtools/SKILL.md +81 -0
  48. package/.cursor/skills/ci-cd-and-automation/SKILL.md +71 -0
  49. package/.cursor/skills/code-simplification/SKILL.md +74 -0
  50. package/.cursor/skills/context-engineering/SKILL.md +74 -0
  51. package/.cursor/skills/deprecation-and-migration/SKILL.md +75 -0
  52. package/.cursor/skills/documentation-and-adrs/SKILL.md +75 -0
  53. package/.cursor/skills/frontend-ui-engineering/SKILL.md +68 -0
  54. package/.cursor/skills/git-workflow-and-versioning/SKILL.md +75 -0
  55. package/.cursor/skills/idea-refine/SKILL.md +84 -0
  56. package/.cursor/skills/incremental-implementation/SKILL.md +75 -0
  57. package/.cursor/skills/performance-optimization/SKILL.md +77 -0
  58. package/.cursor/skills/security-and-hardening/SKILL.md +70 -0
  59. package/.cursor/skills/using-aw-skills/SKILL.md +290 -0
  60. package/.cursor/skills/using-aw-skills/evals/skill-trigger-cases.tsv +25 -0
  61. package/.cursor/skills/using-aw-skills/evals/test-skill-triggers.sh +171 -0
  62. package/.cursor/skills/using-aw-skills/hooks/hooks.json +9 -0
  63. package/.cursor/skills/using-aw-skills/hooks/session-start.sh +67 -0
  64. package/.cursor/skills/using-platform-skills/SKILL.md +163 -0
  65. package/.cursor/skills/using-platform-skills/evals/platform-selection-cases.json +52 -0
  66. package/.opencode/package.json +1 -1
  67. package/package.json +3 -1
  68. package/scripts/cursor-aw-home/hooks.json +15 -15
  69. package/scripts/cursor-aw-hooks/adapter.js +34 -3
  70. package/scripts/cursor-aw-hooks/aw-phase-definitions.js +11 -0
  71. package/scripts/cursor-aw-hooks/before-submit-prompt.js +1 -1
  72. package/scripts/cursor-aw-hooks/before-submit-prompt.sh +17 -0
  73. package/scripts/cursor-aw-hooks/session-start.sh +36 -0
  74. package/scripts/hooks/session-start-rules-context.sh +8 -1
  75. package/scripts/hooks/shared/aw-phase-definitions.js +1 -19
  76. package/scripts/hooks/shared/aw-phase-runner.js +38 -2
  77. package/scripts/hooks/shared/session-start.sh +4 -0
  78. package/scripts/hooks/shared/user-prompt-submit.sh +33 -140
  79. package/scripts/lib/cursor-aw-hook-files.js +2 -0
  80. package/scripts/lib/cursor-hook-config.js +47 -15
  81. package/scripts/lib/install-executor.js +7 -0
  82. package/scripts/lib/install-targets/cursor-project.js +7 -0
  83. package/skills/using-aw-skills/hooks/session-start.sh +4 -0
@@ -3,14 +3,14 @@
3
3
  "hooks": {
4
4
  "sessionStart": [
5
5
  {
6
- "command": "node .cursor/hooks/session-start.js",
6
+ "command": "bash -lc \"for candidate in \\\"$PWD/.cursor/hooks/session-start.sh\\\" \\\"$HOME/.cursor/hooks/session-start.sh\\\"; do if [ -f \\\"$candidate\\\" ]; then exec bash \\\"$candidate\\\"; fi; done; exit 0\"",
7
7
  "event": "sessionStart",
8
8
  "description": "Load previous context and detect environment"
9
9
  }
10
10
  ],
11
11
  "sessionEnd": [
12
12
  {
13
- "command": "node .cursor/hooks/session-end.js",
13
+ "command": "node -e \"const fs=require('fs');const path=require('path');const os=require('os');const scriptName=\\\"session-end.js\\\";const candidates=[path.join(process.cwd(), '.cursor', 'hooks', scriptName),path.join(os.homedir(), '.cursor', 'hooks', scriptName)];const target=candidates.find(candidate => fs.existsSync(candidate));if(!target) process.exit(0);require(target)\"",
14
14
  "event": "sessionEnd",
15
15
  "description": "Persist session state and evaluate patterns"
16
16
  }
@@ -22,91 +22,91 @@
22
22
  "description": "Block git hook-bypass flag to protect pre-commit, commit-msg, and pre-push hooks from being skipped"
23
23
  },
24
24
  {
25
- "command": "node .cursor/hooks/before-shell-execution.js",
25
+ "command": "node -e \"const fs=require('fs');const path=require('path');const os=require('os');const scriptName=\\\"before-shell-execution.js\\\";const candidates=[path.join(process.cwd(), '.cursor', 'hooks', scriptName),path.join(os.homedir(), '.cursor', 'hooks', scriptName)];const target=candidates.find(candidate => fs.existsSync(candidate));if(!target) process.exit(0);require(target)\"",
26
26
  "event": "beforeShellExecution",
27
27
  "description": "Tmux dev server blocker, tmux reminder, git push review"
28
28
  }
29
29
  ],
30
30
  "afterShellExecution": [
31
31
  {
32
- "command": "node .cursor/hooks/after-shell-execution.js",
32
+ "command": "node -e \"const fs=require('fs');const path=require('path');const os=require('os');const scriptName=\\\"after-shell-execution.js\\\";const candidates=[path.join(process.cwd(), '.cursor', 'hooks', scriptName),path.join(os.homedir(), '.cursor', 'hooks', scriptName)];const target=candidates.find(candidate => fs.existsSync(candidate));if(!target) process.exit(0);require(target)\"",
33
33
  "event": "afterShellExecution",
34
34
  "description": "PR URL logging, build analysis"
35
35
  }
36
36
  ],
37
37
  "afterFileEdit": [
38
38
  {
39
- "command": "node .cursor/hooks/after-file-edit.js",
39
+ "command": "node -e \"const fs=require('fs');const path=require('path');const os=require('os');const scriptName=\\\"after-file-edit.js\\\";const candidates=[path.join(process.cwd(), '.cursor', 'hooks', scriptName),path.join(os.homedir(), '.cursor', 'hooks', scriptName)];const target=candidates.find(candidate => fs.existsSync(candidate));if(!target) process.exit(0);require(target)\"",
40
40
  "event": "afterFileEdit",
41
41
  "description": "Auto-format, TypeScript check, console.log warning"
42
42
  }
43
43
  ],
44
44
  "beforeMCPExecution": [
45
45
  {
46
- "command": "node .cursor/hooks/before-mcp-execution.js",
46
+ "command": "node -e \"const fs=require('fs');const path=require('path');const os=require('os');const scriptName=\\\"before-mcp-execution.js\\\";const candidates=[path.join(process.cwd(), '.cursor', 'hooks', scriptName),path.join(os.homedir(), '.cursor', 'hooks', scriptName)];const target=candidates.find(candidate => fs.existsSync(candidate));if(!target) process.exit(0);require(target)\"",
47
47
  "event": "beforeMCPExecution",
48
48
  "description": "MCP audit logging and untrusted server warning"
49
49
  }
50
50
  ],
51
51
  "afterMCPExecution": [
52
52
  {
53
- "command": "node .cursor/hooks/after-mcp-execution.js",
53
+ "command": "node -e \"const fs=require('fs');const path=require('path');const os=require('os');const scriptName=\\\"after-mcp-execution.js\\\";const candidates=[path.join(process.cwd(), '.cursor', 'hooks', scriptName),path.join(os.homedir(), '.cursor', 'hooks', scriptName)];const target=candidates.find(candidate => fs.existsSync(candidate));if(!target) process.exit(0);require(target)\"",
54
54
  "event": "afterMCPExecution",
55
55
  "description": "MCP result logging"
56
56
  }
57
57
  ],
58
58
  "beforeReadFile": [
59
59
  {
60
- "command": "node .cursor/hooks/before-read-file.js",
60
+ "command": "node -e \"const fs=require('fs');const path=require('path');const os=require('os');const scriptName=\\\"before-read-file.js\\\";const candidates=[path.join(process.cwd(), '.cursor', 'hooks', scriptName),path.join(os.homedir(), '.cursor', 'hooks', scriptName)];const target=candidates.find(candidate => fs.existsSync(candidate));if(!target) process.exit(0);require(target)\"",
61
61
  "event": "beforeReadFile",
62
62
  "description": "Warn when reading sensitive files (.env, .key, .pem)"
63
63
  }
64
64
  ],
65
65
  "beforeSubmitPrompt": [
66
66
  {
67
- "command": "node .cursor/hooks/before-submit-prompt.js",
67
+ "command": "bash -lc \"for candidate in \\\"$PWD/.cursor/hooks/before-submit-prompt.sh\\\" \\\"$HOME/.cursor/hooks/before-submit-prompt.sh\\\"; do if [ -f \\\"$candidate\\\" ]; then exec bash \\\"$candidate\\\"; fi; done; exit 0\"",
68
68
  "event": "beforeSubmitPrompt",
69
69
  "description": "Detect secrets in prompts (sk-, ghp_, AKIA patterns)"
70
70
  }
71
71
  ],
72
72
  "subagentStart": [
73
73
  {
74
- "command": "node .cursor/hooks/subagent-start.js",
74
+ "command": "node -e \"const fs=require('fs');const path=require('path');const os=require('os');const scriptName=\\\"subagent-start.js\\\";const candidates=[path.join(process.cwd(), '.cursor', 'hooks', scriptName),path.join(os.homedir(), '.cursor', 'hooks', scriptName)];const target=candidates.find(candidate => fs.existsSync(candidate));if(!target) process.exit(0);require(target)\"",
75
75
  "event": "subagentStart",
76
76
  "description": "Log agent spawning for observability"
77
77
  }
78
78
  ],
79
79
  "subagentStop": [
80
80
  {
81
- "command": "node .cursor/hooks/subagent-stop.js",
81
+ "command": "node -e \"const fs=require('fs');const path=require('path');const os=require('os');const scriptName=\\\"subagent-stop.js\\\";const candidates=[path.join(process.cwd(), '.cursor', 'hooks', scriptName),path.join(os.homedir(), '.cursor', 'hooks', scriptName)];const target=candidates.find(candidate => fs.existsSync(candidate));if(!target) process.exit(0);require(target)\"",
82
82
  "event": "subagentStop",
83
83
  "description": "Log agent completion"
84
84
  }
85
85
  ],
86
86
  "beforeTabFileRead": [
87
87
  {
88
- "command": "node .cursor/hooks/before-tab-file-read.js",
88
+ "command": "node -e \"const fs=require('fs');const path=require('path');const os=require('os');const scriptName=\\\"before-tab-file-read.js\\\";const candidates=[path.join(process.cwd(), '.cursor', 'hooks', scriptName),path.join(os.homedir(), '.cursor', 'hooks', scriptName)];const target=candidates.find(candidate => fs.existsSync(candidate));if(!target) process.exit(0);require(target)\"",
89
89
  "event": "beforeTabFileRead",
90
90
  "description": "Block Tab from reading secrets (.env, .key, .pem, credentials)"
91
91
  }
92
92
  ],
93
93
  "afterTabFileEdit": [
94
94
  {
95
- "command": "node .cursor/hooks/after-tab-file-edit.js",
95
+ "command": "node -e \"const fs=require('fs');const path=require('path');const os=require('os');const scriptName=\\\"after-tab-file-edit.js\\\";const candidates=[path.join(process.cwd(), '.cursor', 'hooks', scriptName),path.join(os.homedir(), '.cursor', 'hooks', scriptName)];const target=candidates.find(candidate => fs.existsSync(candidate));if(!target) process.exit(0);require(target)\"",
96
96
  "event": "afterTabFileEdit",
97
97
  "description": "Auto-format Tab edits"
98
98
  }
99
99
  ],
100
100
  "preCompact": [
101
101
  {
102
- "command": "node .cursor/hooks/pre-compact.js",
102
+ "command": "node -e \"const fs=require('fs');const path=require('path');const os=require('os');const scriptName=\\\"pre-compact.js\\\";const candidates=[path.join(process.cwd(), '.cursor', 'hooks', scriptName),path.join(os.homedir(), '.cursor', 'hooks', scriptName)];const target=candidates.find(candidate => fs.existsSync(candidate));if(!target) process.exit(0);require(target)\"",
103
103
  "event": "preCompact",
104
104
  "description": "Save state before context compaction"
105
105
  }
106
106
  ],
107
107
  "stop": [
108
108
  {
109
- "command": "node .cursor/hooks/stop.js",
109
+ "command": "node -e \"const fs=require('fs');const path=require('path');const os=require('os');const scriptName=\\\"stop.js\\\";const candidates=[path.join(process.cwd(), '.cursor', 'hooks', scriptName),path.join(os.homedir(), '.cursor', 'hooks', scriptName)];const target=candidates.find(candidate => fs.existsSync(candidate));if(!target) process.exit(0);require(target)\"",
110
110
  "event": "stop",
111
111
  "description": "Console.log audit on all modified files"
112
112
  }
@@ -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
+ };
@@ -2,7 +2,7 @@
2
2
  const { readStdin, runManagedShellHook } = require('./adapter');
3
3
 
4
4
  function emitAwPromptReminder(raw) {
5
- const result = runManagedShellHook('.cursor/hooks/shared/user-prompt-submit.sh', raw);
5
+ const result = runManagedShellHook('scripts/hooks/shared/user-prompt-submit.sh', raw);
6
6
  if (result.stderr) {
7
7
  process.stderr.write(result.stderr);
8
8
  }
@@ -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,6 +1,10 @@
1
1
  #!/usr/bin/env bash
2
2
  set -euo pipefail
3
3
 
4
+ # Drain stdin because harnesses can stream a JSON payload even though the AW
5
+ # session-start hook only emits routing context.
6
+ cat >/dev/null || true
7
+
4
8
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
5
9
  ROOT_DIR="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
6
10
 
@@ -1,155 +1,48 @@
1
1
  #!/usr/bin/env bash
2
- # UserPromptSubmit hook — inject a compact AW routing reminder plus scoped rule reminders
3
- #
4
- # Output is printed to stdout as a plain-text prompt reminder.
5
-
6
2
  set -euo pipefail
7
3
 
8
- INPUT=$(cat)
9
-
10
- TMPFILE=$(mktemp) || exit 0
11
- trap 'rm -f "$TMPFILE"' EXIT
12
-
13
- PYTHON_CMD=""
14
- PYTHON_ARGS=()
4
+ RAW="$(cat || true)"
15
5
 
16
- resolve_python_cmd() {
17
- if [ -n "${CLV2_PYTHON_CMD:-}" ] && command -v "$CLV2_PYTHON_CMD" >/dev/null 2>&1; then
18
- PYTHON_CMD="$CLV2_PYTHON_CMD"
19
- PYTHON_ARGS=()
20
- return 0
21
- fi
22
-
23
- if command -v python3 >/dev/null 2>&1; then
24
- PYTHON_CMD="python3"
25
- PYTHON_ARGS=()
26
- return 0
27
- fi
6
+ extract_workspace_root() {
7
+ printf '%s' "$1" | sed -n 's/.*"workspace_roots"[[:space:]]*:[[:space:]]*\[[[:space:]]*"\([^"]*\)".*/\1/p' | head -n 1
8
+ }
28
9
 
29
- if command -v python >/dev/null 2>&1; then
30
- PYTHON_CMD="python"
31
- PYTHON_ARGS=()
32
- return 0
33
- fi
10
+ extract_cwd() {
11
+ printf '%s' "$1" | sed -n 's/.*"cwd"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n 1
12
+ }
34
13
 
35
- if command -v py >/dev/null 2>&1; then
36
- PYTHON_CMD="py"
37
- PYTHON_ARGS=(-3)
38
- return 0
39
- fi
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_rules/platform" \
22
+ "$cwd_dir/.aw_rules/platform" \
23
+ "$HOME/.aw_rules/platform" \
24
+ "$HOME/.aw/.aw_registry/.aw_rules/platform"
25
+ do
26
+ if [ -n "$candidate" ] && [ -d "$candidate" ]; then
27
+ printf '%s' "$candidate"
28
+ return 0
29
+ fi
30
+ done
40
31
 
41
32
  return 1
42
33
  }
43
34
 
44
- resolve_python_cmd || exit 0
45
-
46
- if [ "${#PYTHON_ARGS[@]}" -gt 0 ]; then
47
- echo "$INPUT" | "$PYTHON_CMD" "${PYTHON_ARGS[@]}" -c "
48
- import os, sys, json, re
49
- d = json.load(sys.stdin)
50
- cwd = d.get('cwd', '')
51
- prompt = d.get('prompt', '')
52
- cwd = re.sub(r'[^a-zA-Z0-9./_@:\\\\~\ -]', '', cwd)
53
- prompt = prompt[:500]
54
- stack_overlays_enabled = os.environ.get('AW_ENABLE_STACK_OVERLAY_RULES') == '1'
55
- print(f'CWD={cwd}')
56
- prompt_lower = prompt.lower()
57
- if any(k in prompt_lower for k in ['controller', 'service', 'module', '@body', 'nestjs', 'worker', 'dto']):
58
- print('DOMAIN=backend')
59
- if stack_overlays_enabled and any(k in prompt_lower for k in ['nestjs', '@body', '@controller', '@module', 'class-validator', 'dto']):
60
- print('STACK=nestjs')
61
- elif stack_overlays_enabled and any(k in prompt_lower for k in ['connectrpc', 'connect-go', 'buf.gen', 'protoc-gen-connect-go']):
62
- print('STACK=go-connect')
63
- elif any(k in prompt_lower for k in ['vue', 'component', 'template', 'frontend', '<script', 'nuxt']):
64
- print('DOMAIN=frontend')
65
- if stack_overlays_enabled and any(k in prompt_lower for k in ['nuxt', 'app.vue', 'useasyncdata', 'definepagemeta']):
66
- print('STACK=nuxt')
67
- elif stack_overlays_enabled and any(k in prompt_lower for k in ['vue', '<script', 'script setup', 'composable']):
68
- print('STACK=vue')
69
- elif any(k in prompt_lower for k in ['mongo', 'redis', 'schema', 'migration', 'database', 'index']):
70
- print('DOMAIN=data')
71
- elif any(k in prompt_lower for k in ['helm', 'terraform', 'kubernetes', 'k8s', 'deploy', 'dockerfile']):
72
- print('DOMAIN=infra')
73
- else:
74
- print('DOMAIN=universal')
75
- " > "$TMPFILE" 2>/dev/null || exit 0
76
- else
77
- echo "$INPUT" | "$PYTHON_CMD" -c "
78
- import os, sys, json, re
79
- d = json.load(sys.stdin)
80
- cwd = d.get('cwd', '')
81
- prompt = d.get('prompt', '')
82
- cwd = re.sub(r'[^a-zA-Z0-9./_@:\\\\~\ -]', '', cwd)
83
- prompt = prompt[:500]
84
- stack_overlays_enabled = os.environ.get('AW_ENABLE_STACK_OVERLAY_RULES') == '1'
85
- print(f'CWD={cwd}')
86
- prompt_lower = prompt.lower()
87
- if any(k in prompt_lower for k in ['controller', 'service', 'module', '@body', 'nestjs', 'worker', 'dto']):
88
- print('DOMAIN=backend')
89
- if stack_overlays_enabled and any(k in prompt_lower for k in ['nestjs', '@body', '@controller', '@module', 'class-validator', 'dto']):
90
- print('STACK=nestjs')
91
- elif stack_overlays_enabled and any(k in prompt_lower for k in ['connectrpc', 'connect-go', 'buf.gen', 'protoc-gen-connect-go']):
92
- print('STACK=go-connect')
93
- elif any(k in prompt_lower for k in ['vue', 'component', 'template', 'frontend', '<script', 'nuxt']):
94
- print('DOMAIN=frontend')
95
- if stack_overlays_enabled and any(k in prompt_lower for k in ['nuxt', 'app.vue', 'useasyncdata', 'definepagemeta']):
96
- print('STACK=nuxt')
97
- elif stack_overlays_enabled and any(k in prompt_lower for k in ['vue', '<script', 'script setup', 'composable']):
98
- print('STACK=vue')
99
- elif any(k in prompt_lower for k in ['mongo', 'redis', 'schema', 'migration', 'database', 'index']):
100
- print('DOMAIN=data')
101
- elif any(k in prompt_lower for k in ['helm', 'terraform', 'kubernetes', 'k8s', 'deploy', 'dockerfile']):
102
- print('DOMAIN=infra')
103
- else:
104
- print('DOMAIN=universal')
105
- " > "$TMPFILE" 2>/dev/null || exit 0
35
+ WORKSPACE_ROOT="$(extract_workspace_root "$RAW")"
36
+ if [ -z "$WORKSPACE_ROOT" ]; then
37
+ WORKSPACE_ROOT="$(extract_cwd "$RAW")"
106
38
  fi
107
39
 
108
- CWD="" DOMAIN="universal" STACK=""
109
- while IFS='=' read -r key value; do
110
- # Strip trailing \r from Windows Python CRLF output
111
- key="${key%$'\r'}"
112
- value="${value%$'\r'}"
113
- case "${key:-}" in
114
- CWD) CWD="$value" ;;
115
- DOMAIN) DOMAIN="$value" ;;
116
- STACK) STACK="$value" ;;
117
- esac
118
- done < "$TMPFILE"
119
-
120
- RULES_DIR=""
121
- DOMAIN_AGENTS=""
122
- STACK_AGENTS=""
123
-
124
- if [ -d "$CWD/.aw_registry/.aw_rules/platform" ]; then
125
- RULES_DIR="$CWD/.aw_registry/.aw_rules/platform"
126
- DOMAIN_AGENTS="$RULES_DIR/$DOMAIN/AGENTS.md"
127
- elif [ -d "$CWD/.aw_rules" ]; then
128
- RULES_DIR="$CWD/.aw_rules"
129
- DOMAIN_AGENTS="$RULES_DIR/$DOMAIN/AGENTS.md"
40
+ RULES_ROOT="$(resolve_rules_root "$WORKSPACE_ROOT" || true)"
41
+ if [ -z "$RULES_ROOT" ]; then
42
+ RULES_ROOT="$HOME/.aw_rules/platform"
130
43
  fi
131
44
 
132
- [ -n "$RULES_DIR" ] || exit 0
133
- [ -f "$DOMAIN_AGENTS" ] || exit 0
134
-
135
- if [ -n "$STACK" ] && [ -f "$RULES_DIR/$DOMAIN/$STACK/AGENTS.md" ]; then
136
- STACK_AGENTS="$RULES_DIR/$DOMAIN/$STACK/AGENTS.md"
137
- fi
138
-
139
- DOMAIN_RULES=$(head -20 "$DOMAIN_AGENTS" 2>/dev/null || echo "")
140
- STACK_RULES=""
141
- if [ -n "$STACK_AGENTS" ]; then
142
- STACK_RULES=$(head -20 "$STACK_AGENTS" 2>/dev/null || echo "")
143
- fi
144
-
145
- RULE_BULLETS=$(printf '%s\n%s\n' "$DOMAIN_RULES" "$STACK_RULES" | grep -E "^\- .*(MUST|Never)" | awk '!seen[$0]++' | head -8 2>/dev/null || true)
146
-
147
- [ -z "$DOMAIN_RULES" ] && [ -z "$STACK_RULES" ] && exit 0
148
-
149
- cat << EOF
150
- [AW Router reminder] Re-apply using-aw-skills for this prompt and re-select the smallest correct AW route and stage skill before substantive work.
151
- [Rule reminder — $RULES_DIR/$DOMAIN${STACK_AGENTS:+/$STACK}] Active MUST rules for this scope. Follow them.
152
- ${RULE_BULLETS:-See $DOMAIN_AGENTS${STACK_AGENTS:+ and $STACK_AGENTS}}
45
+ cat <<EOF
46
+ [AW Router reminder] Re-apply using-aw-skills and select the smallest correct AW route before substantive work.
47
+ [Rule reminder] Read ${RULES_ROOT}/universal/AGENTS.md and ${RULES_ROOT}/security/AGENTS.md, then the touched domain AGENTS.md plus references/ on demand.
153
48
  EOF
154
-
155
- 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