aw-ecc 1.4.32 → 1.4.47
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 +8 -2
- package/.codex/hooks/aw-session-start.sh +11 -4
- package/.codex/hooks/aw-stop.sh +8 -2
- package/.codex/hooks/aw-user-prompt-submit.sh +10 -2
- package/.codex/hooks.json +8 -8
- package/.cursor/INSTALL.md +7 -5
- package/.cursor/hooks/adapter.js +41 -4
- package/.cursor/hooks/after-agent-response.js +62 -0
- package/.cursor/hooks/before-submit-prompt.js +7 -1
- package/.cursor/hooks/post-tool-use-failure.js +21 -0
- package/.cursor/hooks/post-tool-use.js +39 -0
- package/.cursor/hooks/shared/aw-phase-definitions.js +53 -0
- package/.cursor/hooks/shared/aw-phase-runner.js +3 -1
- package/.cursor/hooks/subagent-start.js +22 -4
- package/.cursor/hooks/subagent-stop.js +18 -1
- package/.cursor/hooks.json +23 -2
- package/.opencode/package.json +1 -1
- package/AGENTS.md +3 -3
- package/README.md +5 -5
- package/commands/adk.md +52 -0
- package/commands/build.md +22 -9
- package/commands/deploy.md +12 -0
- package/commands/execute.md +9 -0
- package/commands/feature.md +333 -0
- package/commands/investigate.md +18 -5
- package/commands/plan.md +23 -9
- package/commands/publish.md +65 -0
- package/commands/review.md +12 -0
- package/commands/ship.md +12 -0
- package/commands/test.md +12 -0
- package/commands/verify.md +9 -0
- package/hooks/hooks.json +36 -0
- package/manifests/install-components.json +8 -0
- package/manifests/install-modules.json +83 -0
- package/manifests/install-profiles.json +7 -0
- package/package.json +1 -1
- package/scripts/ci/validate-rules.js +51 -0
- package/scripts/cursor-aw-home/hooks.json +23 -2
- package/scripts/cursor-aw-hooks/adapter.js +41 -4
- package/scripts/cursor-aw-hooks/before-submit-prompt.js +7 -1
- package/scripts/hooks/aw-usage-commit-created.js +32 -0
- package/scripts/hooks/aw-usage-post-tool-use-failure.js +56 -0
- package/scripts/hooks/aw-usage-post-tool-use.js +242 -0
- package/scripts/hooks/aw-usage-prompt-submit.js +112 -0
- package/scripts/hooks/aw-usage-session-start.js +48 -0
- package/scripts/hooks/aw-usage-stop.js +182 -0
- package/scripts/hooks/aw-usage-telemetry-send.js +84 -0
- package/scripts/hooks/cost-tracker.js +3 -23
- package/scripts/hooks/shared/aw-phase-definitions.js +53 -0
- package/scripts/hooks/shared/aw-phase-runner.js +3 -1
- package/scripts/lib/aw-hook-contract.js +2 -2
- package/scripts/lib/aw-pricing.js +306 -0
- package/scripts/lib/aw-usage-telemetry.js +472 -0
- package/scripts/lib/codex-hook-config.js +8 -8
- package/scripts/lib/cursor-hook-config.js +25 -10
- package/scripts/lib/install-targets/cursor-project.js +3 -0
- package/scripts/lib/install-targets/helpers.js +20 -3
- package/skills/aw-adk/SKILL.md +317 -0
- package/skills/aw-adk/agents/analyzer.md +113 -0
- package/skills/aw-adk/agents/comparator.md +113 -0
- package/skills/aw-adk/agents/grader.md +115 -0
- package/skills/aw-adk/assets/eval_review.html +76 -0
- package/skills/aw-adk/eval-viewer/generate_review.py +164 -0
- package/skills/aw-adk/eval-viewer/viewer.html +181 -0
- package/skills/aw-adk/evals/eval-colocated-placement.md +84 -0
- package/skills/aw-adk/evals/eval-create-agent.md +90 -0
- package/skills/aw-adk/evals/eval-create-command.md +98 -0
- package/skills/aw-adk/evals/eval-create-eval.md +89 -0
- package/skills/aw-adk/evals/eval-create-rule.md +99 -0
- package/skills/aw-adk/evals/eval-create-skill.md +97 -0
- package/skills/aw-adk/evals/eval-delete-agent.md +79 -0
- package/skills/aw-adk/evals/eval-delete-command.md +89 -0
- package/skills/aw-adk/evals/eval-delete-rule.md +86 -0
- package/skills/aw-adk/evals/eval-delete-skill.md +90 -0
- package/skills/aw-adk/evals/eval-meta-eval-coverage.md +78 -0
- package/skills/aw-adk/evals/eval-meta-eval-determinism.md +81 -0
- package/skills/aw-adk/evals/eval-meta-eval-false-pass.md +81 -0
- package/skills/aw-adk/evals/eval-score-accuracy.md +95 -0
- package/skills/aw-adk/evals/eval-type-redirect.md +68 -0
- package/skills/aw-adk/evals/evals.json +96 -0
- package/skills/aw-adk/references/artifact-wiring.md +162 -0
- package/skills/aw-adk/references/cross-ide-mapping.md +71 -0
- package/skills/aw-adk/references/eval-placement-guide.md +183 -0
- package/skills/aw-adk/references/external-resources.md +75 -0
- package/skills/aw-adk/references/getting-started.md +66 -0
- package/skills/aw-adk/references/registry-structure.md +152 -0
- package/skills/aw-adk/references/rubric-agent.md +36 -0
- package/skills/aw-adk/references/rubric-command.md +36 -0
- package/skills/aw-adk/references/rubric-eval.md +36 -0
- package/skills/aw-adk/references/rubric-meta-eval.md +132 -0
- package/skills/aw-adk/references/rubric-rule.md +36 -0
- package/skills/aw-adk/references/rubric-skill.md +36 -0
- package/skills/aw-adk/references/schemas.md +222 -0
- package/skills/aw-adk/references/template-agent.md +251 -0
- package/skills/aw-adk/references/template-command.md +279 -0
- package/skills/aw-adk/references/template-eval.md +176 -0
- package/skills/aw-adk/references/template-rule.md +119 -0
- package/skills/aw-adk/references/template-skill.md +123 -0
- package/skills/aw-adk/references/type-classifier.md +98 -0
- package/skills/aw-adk/references/writing-good-agents.md +227 -0
- package/skills/aw-adk/references/writing-good-commands.md +258 -0
- package/skills/aw-adk/references/writing-good-evals.md +271 -0
- package/skills/aw-adk/references/writing-good-rules.md +214 -0
- package/skills/aw-adk/references/writing-good-skills.md +159 -0
- package/skills/aw-adk/scripts/aggregate-benchmark.py +190 -0
- package/skills/aw-adk/scripts/lint-artifact.sh +211 -0
- package/skills/aw-adk/scripts/score-artifact.sh +179 -0
- package/skills/aw-adk/scripts/trigger-eval.py +192 -0
- package/skills/aw-build/SKILL.md +19 -2
- package/skills/aw-deploy/SKILL.md +65 -3
- package/skills/aw-design/SKILL.md +156 -0
- package/skills/aw-design/references/highrise-tokens.md +394 -0
- package/skills/aw-design/references/micro-interactions.md +76 -0
- package/skills/aw-design/references/prompt-template.md +160 -0
- package/skills/aw-design/references/quality-checklist.md +70 -0
- package/skills/aw-design/references/self-review.md +497 -0
- package/skills/aw-design/references/stitch-workflow.md +127 -0
- package/skills/aw-feature/SKILL.md +293 -0
- package/skills/aw-investigate/SKILL.md +17 -0
- package/skills/aw-plan/SKILL.md +34 -3
- package/skills/aw-publish/SKILL.md +300 -0
- package/skills/aw-publish/evals/eval-confirmation-gate.md +60 -0
- package/skills/aw-publish/evals/eval-intent-detection.md +111 -0
- package/skills/aw-publish/evals/eval-push-modes.md +67 -0
- package/skills/aw-publish/evals/eval-rules-push.md +60 -0
- package/skills/aw-publish/evals/evals.json +29 -0
- package/skills/aw-publish/references/push-modes.md +38 -0
- package/skills/aw-review/SKILL.md +88 -9
- package/skills/aw-rules-review/SKILL.md +124 -0
- package/skills/aw-rules-review/agents/openai.yaml +3 -0
- package/skills/aw-rules-review/scripts/generate-review-template.mjs +323 -0
- package/skills/aw-ship/SKILL.md +16 -0
- package/skills/aw-spec/SKILL.md +15 -0
- package/skills/aw-tasks/SKILL.md +15 -0
- package/skills/aw-test/SKILL.md +16 -0
- package/skills/aw-yolo/SKILL.md +4 -0
- package/skills/diagnose/SKILL.md +121 -0
- package/skills/diagnose/scripts/hitl-loop.template.sh +41 -0
- package/skills/finish-only-when-green/SKILL.md +265 -0
- package/skills/grill-me/SKILL.md +24 -0
- package/skills/grill-with-docs/SKILL.md +92 -0
- package/skills/grill-with-docs/adr-format.md +47 -0
- package/skills/grill-with-docs/context-format.md +67 -0
- package/skills/improve-codebase-architecture/SKILL.md +75 -0
- package/skills/improve-codebase-architecture/deepening.md +37 -0
- package/skills/improve-codebase-architecture/interface-design.md +44 -0
- package/skills/improve-codebase-architecture/language.md +53 -0
- package/skills/local-ghl-setup-from-screenshot/SKILL.md +538 -0
- package/skills/tdd/SKILL.md +115 -0
- package/skills/tdd/deep-modules.md +33 -0
- package/skills/tdd/interface-design.md +31 -0
- package/skills/tdd/mocking.md +59 -0
- package/skills/tdd/refactoring.md +10 -0
- package/skills/tdd/tests.md +61 -0
- package/skills/to-issues/SKILL.md +62 -0
- package/skills/to-prd/SKILL.md +75 -0
- package/skills/using-aw-skills/SKILL.md +170 -237
- package/skills/using-aw-skills/hooks/session-start.sh +11 -41
- package/skills/zoom-out/SKILL.md +24 -0
- package/.cursor/rules/common-agents.md +0 -53
- package/.cursor/rules/common-aw-routing.md +0 -43
- package/.cursor/rules/common-coding-style.md +0 -52
- package/.cursor/rules/common-development-workflow.md +0 -33
- package/.cursor/rules/common-git-workflow.md +0 -28
- package/.cursor/rules/common-hooks.md +0 -34
- package/.cursor/rules/common-patterns.md +0 -35
- package/.cursor/rules/common-performance.md +0 -59
- package/.cursor/rules/common-security.md +0 -33
- package/.cursor/rules/common-testing.md +0 -33
- package/.cursor/skills/api-and-interface-design/SKILL.md +0 -75
- package/.cursor/skills/article-writing/SKILL.md +0 -85
- package/.cursor/skills/aw-brainstorm/SKILL.md +0 -115
- package/.cursor/skills/aw-build/SKILL.md +0 -152
- package/.cursor/skills/aw-build/evals/build-stage-cases.json +0 -28
- package/.cursor/skills/aw-debug/SKILL.md +0 -49
- package/.cursor/skills/aw-deploy/SKILL.md +0 -101
- package/.cursor/skills/aw-deploy/evals/deploy-stage-cases.json +0 -32
- package/.cursor/skills/aw-execute/SKILL.md +0 -47
- package/.cursor/skills/aw-execute/references/mode-code.md +0 -47
- package/.cursor/skills/aw-execute/references/mode-docs.md +0 -28
- package/.cursor/skills/aw-execute/references/mode-infra.md +0 -44
- package/.cursor/skills/aw-execute/references/mode-migration.md +0 -58
- package/.cursor/skills/aw-execute/references/worker-implementer.md +0 -26
- package/.cursor/skills/aw-execute/references/worker-parallel-worker.md +0 -23
- package/.cursor/skills/aw-execute/references/worker-quality-reviewer.md +0 -23
- package/.cursor/skills/aw-execute/references/worker-spec-reviewer.md +0 -23
- package/.cursor/skills/aw-execute/scripts/build-worker-bundle.js +0 -229
- package/.cursor/skills/aw-finish/SKILL.md +0 -111
- package/.cursor/skills/aw-investigate/SKILL.md +0 -109
- package/.cursor/skills/aw-plan/SKILL.md +0 -368
- package/.cursor/skills/aw-prepare/SKILL.md +0 -118
- package/.cursor/skills/aw-review/SKILL.md +0 -118
- package/.cursor/skills/aw-ship/SKILL.md +0 -115
- package/.cursor/skills/aw-spec/SKILL.md +0 -104
- package/.cursor/skills/aw-tasks/SKILL.md +0 -138
- package/.cursor/skills/aw-test/SKILL.md +0 -118
- package/.cursor/skills/aw-verify/SKILL.md +0 -51
- package/.cursor/skills/aw-yolo/SKILL.md +0 -111
- package/.cursor/skills/browser-testing-with-devtools/SKILL.md +0 -81
- package/.cursor/skills/bun-runtime/SKILL.md +0 -84
- package/.cursor/skills/ci-cd-and-automation/SKILL.md +0 -71
- package/.cursor/skills/code-simplification/SKILL.md +0 -74
- package/.cursor/skills/content-engine/SKILL.md +0 -88
- package/.cursor/skills/context-engineering/SKILL.md +0 -74
- package/.cursor/skills/deprecation-and-migration/SKILL.md +0 -75
- package/.cursor/skills/documentation-and-adrs/SKILL.md +0 -75
- package/.cursor/skills/documentation-lookup/SKILL.md +0 -90
- package/.cursor/skills/frontend-slides/SKILL.md +0 -184
- package/.cursor/skills/frontend-slides/STYLE_PRESETS.md +0 -330
- package/.cursor/skills/frontend-ui-engineering/SKILL.md +0 -68
- package/.cursor/skills/git-workflow-and-versioning/SKILL.md +0 -75
- package/.cursor/skills/idea-refine/SKILL.md +0 -84
- package/.cursor/skills/incremental-implementation/SKILL.md +0 -75
- package/.cursor/skills/investor-materials/SKILL.md +0 -96
- package/.cursor/skills/investor-outreach/SKILL.md +0 -76
- package/.cursor/skills/market-research/SKILL.md +0 -75
- package/.cursor/skills/mcp-server-patterns/SKILL.md +0 -67
- package/.cursor/skills/nextjs-turbopack/SKILL.md +0 -44
- package/.cursor/skills/performance-optimization/SKILL.md +0 -77
- package/.cursor/skills/security-and-hardening/SKILL.md +0 -70
- package/.cursor/skills/using-aw-skills/SKILL.md +0 -290
- package/.cursor/skills/using-aw-skills/evals/skill-trigger-cases.tsv +0 -25
- package/.cursor/skills/using-aw-skills/evals/test-skill-triggers.sh +0 -171
- package/.cursor/skills/using-aw-skills/hooks/hooks.json +0 -9
- package/.cursor/skills/using-aw-skills/hooks/session-start.sh +0 -67
- package/.cursor/skills/using-platform-skills/SKILL.md +0 -163
- package/.cursor/skills/using-platform-skills/evals/platform-selection-cases.json +0 -52
- /package/.cursor/rules/{golang-coding-style.md → golang-coding-style.mdc} +0 -0
- /package/.cursor/rules/{golang-hooks.md → golang-hooks.mdc} +0 -0
- /package/.cursor/rules/{golang-patterns.md → golang-patterns.mdc} +0 -0
- /package/.cursor/rules/{golang-security.md → golang-security.mdc} +0 -0
- /package/.cursor/rules/{golang-testing.md → golang-testing.mdc} +0 -0
- /package/.cursor/rules/{kotlin-coding-style.md → kotlin-coding-style.mdc} +0 -0
- /package/.cursor/rules/{kotlin-hooks.md → kotlin-hooks.mdc} +0 -0
- /package/.cursor/rules/{kotlin-patterns.md → kotlin-patterns.mdc} +0 -0
- /package/.cursor/rules/{kotlin-security.md → kotlin-security.mdc} +0 -0
- /package/.cursor/rules/{kotlin-testing.md → kotlin-testing.mdc} +0 -0
- /package/.cursor/rules/{php-coding-style.md → php-coding-style.mdc} +0 -0
- /package/.cursor/rules/{php-hooks.md → php-hooks.mdc} +0 -0
- /package/.cursor/rules/{php-patterns.md → php-patterns.mdc} +0 -0
- /package/.cursor/rules/{php-security.md → php-security.mdc} +0 -0
- /package/.cursor/rules/{php-testing.md → php-testing.mdc} +0 -0
- /package/.cursor/rules/{python-coding-style.md → python-coding-style.mdc} +0 -0
- /package/.cursor/rules/{python-hooks.md → python-hooks.mdc} +0 -0
- /package/.cursor/rules/{python-patterns.md → python-patterns.mdc} +0 -0
- /package/.cursor/rules/{python-security.md → python-security.mdc} +0 -0
- /package/.cursor/rules/{python-testing.md → python-testing.mdc} +0 -0
- /package/.cursor/rules/{swift-coding-style.md → swift-coding-style.mdc} +0 -0
- /package/.cursor/rules/{swift-hooks.md → swift-hooks.mdc} +0 -0
- /package/.cursor/rules/{swift-patterns.md → swift-patterns.mdc} +0 -0
- /package/.cursor/rules/{swift-security.md → swift-security.mdc} +0 -0
- /package/.cursor/rules/{swift-testing.md → swift-testing.mdc} +0 -0
- /package/.cursor/rules/{typescript-coding-style.md → typescript-coding-style.mdc} +0 -0
- /package/.cursor/rules/{typescript-hooks.md → typescript-hooks.mdc} +0 -0
- /package/.cursor/rules/{typescript-patterns.md → typescript-patterns.mdc} +0 -0
- /package/.cursor/rules/{typescript-security.md → typescript-security.mdc} +0 -0
- /package/.cursor/rules/{typescript-testing.md → typescript-testing.mdc} +0 -0
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AW Usage Telemetry — shared collection module.
|
|
3
|
+
*
|
|
4
|
+
* Two exports:
|
|
5
|
+
* buildEvent(hookInput, eventType, payload) — normalize cross-harness fields
|
|
6
|
+
* sendAsync(event) — fire-and-forget POST via detached child
|
|
7
|
+
*
|
|
8
|
+
* Called by individual hook scripts (post-tool-use, stop, etc.).
|
|
9
|
+
* CJS module — consistent with existing aw-ecc hook ecosystem.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const os = require('os');
|
|
17
|
+
const { spawn, execSync } = require('child_process');
|
|
18
|
+
const crypto = require('crypto');
|
|
19
|
+
|
|
20
|
+
const SENDER_SCRIPT = path.join(__dirname, '..', 'hooks', 'aw-usage-telemetry-send.js');
|
|
21
|
+
const AW_HOME = path.join(os.homedir(), '.aw');
|
|
22
|
+
const CONFIG_PATH = path.join(AW_HOME, 'telemetry', 'config.json');
|
|
23
|
+
const SESSION_DIR = path.join(AW_HOME, 'telemetry', 'sessions');
|
|
24
|
+
const DEDUPE_DIR = path.join(os.tmpdir(), 'aw-usage-telemetry-dedupe');
|
|
25
|
+
|
|
26
|
+
// ── Git config cache (once per process) ──────────────────────────────
|
|
27
|
+
|
|
28
|
+
let _gitCache = null;
|
|
29
|
+
|
|
30
|
+
function getGitInfo() {
|
|
31
|
+
if (_gitCache) return _gitCache;
|
|
32
|
+
_gitCache = { user: null, email: null };
|
|
33
|
+
try {
|
|
34
|
+
_gitCache.user = execSync('git config user.name', { encoding: 'utf8', timeout: 3000 }).trim() || null;
|
|
35
|
+
} catch { /* ignore */ }
|
|
36
|
+
try {
|
|
37
|
+
_gitCache.email = execSync('git config user.email', { encoding: 'utf8', timeout: 3000 }).trim() || null;
|
|
38
|
+
} catch { /* ignore */ }
|
|
39
|
+
return _gitCache;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ── Telemetry config ─────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
let _configCache = null;
|
|
45
|
+
|
|
46
|
+
function generateMachineId() {
|
|
47
|
+
const raw = `${os.hostname()}:${os.userInfo().username}`;
|
|
48
|
+
return crypto.createHash('sha256').update(raw).digest('hex');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function loadConfig() {
|
|
52
|
+
if (_configCache) return _configCache;
|
|
53
|
+
try {
|
|
54
|
+
_configCache = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
|
|
55
|
+
} catch {
|
|
56
|
+
// Config missing or corrupt — self-heal by generating it
|
|
57
|
+
_configCache = { enabled: true, machine_id: generateMachineId() };
|
|
58
|
+
try {
|
|
59
|
+
fs.mkdirSync(path.dirname(CONFIG_PATH), { recursive: true });
|
|
60
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(_configCache, null, 2) + '\n');
|
|
61
|
+
} catch { /* best effort — don't block the hook */ }
|
|
62
|
+
}
|
|
63
|
+
// Backfill machine_id if config exists but field is missing
|
|
64
|
+
if (!_configCache.machine_id || _configCache.machine_id === 'unknown') {
|
|
65
|
+
_configCache.machine_id = generateMachineId();
|
|
66
|
+
try {
|
|
67
|
+
fs.mkdirSync(path.dirname(CONFIG_PATH), { recursive: true });
|
|
68
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(_configCache, null, 2) + '\n');
|
|
69
|
+
} catch { /* best effort */ }
|
|
70
|
+
}
|
|
71
|
+
return _configCache;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ── Opt-out check ────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
function isDisabled() {
|
|
77
|
+
if (process.env.DO_NOT_TRACK === '1') return true;
|
|
78
|
+
if (process.env.AW_TELEMETRY_DISABLED === '1') return true;
|
|
79
|
+
const cfg = loadConfig();
|
|
80
|
+
return cfg.enabled === false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ── AW version ───────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
let _awVersion = null;
|
|
86
|
+
|
|
87
|
+
function parseVersionString(raw) {
|
|
88
|
+
if (!raw) return null;
|
|
89
|
+
const match = String(raw).match(/\bv?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)\b/);
|
|
90
|
+
return match ? match[1] : null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function getAwVersion() {
|
|
94
|
+
if (_awVersion) return _awVersion;
|
|
95
|
+
const envVersion = parseVersionString(process.env.AW_VERSION);
|
|
96
|
+
if (envVersion) {
|
|
97
|
+
_awVersion = envVersion;
|
|
98
|
+
return _awVersion;
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
const cliVersion = parseVersionString(execSync('aw --version', {
|
|
102
|
+
encoding: 'utf8',
|
|
103
|
+
timeout: 1500,
|
|
104
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
105
|
+
}));
|
|
106
|
+
if (cliVersion) {
|
|
107
|
+
_awVersion = cliVersion;
|
|
108
|
+
return _awVersion;
|
|
109
|
+
}
|
|
110
|
+
} catch { /* ignore */ }
|
|
111
|
+
const candidates = [
|
|
112
|
+
path.join(AW_HOME, 'node_modules', '@ghl-ai', 'aw', 'package.json'),
|
|
113
|
+
];
|
|
114
|
+
// Derive global npm prefix from the running node binary (no shell needed)
|
|
115
|
+
try {
|
|
116
|
+
const nodeDir = path.dirname(process.execPath);
|
|
117
|
+
candidates.push(path.join(nodeDir, '..', 'lib', 'node_modules', '@ghl-ai', 'aw', 'package.json'));
|
|
118
|
+
} catch { /* ignore */ }
|
|
119
|
+
try {
|
|
120
|
+
const globalPrefix = execSync('npm prefix -g', { encoding: 'utf8', timeout: 3000 }).trim();
|
|
121
|
+
candidates.push(path.join(globalPrefix, 'lib', 'node_modules', '@ghl-ai', 'aw', 'package.json'));
|
|
122
|
+
} catch { /* ignore */ }
|
|
123
|
+
// aw-ecc version as last-resort fallback
|
|
124
|
+
candidates.push(path.join(os.homedir(), '.aw-ecc', 'package.json'));
|
|
125
|
+
for (const pkgPath of candidates) {
|
|
126
|
+
try {
|
|
127
|
+
_awVersion = parseVersionString(JSON.parse(fs.readFileSync(pkgPath, 'utf8')).version) || null;
|
|
128
|
+
if (_awVersion) return _awVersion;
|
|
129
|
+
} catch { /* ignore */ }
|
|
130
|
+
}
|
|
131
|
+
_awVersion = null;
|
|
132
|
+
return _awVersion;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ── Harness detection ────────────────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
function detectHarness(input) {
|
|
138
|
+
// Explicit harness override from shell wrapper (e.g. Codex SessionStart has no turn_id)
|
|
139
|
+
if (process.env.AW_HARNESS) return process.env.AW_HARNESS;
|
|
140
|
+
if (input._cursor || input.conversation_id || input.cursor_version) return 'cursor';
|
|
141
|
+
// Codex provides turn_id on turn-scoped hooks, Claude does not
|
|
142
|
+
if (input.turn_id !== undefined) return 'codex';
|
|
143
|
+
return 'claude';
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ── Project hash ─────────────────────────────────────────────────────
|
|
147
|
+
|
|
148
|
+
function computeProjectHash(cwd) {
|
|
149
|
+
if (!cwd) return null;
|
|
150
|
+
return crypto.createHash('sha256').update(cwd).digest('hex').slice(0, 16);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ── Session file cleanup ─────────────────────────────────────────────
|
|
154
|
+
// Prune session files older than SESSION_MAX_AGE_MS to prevent unbounded growth.
|
|
155
|
+
// Called once per session start — best-effort, never blocks.
|
|
156
|
+
|
|
157
|
+
const SESSION_MAX_AGE_MS = 72 * 60 * 60 * 1000; // 72 hours
|
|
158
|
+
|
|
159
|
+
function pruneStaleSessionFiles() {
|
|
160
|
+
try {
|
|
161
|
+
const entries = fs.readdirSync(SESSION_DIR);
|
|
162
|
+
const now = Date.now();
|
|
163
|
+
for (const entry of entries) {
|
|
164
|
+
if (!entry.endsWith('.json')) continue;
|
|
165
|
+
const filePath = path.join(SESSION_DIR, entry);
|
|
166
|
+
const stat = fs.statSync(filePath);
|
|
167
|
+
if (now - stat.mtimeMs > SESSION_MAX_AGE_MS) {
|
|
168
|
+
fs.unlinkSync(filePath);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
} catch { /* best effort */ }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ── Session model persistence ────────────────────────────────────────
|
|
175
|
+
// SessionStart captures the model; later hooks read it from disk.
|
|
176
|
+
|
|
177
|
+
function persistSessionModel(sessionId, model) {
|
|
178
|
+
if (!sessionId || !model) return;
|
|
179
|
+
try {
|
|
180
|
+
fs.mkdirSync(SESSION_DIR, { recursive: true });
|
|
181
|
+
const state = readSessionState(sessionId);
|
|
182
|
+
fs.writeFileSync(path.join(SESSION_DIR, sessionId + '.json'), JSON.stringify({
|
|
183
|
+
...state,
|
|
184
|
+
model,
|
|
185
|
+
}));
|
|
186
|
+
} catch { /* ignore */ }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function readSessionModel(sessionId) {
|
|
190
|
+
const state = readSessionState(sessionId);
|
|
191
|
+
return state.model || null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function readSessionState(sessionId) {
|
|
195
|
+
if (!sessionId) return {};
|
|
196
|
+
try {
|
|
197
|
+
const filePath = path.join(SESSION_DIR, sessionId + '.json');
|
|
198
|
+
const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
199
|
+
// Touch mtime so active sessions are never pruned by cleanup
|
|
200
|
+
try { const now = new Date(); fs.utimesSync(filePath, now, now); } catch { /* ignore */ }
|
|
201
|
+
return data;
|
|
202
|
+
} catch { return {}; }
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function persistSessionSkill(sessionId, turnId, skill) {
|
|
206
|
+
if (!sessionId || !skill?.skill_name) return;
|
|
207
|
+
try {
|
|
208
|
+
fs.mkdirSync(SESSION_DIR, { recursive: true });
|
|
209
|
+
const state = readSessionState(sessionId);
|
|
210
|
+
fs.writeFileSync(path.join(SESSION_DIR, sessionId + '.json'), JSON.stringify({
|
|
211
|
+
...state,
|
|
212
|
+
last_skill: {
|
|
213
|
+
turn_id: turnId || null,
|
|
214
|
+
skill_name: skill.skill_name,
|
|
215
|
+
args: skill.args || '',
|
|
216
|
+
source: skill.source || 'unknown',
|
|
217
|
+
updated_at: new Date().toISOString(),
|
|
218
|
+
},
|
|
219
|
+
}));
|
|
220
|
+
} catch { /* ignore */ }
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function readSessionSkill(sessionId, turnId) {
|
|
224
|
+
const skill = readSessionState(sessionId)?.last_skill;
|
|
225
|
+
if (!skill?.skill_name) return null;
|
|
226
|
+
if (turnId) {
|
|
227
|
+
return skill.turn_id === turnId ? skill : null;
|
|
228
|
+
}
|
|
229
|
+
return skill.turn_id ? null : skill;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ── Short-TTL dedupe guards ──────────────────────────────────────────
|
|
233
|
+
|
|
234
|
+
function normalizeDedupePart(value) {
|
|
235
|
+
if (value === undefined || value === null) return '';
|
|
236
|
+
if (typeof value === 'string') return value;
|
|
237
|
+
if (typeof value === 'number' || typeof value === 'boolean') return String(value);
|
|
238
|
+
try {
|
|
239
|
+
return JSON.stringify(value);
|
|
240
|
+
} catch {
|
|
241
|
+
return String(value);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function tryAcquireDedupe(scope, parts, ttlMs = 2500) {
|
|
246
|
+
const normalizedParts = Array.isArray(parts) ? parts.map(normalizeDedupePart) : [normalizeDedupePart(parts)];
|
|
247
|
+
const digest = crypto.createHash('sha256')
|
|
248
|
+
.update(normalizedParts.join('\n'))
|
|
249
|
+
.digest('hex');
|
|
250
|
+
const safeScope = String(scope || 'event').replace(/[^a-z0-9_-]+/gi, '-').toLowerCase();
|
|
251
|
+
const lockPath = path.join(DEDUPE_DIR, `${safeScope}-${digest}.lock`);
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
fs.mkdirSync(DEDUPE_DIR, { recursive: true });
|
|
255
|
+
} catch {
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
try {
|
|
260
|
+
const stat = fs.statSync(lockPath);
|
|
261
|
+
if (Date.now() - stat.mtimeMs <= ttlMs) {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
fs.unlinkSync(lockPath);
|
|
265
|
+
} catch { /* no active lock */ }
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
fs.writeFileSync(lockPath, String(Date.now()), { flag: 'wx' });
|
|
269
|
+
return true;
|
|
270
|
+
} catch {
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ── Codex internal-session filters ───────────────────────────────────
|
|
276
|
+
|
|
277
|
+
function resolvePromptText(input) {
|
|
278
|
+
const candidates = [
|
|
279
|
+
input?.prompt,
|
|
280
|
+
input?.user_prompt,
|
|
281
|
+
input?.message,
|
|
282
|
+
input?.text,
|
|
283
|
+
];
|
|
284
|
+
for (const candidate of candidates) {
|
|
285
|
+
if (typeof candidate === 'string' && candidate.trim()) {
|
|
286
|
+
return candidate;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return '';
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function isCodexInternalTaskTitlePrompt(input) {
|
|
293
|
+
if (detectHarness(input) !== 'codex') return false;
|
|
294
|
+
if (input?.transcript_path) return false;
|
|
295
|
+
const prompt = resolvePromptText(input);
|
|
296
|
+
return prompt.includes('Generate a concise UI title')
|
|
297
|
+
&& prompt.includes('User prompt:');
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function isCodexInternalTaskTitleCompletion(input) {
|
|
301
|
+
if (detectHarness(input) !== 'codex') return false;
|
|
302
|
+
if (input?.transcript_path) return false;
|
|
303
|
+
const rawMessage = typeof input?.last_assistant_message === 'string'
|
|
304
|
+
? input.last_assistant_message.trim()
|
|
305
|
+
: '';
|
|
306
|
+
if (!rawMessage) return false;
|
|
307
|
+
try {
|
|
308
|
+
const parsed = JSON.parse(rawMessage);
|
|
309
|
+
return parsed
|
|
310
|
+
&& typeof parsed === 'object'
|
|
311
|
+
&& !Array.isArray(parsed)
|
|
312
|
+
&& typeof parsed.title === 'string'
|
|
313
|
+
&& Object.keys(parsed).length === 1;
|
|
314
|
+
} catch {
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ── Transcript parsing ───────────────────────────────────────────────
|
|
320
|
+
|
|
321
|
+
function buildCodexUsage(entry) {
|
|
322
|
+
if (entry?.type !== 'event_msg' || entry?.payload?.type !== 'token_count') return null;
|
|
323
|
+
const info = entry.payload?.info || {};
|
|
324
|
+
const usage = info.last_token_usage || info.total_token_usage;
|
|
325
|
+
if (!usage || typeof usage !== 'object') return null;
|
|
326
|
+
return {
|
|
327
|
+
input_tokens: usage.input_tokens ?? null,
|
|
328
|
+
output_tokens: usage.output_tokens ?? null,
|
|
329
|
+
cached_input_tokens: usage.cached_input_tokens ?? null,
|
|
330
|
+
reasoning_output_tokens: usage.reasoning_output_tokens ?? null,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function isCodexAssistantEntry(entry) {
|
|
335
|
+
return entry?.type === 'response_item'
|
|
336
|
+
&& entry?.payload?.type === 'message'
|
|
337
|
+
&& entry?.payload?.role === 'assistant';
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Read the last assistant entry from a transcript JSONL file.
|
|
342
|
+
* Reads the last 256KB (enough for several entries) to avoid loading
|
|
343
|
+
* the entire file which can be 10MB+.
|
|
344
|
+
*
|
|
345
|
+
* Works across harnesses — Claude/Cursor transcripts expose assistant
|
|
346
|
+
* rows directly, while Codex writes `response_item` + `event_msg`
|
|
347
|
+
* lines and surfaces usage via `token_count`.
|
|
348
|
+
*
|
|
349
|
+
* Returns { model, stop_reason, usage } or null.
|
|
350
|
+
*/
|
|
351
|
+
function readLastAssistantFromTranscript(transcriptPath) {
|
|
352
|
+
if (!transcriptPath) return null;
|
|
353
|
+
try {
|
|
354
|
+
const stat = fs.statSync(transcriptPath);
|
|
355
|
+
const TAIL_BYTES = 256 * 1024;
|
|
356
|
+
const start = Math.max(0, stat.size - TAIL_BYTES);
|
|
357
|
+
const fd = fs.openSync(transcriptPath, 'r');
|
|
358
|
+
const buf = Buffer.alloc(Math.min(TAIL_BYTES, stat.size));
|
|
359
|
+
fs.readSync(fd, buf, 0, buf.length, start);
|
|
360
|
+
fs.closeSync(fd);
|
|
361
|
+
|
|
362
|
+
const chunk = buf.toString('utf8');
|
|
363
|
+
const lines = chunk.split('\n').filter(Boolean);
|
|
364
|
+
let latestCodexUsage = null;
|
|
365
|
+
|
|
366
|
+
// Walk backward to find the last assistant entry for any harness.
|
|
367
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
368
|
+
try {
|
|
369
|
+
const entry = JSON.parse(lines[i]);
|
|
370
|
+
if (!latestCodexUsage) {
|
|
371
|
+
latestCodexUsage = buildCodexUsage(entry);
|
|
372
|
+
}
|
|
373
|
+
if (entry.type === 'assistant' && entry.message) {
|
|
374
|
+
const msg = entry.message;
|
|
375
|
+
return {
|
|
376
|
+
model: msg.model || null,
|
|
377
|
+
stop_reason: msg.stop_reason || null,
|
|
378
|
+
usage: msg.usage || null,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
if (isCodexAssistantEntry(entry)) {
|
|
382
|
+
return {
|
|
383
|
+
model: null,
|
|
384
|
+
stop_reason: null,
|
|
385
|
+
usage: latestCodexUsage,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
} catch { /* skip malformed lines */ }
|
|
389
|
+
}
|
|
390
|
+
} catch { /* transcript unreadable — non-blocking */ }
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// ── buildEvent ───────────────────────────────────────────────────────
|
|
395
|
+
|
|
396
|
+
function buildEvent(hookInput, eventType, payload) {
|
|
397
|
+
const input = hookInput || {};
|
|
398
|
+
const cfg = loadConfig();
|
|
399
|
+
const git = getGitInfo();
|
|
400
|
+
const harness = detectHarness(input);
|
|
401
|
+
|
|
402
|
+
// Normalize session_id: Claude/Codex use session_id, Cursor uses conversation_id
|
|
403
|
+
const sessionId = input.session_id
|
|
404
|
+
|| input._cursor?.conversation_id
|
|
405
|
+
|| input.conversation_id
|
|
406
|
+
|| null;
|
|
407
|
+
|
|
408
|
+
// generation_id: Cursor only
|
|
409
|
+
const generationId = input.generation_id || null;
|
|
410
|
+
|
|
411
|
+
// model: Claude only on SessionStart, Codex/Cursor on all hooks
|
|
412
|
+
// Fall back to persisted session model from SessionStart
|
|
413
|
+
const model = input.model
|
|
414
|
+
|| input._cursor?.model
|
|
415
|
+
|| readSessionModel(sessionId)
|
|
416
|
+
|| null;
|
|
417
|
+
|
|
418
|
+
// cwd: Claude/Codex have input.cwd, Cursor uses workspace_roots
|
|
419
|
+
const cwd = input.cwd
|
|
420
|
+
|| (input.workspace_roots && input.workspace_roots[0])
|
|
421
|
+
|| null;
|
|
422
|
+
|
|
423
|
+
return {
|
|
424
|
+
session_id: sessionId,
|
|
425
|
+
generation_id: generationId,
|
|
426
|
+
harness,
|
|
427
|
+
model,
|
|
428
|
+
machine_id: cfg.machine_id || 'unknown',
|
|
429
|
+
github_user: git.user || input.user_email || null,
|
|
430
|
+
github_email: git.email || input.user_email || null,
|
|
431
|
+
project_hash: computeProjectHash(cwd),
|
|
432
|
+
aw_version: getAwVersion(),
|
|
433
|
+
event: eventType,
|
|
434
|
+
client_ts: new Date().toISOString(),
|
|
435
|
+
payload: payload || {},
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// ── sendAsync ────────────────────────────────────────────────────────
|
|
440
|
+
|
|
441
|
+
function sendAsync(event) {
|
|
442
|
+
if (isDisabled()) return;
|
|
443
|
+
if (!event) return;
|
|
444
|
+
|
|
445
|
+
try {
|
|
446
|
+
const child = spawn('node', [SENDER_SCRIPT, JSON.stringify(event)], {
|
|
447
|
+
detached: true,
|
|
448
|
+
stdio: 'ignore',
|
|
449
|
+
});
|
|
450
|
+
child.unref();
|
|
451
|
+
} catch {
|
|
452
|
+
// Fire-and-forget — never block the hook.
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
module.exports = {
|
|
457
|
+
buildEvent,
|
|
458
|
+
sendAsync,
|
|
459
|
+
isDisabled,
|
|
460
|
+
detectHarness,
|
|
461
|
+
loadConfig,
|
|
462
|
+
persistSessionModel,
|
|
463
|
+
pruneStaleSessionFiles,
|
|
464
|
+
readSessionModel,
|
|
465
|
+
persistSessionSkill,
|
|
466
|
+
readSessionSkill,
|
|
467
|
+
readLastAssistantFromTranscript,
|
|
468
|
+
resolvePromptText,
|
|
469
|
+
tryAcquireDedupe,
|
|
470
|
+
isCodexInternalTaskTitlePrompt,
|
|
471
|
+
isCodexInternalTaskTitleCompletion,
|
|
472
|
+
};
|
|
@@ -7,7 +7,7 @@ const GENERATED_AW_HOOKS = Object.freeze({
|
|
|
7
7
|
hooks: [
|
|
8
8
|
{
|
|
9
9
|
type: 'command',
|
|
10
|
-
command: 'bash
|
|
10
|
+
command: 'bash "$HOME/.codex/hooks/aw-session-start.sh"',
|
|
11
11
|
},
|
|
12
12
|
],
|
|
13
13
|
description: 'Load AW routing context at session start',
|
|
@@ -18,10 +18,10 @@ const GENERATED_AW_HOOKS = Object.freeze({
|
|
|
18
18
|
hooks: [
|
|
19
19
|
{
|
|
20
20
|
type: 'command',
|
|
21
|
-
command: 'bash
|
|
21
|
+
command: 'bash "$HOME/.codex/hooks/aw-user-prompt-submit.sh"',
|
|
22
22
|
},
|
|
23
23
|
],
|
|
24
|
-
description: '
|
|
24
|
+
description: 'Emit Codex prompt telemetry and inject compact AW routing reminders on each prompt',
|
|
25
25
|
},
|
|
26
26
|
],
|
|
27
27
|
PreToolUse: [
|
|
@@ -30,7 +30,7 @@ const GENERATED_AW_HOOKS = Object.freeze({
|
|
|
30
30
|
hooks: [
|
|
31
31
|
{
|
|
32
32
|
type: 'command',
|
|
33
|
-
command: 'bash
|
|
33
|
+
command: 'bash "$HOME/.codex/hooks/aw-pre-tool-use.sh"',
|
|
34
34
|
},
|
|
35
35
|
],
|
|
36
36
|
description: 'Reserved AW pre-tool-use phase for Codex home installs',
|
|
@@ -42,10 +42,10 @@ const GENERATED_AW_HOOKS = Object.freeze({
|
|
|
42
42
|
hooks: [
|
|
43
43
|
{
|
|
44
44
|
type: 'command',
|
|
45
|
-
command: 'bash
|
|
45
|
+
command: 'bash "$HOME/.codex/hooks/aw-post-tool-use.sh"',
|
|
46
46
|
},
|
|
47
47
|
],
|
|
48
|
-
description: '
|
|
48
|
+
description: 'Emit Codex post-tool-use telemetry for supported Bash-backed events',
|
|
49
49
|
},
|
|
50
50
|
],
|
|
51
51
|
Stop: [
|
|
@@ -53,10 +53,10 @@ const GENERATED_AW_HOOKS = Object.freeze({
|
|
|
53
53
|
hooks: [
|
|
54
54
|
{
|
|
55
55
|
type: 'command',
|
|
56
|
-
command: 'bash
|
|
56
|
+
command: 'bash "$HOME/.codex/hooks/aw-stop.sh"',
|
|
57
57
|
},
|
|
58
58
|
],
|
|
59
|
-
description: '
|
|
59
|
+
description: 'Emit Codex stop telemetry for response_completed events',
|
|
60
60
|
},
|
|
61
61
|
],
|
|
62
62
|
});
|
|
@@ -18,16 +18,10 @@ function buildManagedCursorHookCommand(scriptFileName) {
|
|
|
18
18
|
|
|
19
19
|
function buildManagedCursorShellCommand(scriptFileName) {
|
|
20
20
|
const scriptName = String(scriptFileName || '').replace(/\\/g, '/');
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
`if [ -f "$candidate" ]; then exec bash "$candidate"; fi; ` +
|
|
26
|
-
'done; exit 0'
|
|
27
|
-
),
|
|
28
|
-
].join(' ');
|
|
29
|
-
|
|
30
|
-
return command;
|
|
21
|
+
// Simple fallback: project-level first, then home-level.
|
|
22
|
+
// Previous version used triple-escaped quoting inside bash -lc that
|
|
23
|
+
// silently failed when Cursor spawned the process (Bug #8).
|
|
24
|
+
return `bash -c 'f="$PWD/.cursor/hooks/${scriptName}"; [ -f "$f" ] || f="$HOME/.cursor/hooks/${scriptName}"; exec bash "$f"'`;
|
|
31
25
|
}
|
|
32
26
|
|
|
33
27
|
const CURSOR_HOOK_ENTRIES = Object.freeze({
|
|
@@ -113,6 +107,20 @@ const CURSOR_HOOK_ENTRIES = Object.freeze({
|
|
|
113
107
|
description: 'Log agent completion',
|
|
114
108
|
},
|
|
115
109
|
],
|
|
110
|
+
postToolUse: [
|
|
111
|
+
{
|
|
112
|
+
command: buildManagedCursorHookCommand('post-tool-use.js'),
|
|
113
|
+
event: 'postToolUse',
|
|
114
|
+
description: 'Telemetry: skill_invoked detection for Read tool and other non-shell/edit/MCP tools',
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
postToolUseFailure: [
|
|
118
|
+
{
|
|
119
|
+
command: buildManagedCursorHookCommand('post-tool-use-failure.js'),
|
|
120
|
+
event: 'postToolUseFailure',
|
|
121
|
+
description: 'Telemetry: tool_error detection on tool failure/timeout/denial',
|
|
122
|
+
},
|
|
123
|
+
],
|
|
116
124
|
beforeTabFileRead: [
|
|
117
125
|
{
|
|
118
126
|
command: buildManagedCursorHookCommand('before-tab-file-read.js'),
|
|
@@ -134,6 +142,13 @@ const CURSOR_HOOK_ENTRIES = Object.freeze({
|
|
|
134
142
|
description: 'Save state before context compaction',
|
|
135
143
|
},
|
|
136
144
|
],
|
|
145
|
+
afterAgentResponse: [
|
|
146
|
+
{
|
|
147
|
+
command: buildManagedCursorHookCommand('after-agent-response.js'),
|
|
148
|
+
event: 'afterAgentResponse',
|
|
149
|
+
description: 'Telemetry: response_completed with tokens and cost per turn',
|
|
150
|
+
},
|
|
151
|
+
],
|
|
137
152
|
stop: [
|
|
138
153
|
{
|
|
139
154
|
command: buildManagedCursorHookCommand('stop.js'),
|
|
@@ -43,6 +43,9 @@ module.exports = createInstallTargetAdapter({
|
|
|
43
43
|
repoRoot,
|
|
44
44
|
sourceRelativePath,
|
|
45
45
|
destinationDir: path.join(targetRoot, 'rules'),
|
|
46
|
+
// Cursor only loads .mdc rule files; .md is ignored.
|
|
47
|
+
// Flatten .md rule sources to .mdc at the Cursor target.
|
|
48
|
+
extensionMap: { '.md': '.mdc' },
|
|
46
49
|
});
|
|
47
50
|
}
|
|
48
51
|
|
|
@@ -160,7 +160,22 @@ function createNamespacedFlatRuleOperations(adapter, moduleId, sourceRelativePat
|
|
|
160
160
|
return operations;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
|
|
163
|
+
/**
|
|
164
|
+
* Transform an output filename's extension, preserving README.md
|
|
165
|
+
* (Cursor requires .mdc for rule files; README.md is docs and stays .md).
|
|
166
|
+
*/
|
|
167
|
+
function applyExtensionMapping(fileName, extensionMap) {
|
|
168
|
+
if (!extensionMap || fileName === 'README.md') {
|
|
169
|
+
return fileName;
|
|
170
|
+
}
|
|
171
|
+
const dot = fileName.lastIndexOf('.');
|
|
172
|
+
if (dot === -1) return fileName;
|
|
173
|
+
const ext = fileName.slice(dot);
|
|
174
|
+
const newExt = extensionMap[ext];
|
|
175
|
+
return newExt ? fileName.slice(0, dot) + newExt : fileName;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function createFlatRuleOperations({ moduleId, repoRoot, sourceRelativePath, destinationDir, extensionMap }) {
|
|
164
179
|
const normalizedSourcePath = normalizeRelativePath(sourceRelativePath);
|
|
165
180
|
const sourceRoot = path.join(repoRoot || '', normalizedSourcePath);
|
|
166
181
|
|
|
@@ -180,7 +195,8 @@ function createFlatRuleOperations({ moduleId, repoRoot, sourceRelativePath, dest
|
|
|
180
195
|
if (entry.isDirectory()) {
|
|
181
196
|
const relativeFiles = listRelativeFiles(entryPath);
|
|
182
197
|
for (const relativeFile of relativeFiles) {
|
|
183
|
-
const
|
|
198
|
+
const rawName = `${namespace}-${normalizeRelativePath(relativeFile).replace(/\//g, '-')}`;
|
|
199
|
+
const flattenedFileName = applyExtensionMapping(rawName, extensionMap);
|
|
184
200
|
operations.push(createManagedOperation({
|
|
185
201
|
moduleId,
|
|
186
202
|
sourceRelativePath: path.join(normalizedSourcePath, namespace, relativeFile),
|
|
@@ -189,10 +205,11 @@ function createFlatRuleOperations({ moduleId, repoRoot, sourceRelativePath, dest
|
|
|
189
205
|
}));
|
|
190
206
|
}
|
|
191
207
|
} else if (entry.isFile()) {
|
|
208
|
+
const fileName = applyExtensionMapping(entry.name, extensionMap);
|
|
192
209
|
operations.push(createManagedOperation({
|
|
193
210
|
moduleId,
|
|
194
211
|
sourceRelativePath: path.join(normalizedSourcePath, entry.name),
|
|
195
|
-
destinationPath: path.join(destinationDir,
|
|
212
|
+
destinationPath: path.join(destinationDir, fileName),
|
|
196
213
|
strategy: 'flatten-copy',
|
|
197
214
|
}));
|
|
198
215
|
}
|