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.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex/hooks/aw-post-tool-use.sh +1 -0
- package/.codex/hooks/aw-pre-tool-use.sh +1 -0
- package/.codex/hooks/aw-session-start.sh +4 -0
- package/.codex/hooks/aw-stop.sh +1 -0
- package/.codex/hooks/aw-user-prompt-submit.sh +1 -0
- package/.cursor/INSTALL.md +9 -0
- package/.cursor/hooks/adapter.js +34 -3
- package/.cursor/hooks/aw-phase-definitions.js +11 -0
- package/.cursor/hooks/before-submit-prompt.js +1 -1
- package/.cursor/hooks/before-submit-prompt.sh +17 -0
- package/.cursor/hooks/session-start.sh +36 -0
- package/.cursor/hooks/shared/aw-phase-definitions.js +1 -19
- package/.cursor/hooks/shared/aw-phase-runner.js +38 -2
- package/.cursor/hooks/shared/session-start.sh +4 -0
- package/.cursor/hooks/shared/user-prompt-submit.sh +33 -140
- package/.cursor/hooks.json +15 -15
- package/.cursor/rules/common-aw-routing.md +43 -0
- package/.cursor/skills/api-and-interface-design/SKILL.md +75 -0
- package/.cursor/skills/aw-brainstorm/SKILL.md +115 -0
- package/.cursor/skills/aw-build/SKILL.md +152 -0
- package/.cursor/skills/aw-build/evals/build-stage-cases.json +28 -0
- package/.cursor/skills/aw-debug/SKILL.md +49 -0
- package/.cursor/skills/aw-deploy/SKILL.md +101 -0
- package/.cursor/skills/aw-deploy/evals/deploy-stage-cases.json +32 -0
- package/.cursor/skills/aw-execute/SKILL.md +47 -0
- package/.cursor/skills/aw-execute/references/mode-code.md +47 -0
- package/.cursor/skills/aw-execute/references/mode-docs.md +28 -0
- package/.cursor/skills/aw-execute/references/mode-infra.md +44 -0
- package/.cursor/skills/aw-execute/references/mode-migration.md +58 -0
- package/.cursor/skills/aw-execute/references/worker-implementer.md +26 -0
- package/.cursor/skills/aw-execute/references/worker-parallel-worker.md +23 -0
- package/.cursor/skills/aw-execute/references/worker-quality-reviewer.md +23 -0
- package/.cursor/skills/aw-execute/references/worker-spec-reviewer.md +23 -0
- package/.cursor/skills/aw-execute/scripts/build-worker-bundle.js +229 -0
- package/.cursor/skills/aw-finish/SKILL.md +111 -0
- package/.cursor/skills/aw-investigate/SKILL.md +109 -0
- package/.cursor/skills/aw-plan/SKILL.md +368 -0
- package/.cursor/skills/aw-prepare/SKILL.md +118 -0
- package/.cursor/skills/aw-review/SKILL.md +118 -0
- package/.cursor/skills/aw-ship/SKILL.md +115 -0
- package/.cursor/skills/aw-spec/SKILL.md +104 -0
- package/.cursor/skills/aw-tasks/SKILL.md +138 -0
- package/.cursor/skills/aw-test/SKILL.md +118 -0
- package/.cursor/skills/aw-verify/SKILL.md +51 -0
- package/.cursor/skills/aw-yolo/SKILL.md +111 -0
- package/.cursor/skills/browser-testing-with-devtools/SKILL.md +81 -0
- package/.cursor/skills/ci-cd-and-automation/SKILL.md +71 -0
- package/.cursor/skills/code-simplification/SKILL.md +74 -0
- package/.cursor/skills/context-engineering/SKILL.md +74 -0
- package/.cursor/skills/deprecation-and-migration/SKILL.md +75 -0
- package/.cursor/skills/documentation-and-adrs/SKILL.md +75 -0
- package/.cursor/skills/frontend-ui-engineering/SKILL.md +68 -0
- package/.cursor/skills/git-workflow-and-versioning/SKILL.md +75 -0
- package/.cursor/skills/idea-refine/SKILL.md +84 -0
- package/.cursor/skills/incremental-implementation/SKILL.md +75 -0
- package/.cursor/skills/performance-optimization/SKILL.md +77 -0
- package/.cursor/skills/security-and-hardening/SKILL.md +70 -0
- package/.cursor/skills/using-aw-skills/SKILL.md +290 -0
- package/.cursor/skills/using-aw-skills/evals/skill-trigger-cases.tsv +25 -0
- package/.cursor/skills/using-aw-skills/evals/test-skill-triggers.sh +171 -0
- package/.cursor/skills/using-aw-skills/hooks/hooks.json +9 -0
- package/.cursor/skills/using-aw-skills/hooks/session-start.sh +67 -0
- package/.cursor/skills/using-platform-skills/SKILL.md +163 -0
- package/.cursor/skills/using-platform-skills/evals/platform-selection-cases.json +52 -0
- package/.opencode/package.json +1 -1
- package/package.json +3 -1
- package/scripts/cursor-aw-home/hooks.json +15 -15
- package/scripts/cursor-aw-hooks/adapter.js +34 -3
- package/scripts/cursor-aw-hooks/aw-phase-definitions.js +11 -0
- package/scripts/cursor-aw-hooks/before-submit-prompt.js +1 -1
- package/scripts/cursor-aw-hooks/before-submit-prompt.sh +17 -0
- package/scripts/cursor-aw-hooks/session-start.sh +36 -0
- package/scripts/hooks/session-start-rules-context.sh +8 -1
- package/scripts/hooks/shared/aw-phase-definitions.js +1 -19
- package/scripts/hooks/shared/aw-phase-runner.js +38 -2
- package/scripts/hooks/shared/session-start.sh +4 -0
- package/scripts/hooks/shared/user-prompt-submit.sh +33 -140
- package/scripts/lib/cursor-aw-hook-files.js +2 -0
- package/scripts/lib/cursor-hook-config.js +47 -15
- package/scripts/lib/install-executor.js +7 -0
- package/scripts/lib/install-targets/cursor-project.js +7 -0
- 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": "
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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": "
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
const { readStdin, runManagedShellHook } = require('./adapter');
|
|
3
3
|
|
|
4
4
|
function emitAwPromptReminder(raw) {
|
|
5
|
-
const result = runManagedShellHook('
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
133
|
-
[ -
|
|
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
|
|