claudex-setup 1.15.1 → 1.16.1
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/README.md +63 -28
- package/bin/cli.js +167 -7
- package/content/launch-posts.md +159 -93
- package/package.json +9 -3
- package/src/activity.js +214 -1
- package/src/analyze.js +23 -6
- package/src/audit.js +59 -14
- package/src/benchmark.js +24 -0
- package/src/context.js +119 -0
- package/src/deep-review.js +95 -68
- package/src/domain-packs.js +13 -4
- package/src/governance.js +9 -0
- package/src/index.js +4 -0
- package/src/interactive.js +8 -1
- package/src/mcp-packs.js +16 -0
- package/src/secret-patterns.js +30 -0
- package/src/techniques.js +22 -2
- package/src/watch.js +170 -42
package/content/launch-posts.md
CHANGED
|
@@ -1,160 +1,226 @@
|
|
|
1
|
-
# Launch Posts —
|
|
1
|
+
# Launch Posts — Proof-Backed Distribution Assets
|
|
2
|
+
|
|
3
|
+
**Status:** Complete — every asset below is anchored in measured proof, a canonical artifact, or a verified runtime surface
|
|
4
|
+
**Date:** 2026-04-03
|
|
5
|
+
|
|
6
|
+
## Shared Proof Anchors
|
|
7
|
+
|
|
8
|
+
Use these links as the canonical sources behind public claims:
|
|
9
|
+
|
|
10
|
+
- Proof artifact index: https://github.com/DnaFin/claudex/blob/main/research/proof-artifacts/README.md
|
|
11
|
+
- CLAUDEX self-dogfood trace: https://github.com/DnaFin/claudex/blob/main/research/proof-artifacts/claudex-self-dogfood-proof-trace-2026-04-03.md
|
|
12
|
+
- VTCLE case study: https://github.com/DnaFin/claudex/blob/main/research/case-study-vtcle-2026-04-03.md
|
|
13
|
+
- Social case study: https://github.com/DnaFin/claudex/blob/main/research/case-study-social-2026-04-03.md
|
|
14
|
+
- Polymiro case study: https://github.com/DnaFin/claudex/blob/main/research/case-study-polymiro-2026-04-03.md
|
|
15
|
+
- Public proof metrics source: https://github.com/DnaFin/claudex/blob/main/research/claudex-proof-metrics-source-2026-04-03.md
|
|
16
|
+
|
|
17
|
+
Measured-result boundary to preserve:
|
|
18
|
+
|
|
19
|
+
- before/after scores were measured with `claudex-setup@1.10.3` on `2026-04-03`
|
|
20
|
+
- current npm latest is `1.16.0`
|
|
21
|
+
- current product surface is `85 checks`
|
|
2
22
|
|
|
3
23
|
## Post 1: Reddit r/ClaudeAI
|
|
4
24
|
|
|
5
|
-
**Title:** I built a
|
|
25
|
+
**Title:** I built a CLI that audits your Claude Code setup — 85 checks, measured on 4 real repos
|
|
6
26
|
|
|
7
27
|
**Body:**
|
|
8
|
-
|
|
28
|
+
I built a zero-dependency CLI that audits how well a repo is set up for Claude Code.
|
|
9
29
|
|
|
10
|
-
|
|
30
|
+
It checks `85` things across `CLAUDE.md`, hooks, commands, agents, skills, MCP config, permissions, diagrams, and verification loops.
|
|
11
31
|
|
|
12
|
-
|
|
32
|
+
Measured on `2026-04-03` with `claudex-setup@1.10.3`:
|
|
33
|
+
- CLAUDEX: `62 -> 90`
|
|
34
|
+
- VTCLE: `46 -> 64`
|
|
35
|
+
- Social: `40 -> 48`
|
|
36
|
+
- Polymiro: `35 -> 48`
|
|
37
|
+
|
|
38
|
+
```bash
|
|
13
39
|
npx claudex-setup
|
|
14
40
|
```
|
|
15
41
|
|
|
16
|
-
It
|
|
42
|
+
It starts trust-first:
|
|
43
|
+
- audit first
|
|
44
|
+
- plan / suggest-only before writes
|
|
45
|
+
- apply only what you approve
|
|
46
|
+
- rollback artifacts for every applied batch
|
|
17
47
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
Zero dependencies. No API keys. Runs entirely local.
|
|
48
|
+
Zero dependencies. No API keys. Runs local.
|
|
21
49
|
|
|
22
50
|
GitHub: https://github.com/DnaFin/claudex-setup
|
|
23
51
|
|
|
24
|
-
|
|
52
|
+
Proof and case studies:
|
|
53
|
+
- https://github.com/DnaFin/claudex/blob/main/research/proof-artifacts/README.md
|
|
54
|
+
- https://github.com/DnaFin/claudex/blob/main/research/case-study-vtcle-2026-04-03.md
|
|
55
|
+
- https://github.com/DnaFin/claudex/blob/main/research/case-study-social-2026-04-03.md
|
|
56
|
+
- https://github.com/DnaFin/claudex/blob/main/research/case-study-polymiro-2026-04-03.md
|
|
57
|
+
|
|
58
|
+
Would love feedback on what checks or rollout surfaces are still missing.
|
|
59
|
+
|
|
60
|
+
**Evidence anchor:** proof artifact index + 3 external case studies + current proof source
|
|
25
61
|
|
|
26
62
|
---
|
|
27
63
|
|
|
28
64
|
## Post 2: Reddit r/ChatGPTCoding
|
|
29
65
|
|
|
30
|
-
**Title:**
|
|
66
|
+
**Title:** Most Claude Code repos are missing the safety layer, not the model
|
|
31
67
|
|
|
32
68
|
**Body:**
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
69
|
+
The interesting problem with Claude Code is not "can it write code?".
|
|
70
|
+
It's "is the repo actually set up so Claude can work safely and predictably?".
|
|
71
|
+
|
|
72
|
+
I built `claudex-setup` to audit that surface:
|
|
73
|
+
- `85` checks
|
|
74
|
+
- zero dependencies
|
|
75
|
+
- local-only by default
|
|
76
|
+
- trust-first flow: audit -> plan -> apply -> rollback
|
|
77
|
+
|
|
78
|
+
Measured on 4 real repos:
|
|
79
|
+
- FastAPI repo: `46 -> 64`
|
|
80
|
+
- React Native repo: `40 -> 48`
|
|
81
|
+
- Python/Docker repo: `35 -> 48`
|
|
82
|
+
- research engine repo: `62 -> 90`
|
|
83
|
+
|
|
84
|
+
```bash
|
|
43
85
|
npx claudex-setup
|
|
44
86
|
```
|
|
45
87
|
|
|
46
|
-
|
|
88
|
+
The most common misses were not exotic:
|
|
89
|
+
- no deny rules
|
|
90
|
+
- no secrets protection
|
|
91
|
+
- no mermaid architecture
|
|
92
|
+
- no hooks registered in settings
|
|
93
|
+
|
|
94
|
+
Proof:
|
|
95
|
+
https://github.com/DnaFin/claudex/blob/main/research/proof-artifacts/README.md
|
|
47
96
|
|
|
48
|
-
|
|
97
|
+
**Evidence anchor:** measured before/after traces + common gap summary from public proof set
|
|
49
98
|
|
|
50
99
|
---
|
|
51
100
|
|
|
52
101
|
## Post 3: Dev.to Article
|
|
53
102
|
|
|
54
|
-
**Title:**
|
|
103
|
+
**Title:** What 4 Real Repos Taught Me About Claude Code Readiness
|
|
55
104
|
|
|
56
105
|
**Body (excerpt):**
|
|
57
|
-
I
|
|
106
|
+
I tested `claudex-setup` on 4 real repos and the pattern was clear:
|
|
58
107
|
|
|
59
|
-
|
|
108
|
+
- the best teams still miss permission deny rules
|
|
109
|
+
- mature repos often have hooks in files but not actually registered
|
|
110
|
+
- non-standard settings formats are a real adoption trap
|
|
111
|
+
- shared `settings.json` matters more than personal local overrides
|
|
60
112
|
|
|
61
|
-
|
|
113
|
+
Measured on `2026-04-03` with `claudex-setup@1.10.3`:
|
|
114
|
+
- CLAUDEX: `62 -> 90`
|
|
115
|
+
- VTCLE: `46 -> 64`
|
|
116
|
+
- Social: `40 -> 48`
|
|
117
|
+
- Polymiro: `35 -> 48`
|
|
62
118
|
|
|
63
|
-
|
|
119
|
+
The product today is strongest as:
|
|
64
120
|
|
|
65
|
-
|
|
121
|
+
`audit -> plan -> safe apply -> governance -> benchmark`
|
|
66
122
|
|
|
67
|
-
|
|
123
|
+
Not a code generator. Not an MCP installer. A trust layer for Claude Code repos.
|
|
68
124
|
|
|
69
|
-
|
|
125
|
+
Proof packet:
|
|
126
|
+
https://github.com/DnaFin/claudex/blob/main/research/proof-artifacts/README.md
|
|
70
127
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
7. **XML tags** — `<constraints>`, `<validation>` in CLAUDE.md = unambiguous instructions.
|
|
74
|
-
|
|
75
|
-
8. **Custom agents** — Security reviewer, test writer — specialized subagents for focused tasks.
|
|
76
|
-
|
|
77
|
-
9. **Skills** — Domain-specific workflows that load on demand, not every session.
|
|
78
|
-
|
|
79
|
-
10. **MCP servers** — Connect Claude to your database, ticket system, Slack.
|
|
80
|
-
|
|
81
|
-
I packaged this into a CLI that checks your project:
|
|
82
|
-
```
|
|
83
|
-
npx claudex-setup
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
Full catalog: https://github.com/DnaFin/claudex-setup
|
|
128
|
+
**Evidence anchor:** proof artifact index + case-study docs + current proof source
|
|
87
129
|
|
|
88
130
|
---
|
|
89
131
|
|
|
90
132
|
## Post 4: Twitter/X Thread
|
|
91
133
|
|
|
92
134
|
**Tweet 1:**
|
|
93
|
-
I
|
|
94
|
-
|
|
95
|
-
Most projects use less than 5% of what Claude Code can do.
|
|
135
|
+
I built a zero-dependency CLI that audits Claude Code readiness across `85` checks.
|
|
96
136
|
|
|
97
|
-
|
|
137
|
+
Measured on 4 real repos:
|
|
138
|
+
- `62 -> 90`
|
|
139
|
+
- `46 -> 64`
|
|
140
|
+
- `40 -> 48`
|
|
141
|
+
- `35 -> 48`
|
|
98
142
|
|
|
99
|
-
npx claudex-setup
|
|
143
|
+
`npx claudex-setup`
|
|
100
144
|
|
|
101
|
-
|
|
145
|
+
Proof: github.com/DnaFin/claudex/blob/main/research/proof-artifacts/README.md
|
|
102
146
|
|
|
103
147
|
**Tweet 2:**
|
|
104
|
-
The
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
-
|
|
108
|
-
-
|
|
109
|
-
- Testing framework
|
|
110
|
-
- Project architecture
|
|
148
|
+
The most common misses were boring and important:
|
|
149
|
+
- no deny rules
|
|
150
|
+
- no secrets protection
|
|
151
|
+
- no mermaid diagram
|
|
152
|
+
- no hooks registered in settings
|
|
111
153
|
|
|
112
|
-
|
|
154
|
+
It is much more "trust layer" than "AI magic".
|
|
113
155
|
|
|
114
156
|
**Tweet 3:**
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
157
|
+
What it does well today:
|
|
158
|
+
- audit first
|
|
159
|
+
- suggest / plan before writes
|
|
160
|
+
- apply selectively
|
|
161
|
+
- emit rollback artifacts
|
|
162
|
+
- benchmark on isolated copy
|
|
120
163
|
|
|
121
164
|
**Tweet 4:**
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
CLAUDE.md instructions = ~80% compliance
|
|
125
|
-
Hooks = 100% enforcement
|
|
165
|
+
Best result so far:
|
|
166
|
+
- CLAUDEX self-dogfood: `62 -> 90`
|
|
126
167
|
|
|
127
|
-
|
|
168
|
+
Best external proof:
|
|
169
|
+
- VTCLE: `46 -> 64`
|
|
128
170
|
|
|
129
|
-
|
|
171
|
+
Case studies:
|
|
172
|
+
- github.com/DnaFin/claudex/blob/main/research/case-study-vtcle-2026-04-03.md
|
|
173
|
+
- github.com/DnaFin/claudex/blob/main/research/case-study-social-2026-04-03.md
|
|
174
|
+
- github.com/DnaFin/claudex/blob/main/research/case-study-polymiro-2026-04-03.md
|
|
130
175
|
|
|
131
176
|
**Tweet 5:**
|
|
132
|
-
|
|
177
|
+
Measured results were captured on `claudex-setup@1.10.3` on `2026-04-03`.
|
|
178
|
+
Current npm latest is `1.16.0`, so exact scores can move slightly, but the proof packet is explicit about that boundary.
|
|
133
179
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
Scores 0-100. Shows what's missing. Auto-fixes with `setup`.
|
|
137
|
-
|
|
138
|
-
Free. Open source. Zero dependencies.
|
|
139
|
-
|
|
140
|
-
https://github.com/DnaFin/claudex-setup
|
|
180
|
+
**Evidence anchor:** proof artifact index + per-repo traces
|
|
141
181
|
|
|
142
182
|
---
|
|
143
183
|
|
|
144
184
|
## Post 5: Hacker News (Show HN)
|
|
145
185
|
|
|
146
|
-
**Title:** Show HN: claudex-setup
|
|
186
|
+
**Title:** Show HN: claudex-setup — audit Claude Code readiness with 85 checks
|
|
147
187
|
|
|
148
188
|
**Body:**
|
|
149
|
-
I built a CLI
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
189
|
+
I built a CLI that audits how well a repo is set up for Claude Code.
|
|
190
|
+
|
|
191
|
+
This is not a code-quality linter and not an MCP installer.
|
|
192
|
+
It focuses on Claude workflow quality:
|
|
193
|
+
- `CLAUDE.md`
|
|
194
|
+
- hooks
|
|
195
|
+
- commands
|
|
196
|
+
- agents
|
|
197
|
+
- skills
|
|
198
|
+
- MCP config
|
|
199
|
+
- permissions / deny rules
|
|
200
|
+
- diagrams
|
|
201
|
+
- verification loops
|
|
202
|
+
|
|
203
|
+
Core workflow:
|
|
204
|
+
- `npx claudex-setup`
|
|
205
|
+
- `npx claudex-setup suggest-only`
|
|
206
|
+
- `npx claudex-setup plan`
|
|
207
|
+
- `npx claudex-setup apply`
|
|
208
|
+
- `npx claudex-setup benchmark`
|
|
209
|
+
|
|
210
|
+
Measured on 4 real repos on `2026-04-03` with `claudex-setup@1.10.3`:
|
|
211
|
+
- CLAUDEX: `62 -> 90`
|
|
212
|
+
- VTCLE: `46 -> 64`
|
|
213
|
+
- Social: `40 -> 48`
|
|
214
|
+
- Polymiro: `35 -> 48`
|
|
215
|
+
|
|
216
|
+
Trust decisions that mattered:
|
|
217
|
+
- zero dependencies
|
|
218
|
+
- audit before write
|
|
219
|
+
- rollback artifacts
|
|
220
|
+
- cross-platform Node hooks
|
|
221
|
+
- explicit proof packets instead of vague claims
|
|
222
|
+
|
|
223
|
+
Proof packet:
|
|
224
|
+
https://github.com/DnaFin/claudex/blob/main/research/proof-artifacts/README.md
|
|
225
|
+
|
|
226
|
+
**Evidence anchor:** proof artifact index + current npm proof source
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claudex-setup",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Score your repo's Claude Code setup against
|
|
3
|
+
"version": "1.16.1",
|
|
4
|
+
"description": "Score your repo's Claude Code setup against 85 checks. See gaps, apply fixes selectively with rollback, govern hooks and permissions, and benchmark impact — without breaking existing config.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"claudex-setup": "bin/cli.js"
|
|
@@ -16,7 +16,10 @@
|
|
|
16
16
|
"scripts": {
|
|
17
17
|
"start": "node bin/cli.js",
|
|
18
18
|
"build": "npm pack --dry-run",
|
|
19
|
-
"test": "node test/run.js"
|
|
19
|
+
"test": "node test/run.js",
|
|
20
|
+
"test:jest": "jest",
|
|
21
|
+
"test:coverage": "jest --coverage",
|
|
22
|
+
"test:all": "node test/run.js && node test/check-matrix.js && node test/golden-matrix.js && node test/security-tests.js && jest"
|
|
20
23
|
},
|
|
21
24
|
"keywords": [
|
|
22
25
|
"claude",
|
|
@@ -42,5 +45,8 @@
|
|
|
42
45
|
},
|
|
43
46
|
"engines": {
|
|
44
47
|
"node": ">=18.0.0"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"jest": "^30.3.0"
|
|
45
51
|
}
|
|
46
52
|
}
|
package/src/activity.js
CHANGED
|
@@ -21,10 +21,12 @@ function ensureArtifactDirs(dir) {
|
|
|
21
21
|
const activityDir = path.join(root, 'activity');
|
|
22
22
|
const rollbackDir = path.join(root, 'rollbacks');
|
|
23
23
|
const snapshotDir = path.join(root, 'snapshots');
|
|
24
|
+
const outcomesDir = path.join(root, 'outcomes');
|
|
24
25
|
fs.mkdirSync(activityDir, { recursive: true });
|
|
25
26
|
fs.mkdirSync(rollbackDir, { recursive: true });
|
|
26
27
|
fs.mkdirSync(snapshotDir, { recursive: true });
|
|
27
|
-
|
|
28
|
+
fs.mkdirSync(outcomesDir, { recursive: true });
|
|
29
|
+
return { root, activityDir, rollbackDir, snapshotDir, outcomesDir };
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
function writeJson(filePath, payload) {
|
|
@@ -140,6 +142,14 @@ function updateSnapshotIndex(snapshotDir, record) {
|
|
|
140
142
|
fs.writeFileSync(indexPath, JSON.stringify(entries, null, 2), 'utf8');
|
|
141
143
|
}
|
|
142
144
|
|
|
145
|
+
/**
|
|
146
|
+
* Write a normalized snapshot artifact to .claude/claudex-setup/snapshots/ and update the index.
|
|
147
|
+
* @param {string} dir - Project root directory.
|
|
148
|
+
* @param {string} snapshotKind - Snapshot type ('audit', 'benchmark', 'governance', 'augment', 'suggest-only').
|
|
149
|
+
* @param {Object} payload - Full result payload to persist.
|
|
150
|
+
* @param {Object} [meta={}] - Optional metadata fields merged into the envelope.
|
|
151
|
+
* @returns {Object} Artifact record with id, filePath, relativePath, indexPath, and summary.
|
|
152
|
+
*/
|
|
143
153
|
function writeSnapshotArtifact(dir, snapshotKind, payload, meta = {}) {
|
|
144
154
|
const id = timestampId();
|
|
145
155
|
const { snapshotDir } = ensureArtifactDirs(dir);
|
|
@@ -189,6 +199,12 @@ function readSnapshotIndex(dir) {
|
|
|
189
199
|
}
|
|
190
200
|
}
|
|
191
201
|
|
|
202
|
+
/**
|
|
203
|
+
* Get the audit score history from saved snapshots, most recent first.
|
|
204
|
+
* @param {string} dir - Project root directory.
|
|
205
|
+
* @param {number} [limit=20] - Maximum number of entries to return.
|
|
206
|
+
* @returns {Object[]} Array of snapshot index entries for audit snapshots.
|
|
207
|
+
*/
|
|
192
208
|
function getHistory(dir, limit = 20) {
|
|
193
209
|
const entries = readSnapshotIndex(dir);
|
|
194
210
|
return entries
|
|
@@ -197,6 +213,11 @@ function getHistory(dir, limit = 20) {
|
|
|
197
213
|
.slice(0, limit);
|
|
198
214
|
}
|
|
199
215
|
|
|
216
|
+
/**
|
|
217
|
+
* Compare the two most recent audit snapshots and return the delta.
|
|
218
|
+
* @param {string} dir - Project root directory.
|
|
219
|
+
* @returns {Object|null} Comparison with current/previous scores, delta, regressions, improvements, and trend. Null if fewer than 2 snapshots.
|
|
220
|
+
*/
|
|
200
221
|
function compareLatest(dir) {
|
|
201
222
|
const audits = getHistory(dir, 2);
|
|
202
223
|
if (audits.length < 2) return null;
|
|
@@ -303,6 +324,192 @@ function exportTrendReport(dir) {
|
|
|
303
324
|
return lines.join('\n');
|
|
304
325
|
}
|
|
305
326
|
|
|
327
|
+
function readOutcomeIndex(dir) {
|
|
328
|
+
const indexPath = path.join(dir, '.claude', 'claudex-setup', 'outcomes', 'index.json');
|
|
329
|
+
if (!fs.existsSync(indexPath)) return [];
|
|
330
|
+
try {
|
|
331
|
+
const entries = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
|
|
332
|
+
return Array.isArray(entries) ? entries : [];
|
|
333
|
+
} catch {
|
|
334
|
+
return [];
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function updateOutcomeIndex(outcomesDir, record) {
|
|
339
|
+
const indexPath = path.join(outcomesDir, 'index.json');
|
|
340
|
+
let entries = [];
|
|
341
|
+
|
|
342
|
+
if (fs.existsSync(indexPath)) {
|
|
343
|
+
try {
|
|
344
|
+
entries = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
|
|
345
|
+
if (!Array.isArray(entries)) entries = [];
|
|
346
|
+
} catch {
|
|
347
|
+
entries = [];
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
entries.push(record);
|
|
352
|
+
const MAX_INDEX_ENTRIES = 500;
|
|
353
|
+
if (entries.length > MAX_INDEX_ENTRIES) {
|
|
354
|
+
entries = entries.slice(entries.length - MAX_INDEX_ENTRIES);
|
|
355
|
+
}
|
|
356
|
+
fs.writeFileSync(indexPath, JSON.stringify(entries, null, 2), 'utf8');
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function normalizeOutcomeStatus(value) {
|
|
360
|
+
const normalized = `${value || ''}`.trim().toLowerCase();
|
|
361
|
+
if (!['accepted', 'rejected', 'deferred'].includes(normalized)) {
|
|
362
|
+
throw new Error('feedback status must be one of: accepted, rejected, deferred');
|
|
363
|
+
}
|
|
364
|
+
return normalized;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function normalizeOutcomeEffect(value) {
|
|
368
|
+
const normalized = `${value || ''}`.trim().toLowerCase();
|
|
369
|
+
if (!['positive', 'neutral', 'negative'].includes(normalized)) {
|
|
370
|
+
throw new Error('feedback effect must be one of: positive, neutral, negative');
|
|
371
|
+
}
|
|
372
|
+
return normalized;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function recordRecommendationOutcome(dir, payload) {
|
|
376
|
+
const key = `${payload.key || ''}`.trim();
|
|
377
|
+
if (!key) {
|
|
378
|
+
throw new Error('feedback requires a recommendation key');
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const status = normalizeOutcomeStatus(payload.status);
|
|
382
|
+
const effect = normalizeOutcomeEffect(payload.effect || 'neutral');
|
|
383
|
+
const scoreDelta = Number.isFinite(payload.scoreDelta) ? payload.scoreDelta : (
|
|
384
|
+
payload.scoreDelta === null || payload.scoreDelta === undefined || payload.scoreDelta === ''
|
|
385
|
+
? null
|
|
386
|
+
: Number(payload.scoreDelta)
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
if (scoreDelta !== null && !Number.isFinite(scoreDelta)) {
|
|
390
|
+
throw new Error('feedback scoreDelta must be a number when provided');
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const id = timestampId();
|
|
394
|
+
const { outcomesDir } = ensureArtifactDirs(dir);
|
|
395
|
+
const filePath = path.join(outcomesDir, `${id}.json`);
|
|
396
|
+
const record = {
|
|
397
|
+
id,
|
|
398
|
+
createdAt: new Date().toISOString(),
|
|
399
|
+
key,
|
|
400
|
+
status,
|
|
401
|
+
effect,
|
|
402
|
+
source: `${payload.source || 'manual-cli'}`.trim() || 'manual-cli',
|
|
403
|
+
notes: `${payload.notes || ''}`.trim(),
|
|
404
|
+
scoreDelta,
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
writeJson(filePath, record);
|
|
408
|
+
updateOutcomeIndex(outcomesDir, {
|
|
409
|
+
...record,
|
|
410
|
+
relativePath: path.relative(dir, filePath),
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
return {
|
|
414
|
+
id,
|
|
415
|
+
filePath,
|
|
416
|
+
relativePath: path.relative(dir, filePath),
|
|
417
|
+
record,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function summarizeOutcomeEntries(entries = []) {
|
|
422
|
+
const byKey = {};
|
|
423
|
+
|
|
424
|
+
for (const entry of entries) {
|
|
425
|
+
if (!entry || !entry.key) continue;
|
|
426
|
+
const bucket = byKey[entry.key] || {
|
|
427
|
+
key: entry.key,
|
|
428
|
+
total: 0,
|
|
429
|
+
accepted: 0,
|
|
430
|
+
rejected: 0,
|
|
431
|
+
deferred: 0,
|
|
432
|
+
positive: 0,
|
|
433
|
+
neutral: 0,
|
|
434
|
+
negative: 0,
|
|
435
|
+
scoreDeltaTotal: 0,
|
|
436
|
+
scoreDeltaCount: 0,
|
|
437
|
+
latestAt: null,
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
bucket.total += 1;
|
|
441
|
+
if (bucket[entry.status] !== undefined) bucket[entry.status] += 1;
|
|
442
|
+
if (bucket[entry.effect] !== undefined) bucket[entry.effect] += 1;
|
|
443
|
+
if (Number.isFinite(entry.scoreDelta)) {
|
|
444
|
+
bucket.scoreDeltaTotal += entry.scoreDelta;
|
|
445
|
+
bucket.scoreDeltaCount += 1;
|
|
446
|
+
}
|
|
447
|
+
if (!bucket.latestAt || new Date(entry.createdAt) > new Date(bucket.latestAt)) {
|
|
448
|
+
bucket.latestAt = entry.createdAt;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
byKey[entry.key] = bucket;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
for (const bucket of Object.values(byKey)) {
|
|
455
|
+
bucket.avgScoreDelta = bucket.scoreDeltaCount > 0
|
|
456
|
+
? Number((bucket.scoreDeltaTotal / bucket.scoreDeltaCount).toFixed(2))
|
|
457
|
+
: null;
|
|
458
|
+
bucket.evidenceClass = bucket.total > 0 ? 'measured' : 'estimated';
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return {
|
|
462
|
+
totalEntries: entries.length,
|
|
463
|
+
byKey,
|
|
464
|
+
keys: Object.keys(byKey).sort(),
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function getRecommendationOutcomeSummary(dir) {
|
|
469
|
+
return summarizeOutcomeEntries(readOutcomeIndex(dir));
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function getRecommendationAdjustment(summaryByKey, key) {
|
|
473
|
+
const bucket = summaryByKey && summaryByKey[key];
|
|
474
|
+
if (!bucket) return 0;
|
|
475
|
+
|
|
476
|
+
let adjustment = 0;
|
|
477
|
+
adjustment += bucket.accepted * 2;
|
|
478
|
+
adjustment += bucket.positive * 3;
|
|
479
|
+
adjustment -= bucket.rejected * 3;
|
|
480
|
+
adjustment -= bucket.negative * 4;
|
|
481
|
+
|
|
482
|
+
if (Number.isFinite(bucket.avgScoreDelta)) {
|
|
483
|
+
if (bucket.avgScoreDelta > 0) adjustment += Math.min(4, Math.round(bucket.avgScoreDelta / 4));
|
|
484
|
+
if (bucket.avgScoreDelta < 0) adjustment -= Math.min(4, Math.round(Math.abs(bucket.avgScoreDelta) / 4));
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
if (adjustment > 8) return 8;
|
|
488
|
+
if (adjustment < -8) return -8;
|
|
489
|
+
return adjustment;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function formatRecommendationOutcomeSummary(dir) {
|
|
493
|
+
const summary = getRecommendationOutcomeSummary(dir);
|
|
494
|
+
if (summary.totalEntries === 0) {
|
|
495
|
+
return 'No recommendation outcomes recorded yet. Use `npx claudex-setup feedback --key permissionDeny --status accepted --effect positive` after a real run.';
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const lines = [
|
|
499
|
+
'Recommendation outcome summary:',
|
|
500
|
+
'',
|
|
501
|
+
];
|
|
502
|
+
|
|
503
|
+
for (const key of summary.keys) {
|
|
504
|
+
const bucket = summary.byKey[key];
|
|
505
|
+
const avg = Number.isFinite(bucket.avgScoreDelta) ? ` | avg score delta ${bucket.avgScoreDelta >= 0 ? '+' : ''}${bucket.avgScoreDelta}` : '';
|
|
506
|
+
const adjustment = getRecommendationAdjustment(summary.byKey, key);
|
|
507
|
+
lines.push(` ${key}: total ${bucket.total} | accepted ${bucket.accepted} | rejected ${bucket.rejected} | deferred ${bucket.deferred} | positive ${bucket.positive} | negative ${bucket.negative}${avg} | ranking ${adjustment >= 0 ? '+' : ''}${adjustment}`);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
return lines.join('\n');
|
|
511
|
+
}
|
|
512
|
+
|
|
306
513
|
module.exports = {
|
|
307
514
|
ensureArtifactDirs,
|
|
308
515
|
writeActivityArtifact,
|
|
@@ -313,4 +520,10 @@ module.exports = {
|
|
|
313
520
|
compareLatest,
|
|
314
521
|
formatHistory,
|
|
315
522
|
exportTrendReport,
|
|
523
|
+
readOutcomeIndex,
|
|
524
|
+
recordRecommendationOutcome,
|
|
525
|
+
summarizeOutcomeEntries,
|
|
526
|
+
getRecommendationOutcomeSummary,
|
|
527
|
+
getRecommendationAdjustment,
|
|
528
|
+
formatRecommendationOutcomeSummary,
|
|
316
529
|
};
|
package/src/analyze.js
CHANGED
|
@@ -244,12 +244,15 @@ function toGaps(results) {
|
|
|
244
244
|
}
|
|
245
245
|
|
|
246
246
|
function toRecommendations(auditResult) {
|
|
247
|
-
const failed = auditResult.results
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
247
|
+
const failed = auditResult.results.filter(r => r.passed === false);
|
|
248
|
+
const topActionOrder = new Map((auditResult.topNextActions || []).map((item, index) => [item.key, index]));
|
|
249
|
+
failed.sort((a, b) => {
|
|
250
|
+
const rankedA = topActionOrder.has(a.key) ? topActionOrder.get(a.key) : Number.MAX_SAFE_INTEGER;
|
|
251
|
+
const rankedB = topActionOrder.has(b.key) ? topActionOrder.get(b.key) : Number.MAX_SAFE_INTEGER;
|
|
252
|
+
if (rankedA !== rankedB) return rankedA - rankedB;
|
|
253
|
+
const order = { critical: 3, high: 2, medium: 1, low: 0 };
|
|
254
|
+
return (order[b.impact] || 0) - (order[a.impact] || 0);
|
|
255
|
+
});
|
|
253
256
|
|
|
254
257
|
return failed.slice(0, 10).map((r, index) => ({
|
|
255
258
|
priority: index + 1,
|
|
@@ -259,6 +262,8 @@ function toRecommendations(auditResult) {
|
|
|
259
262
|
module: moduleFromCategory(r.category),
|
|
260
263
|
risk: riskFromImpact(r.impact),
|
|
261
264
|
why: r.fix,
|
|
265
|
+
evidenceClass: (auditResult.topNextActions || []).find(item => item.key === r.key)?.evidenceClass || 'estimated',
|
|
266
|
+
rankingAdjustment: (auditResult.topNextActions || []).find(item => item.key === r.key)?.rankingAdjustment || 0,
|
|
262
267
|
}));
|
|
263
268
|
}
|
|
264
269
|
|
|
@@ -308,6 +313,13 @@ function buildRolloutOrder(report) {
|
|
|
308
313
|
return steps;
|
|
309
314
|
}
|
|
310
315
|
|
|
316
|
+
/**
|
|
317
|
+
* Analyze a project's Claude Code setup and produce a structured recommendation report.
|
|
318
|
+
* @param {Object} options - Analysis options.
|
|
319
|
+
* @param {string} options.dir - Project directory to analyze.
|
|
320
|
+
* @param {string} [options.mode='augment'] - Analysis mode ('augment' or 'suggest-only').
|
|
321
|
+
* @returns {Promise<Object>} Structured report with project summary, gaps, strengths, and recommendations.
|
|
322
|
+
*/
|
|
311
323
|
async function analyzeProject(options) {
|
|
312
324
|
const mode = options.mode || 'augment';
|
|
313
325
|
const ctx = new ProjectContext(options.dir);
|
|
@@ -472,6 +484,11 @@ function printAnalysis(report, options = {}) {
|
|
|
472
484
|
}
|
|
473
485
|
}
|
|
474
486
|
|
|
487
|
+
/**
|
|
488
|
+
* Export an analysis report as a formatted markdown string.
|
|
489
|
+
* @param {Object} report - The report object returned by analyzeProject().
|
|
490
|
+
* @returns {string} Markdown-formatted report content.
|
|
491
|
+
*/
|
|
475
492
|
function exportMarkdown(report) {
|
|
476
493
|
const lines = [];
|
|
477
494
|
lines.push(`# Claudex Setup Analysis Report`);
|