claudex-setup 1.6.0 → 1.7.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.7.0] - 2026-03-31
4
+
5
+ ### Added
6
+ - `augment` / `suggest-only` repo-aware analysis with strengths, gaps, top actions, risk notes, and rollout order
7
+ - `plan` command for exportable proposal bundles with file previews and diff-style output
8
+ - `apply` command for selective starter-safe apply flows with rollback manifests and activity artifacts
9
+ - `governance` command with permission profiles, hook registry, policy packs, and pilot rollout guidance
10
+ - `benchmark` command that measures before/after impact in an isolated temp copy and exports evidence reports
11
+ - claims governance and pilot rollout docs in `content/`
12
+
13
+ ### Changed
14
+ - `setup` now exposes reusable planning primitives and returns written/preserved file summaries
15
+ - CLI now supports `--out`, `--plan`, `--only`, and `--dry-run`
16
+ - README and docs now reflect the actual product surface instead of only audit/setup flows
17
+ - benchmark and proposal workflows now preserve existing files by default and treat mature repos as review-first
18
+
3
19
  ## [0.2.0] - 2026-03-31
4
20
 
5
21
  ### Added
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # claudex-setup
2
2
 
3
- > Score your project 0-100 for Claude Code readiness. Smart CLAUDE.md generator, 63 audit checks, interactive wizard, watch mode, CI action. Never overwrites existing config.
3
+ > Score your project 0-100 for Claude Code readiness. Discover gaps, export proposal bundles, apply safe starter changes with rollback, and benchmark the impact without touching your live repo.
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/claudex-setup)](https://www.npmjs.com/package/claudex-setup)
6
6
  [![npm downloads](https://img.shields.io/npm/dm/claudex-setup)](https://www.npmjs.com/package/claudex-setup)
@@ -11,6 +11,10 @@
11
11
  ```bash
12
12
  npx claudex-setup # Audit your project (10 seconds)
13
13
  npx claudex-setup setup # Auto-fix everything
14
+ npx claudex-setup augment # Repo-aware plan, no writes
15
+ npx claudex-setup plan # Export proposal bundles with file previews
16
+ npx claudex-setup benchmark # Measure before/after in an isolated temp copy
17
+ npx claudex-setup --threshold 60 # Fail CI if score is below 60
14
18
  ```
15
19
 
16
20
  No install. No config. No dependencies.
@@ -36,12 +40,12 @@ No install. No config. No dependencies.
36
40
  CI pipeline configured
37
41
  → Add .github/workflows/ for automated testing
38
42
 
39
- Quick wins
40
- 1. Add LICENSE file
41
- 2. Add CHANGELOG.md
43
+ Best next fixes
44
+ 1. Add CLAUDE.md verification criteria
45
+ 2. Configure safe permissions + deny rules
42
46
 
43
47
  Weakest areas:
44
- design: none (0/2)
48
+ design: none (0/2)
45
49
  devops: none (0/4)
46
50
 
47
51
  29/63 checks passing
@@ -52,9 +56,17 @@ No install. No config. No dependencies.
52
56
 
53
57
  | Command | What it does |
54
58
  |---------|-------------|
55
- | `npx claudex-setup` | **Audit** - Score 0-100 against 63 checks |
56
- | `npx claudex-setup setup` | **Setup** - Smart CLAUDE.md + hooks + commands + agents |
59
+ | `npx claudex-setup` | **Discover** - Score 0-100 against 63 checks |
60
+ | `npx claudex-setup discover` | **Discover** - Alias for audit mode |
61
+ | `npx claudex-setup setup` | **Starter** - Smart CLAUDE.md + hooks + commands + agents |
62
+ | `npx claudex-setup starter` | **Starter** - Alias for setup mode |
57
63
  | `npx claudex-setup setup --auto` | **Auto-setup** - No prompts, apply all |
64
+ | `npx claudex-setup augment` | **Augment** - Repo-aware improvement plan, no writes |
65
+ | `npx claudex-setup suggest-only` | **Suggest-Only** - Structured recommendation report, no writes |
66
+ | `npx claudex-setup plan` | **Plan** - Export proposal bundles with previews, rationale, and file-level changes |
67
+ | `npx claudex-setup apply` | **Apply** - Apply ready proposal bundles with rollback + activity artifacts |
68
+ | `npx claudex-setup governance` | **Governance** - Permission profiles, hook registry, policy packs, pilot kit |
69
+ | `npx claudex-setup benchmark` | **Benchmark** - Before/after evidence from an isolated temp copy |
58
70
  | `npx claudex-setup interactive` | **Wizard** - Step-by-step guided tour |
59
71
  | `npx claudex-setup watch` | **Watch** - Live monitoring with score delta |
60
72
  | `npx claudex-setup badge` | **Badge** - Generate shields.io badge for README |
@@ -65,8 +77,14 @@ No install. No config. No dependencies.
65
77
 
66
78
  | Flag | Effect |
67
79
  |------|--------|
80
+ | `--threshold N` | Exit with code 1 if score is below `N` (great for CI) |
81
+ | `--out FILE` | Write JSON or markdown output to a file |
82
+ | `--plan FILE` | Load a previously exported plan file |
83
+ | `--only A,B` | Limit plan/apply to selected proposal ids |
84
+ | `--dry-run` | Preview apply without writing files |
68
85
  | `--verbose` | Show all recommendations (not just critical/high) |
69
86
  | `--json` | Machine-readable JSON output (for CI) |
87
+ | `--auto` | Apply setup files without prompts |
70
88
  | `--insights` | Enable anonymous usage insights (off by default) |
71
89
 
72
90
  ## Smart CLAUDE.md Generation
@@ -74,11 +92,75 @@ No install. No config. No dependencies.
74
92
  Not a generic template. The `setup` command actually analyzes your project:
75
93
 
76
94
  - **Reads package.json** - includes your actual test, build, lint, dev commands
95
+ - **Reads pyproject.toml** - uses Python project name/description when package.json does not exist
77
96
  - **Detects framework** - Next.js Server Components, Django models, FastAPI Pydantic, React hooks
78
97
  - **TypeScript-aware** - detects strict mode, adds TS-specific rules
79
98
  - **Auto Mermaid diagram** - scans directories and generates architecture visualization (Mermaid diagrams are more token-efficient than prose descriptions, per Anthropic docs)
80
99
  - **XML constraint blocks** - adds `<constraints>` and `<verification>` with context-aware rules
81
100
  - **Verification criteria** - auto-generates checklist from your actual commands
101
+ - **Safer settings.json** - generated hooks config now includes `acceptEdits` plus deny rules for dangerous or secret-sensitive operations
102
+
103
+ ## Mode Model
104
+
105
+ - **Discover**: score the repo, surface critical issues, and show the best next actions
106
+ - **Starter**: generate a safe baseline when the repo has little or no Claude setup
107
+ - **Augment**: inspect the current repo and build a structured improvement plan without writing files
108
+ - **Suggest-Only**: same no-write analysis, optimized for sharing or manual review
109
+ - **Governance**: surface permission profiles, shipped hooks, policy packs, and pilot guidance
110
+ - **Benchmark**: prove value on an isolated copy before touching the real repo
111
+
112
+ ## Proposal + Apply Workflow
113
+
114
+ Use `plan` when you want a file-by-file proposal bundle before any write happens:
115
+
116
+ ```bash
117
+ npx claudex-setup plan --out claudex-plan.json
118
+ ```
119
+
120
+ Each proposal bundle includes:
121
+
122
+ - trigger reasons tied to failed checks
123
+ - file previews and diff-style output
124
+ - `create` vs `manual-review` classification
125
+ - risk/confidence labels
126
+
127
+ Apply only the bundles you want:
128
+
129
+ ```bash
130
+ npx claudex-setup apply --plan claudex-plan.json --only claude-md,hooks
131
+ ```
132
+
133
+ `apply` creates rollback manifests and activity artifacts under `.claude/claudex-setup/`, so every applied batch has a paper trail and a delete-based rollback path.
134
+
135
+ ## Governance And Pilot Readiness
136
+
137
+ Use `governance` when the question is "can we pilot this safely?" instead of "what files can you generate?".
138
+
139
+ ```bash
140
+ npx claudex-setup governance
141
+ ```
142
+
143
+ It exposes:
144
+
145
+ - permission profiles: `read-only`, `suggest-only`, `safe-write`, `power-user`, `internal-research`
146
+ - hook registry with trigger point, purpose, side effects, risk, and rollback path
147
+ - policy packs for baseline engineering, security-sensitive repos, OSS, and regulated-lite teams
148
+ - a pilot rollout kit with scope, approvals, success metrics, and rollback expectations
149
+
150
+ ## Benchmark And Evidence
151
+
152
+ Use `benchmark` to measure the impact of starter-safe improvements without modifying your working repo:
153
+
154
+ ```bash
155
+ npx claudex-setup benchmark --out benchmark.md
156
+ ```
157
+
158
+ Benchmark mode:
159
+
160
+ - runs a baseline audit on your repo
161
+ - copies the repo to an isolated temp workspace
162
+ - applies starter-safe artifacts only in the copy
163
+ - reruns the audit and emits before/after deltas, a case-study summary, and an executive recommendation
82
164
 
83
165
  ## 63 Checks Across 14 Categories
84
166
 
@@ -172,6 +254,7 @@ These checks evaluate **quality**, not just existence. A well-configured project
172
254
 
173
255
  - **Zero dependencies** - nothing to audit
174
256
  - **Runs 100% locally** - no cloud processing
257
+ - **Benchmark uses an isolated temp copy** - your live repo is not touched
175
258
  - **Anonymous insights** - opt-in, no PII, no file contents (enable with `--insights`)
176
259
  - **MIT Licensed** - use anywhere
177
260
 
package/bin/cli.js CHANGED
@@ -2,11 +2,115 @@
2
2
 
3
3
  const { audit } = require('../src/audit');
4
4
  const { setup } = require('../src/setup');
5
+ const { analyzeProject, printAnalysis } = require('../src/analyze');
6
+ const { buildProposalBundle, printProposalBundle, writePlanFile, applyProposalBundle, printApplyResult } = require('../src/plans');
7
+ const { getGovernanceSummary, printGovernanceSummary } = require('../src/governance');
8
+ const { runBenchmark, printBenchmark, writeBenchmarkReport } = require('../src/benchmark');
5
9
  const { version } = require('../package.json');
6
10
 
7
11
  const args = process.argv.slice(2);
8
- const command = args[0] || 'audit';
9
- const flags = args.filter(a => a.startsWith('--'));
12
+ const COMMAND_ALIASES = {
13
+ review: 'deep-review',
14
+ wizard: 'interactive',
15
+ learn: 'insights',
16
+ discover: 'audit',
17
+ starter: 'setup',
18
+ suggest: 'suggest-only',
19
+ gov: 'governance',
20
+ };
21
+ const KNOWN_COMMANDS = ['audit', 'setup', 'augment', 'suggest-only', 'plan', 'apply', 'governance', 'benchmark', 'deep-review', 'interactive', 'watch', 'badge', 'insights', 'help', 'version'];
22
+
23
+ function levenshtein(a, b) {
24
+ const matrix = Array.from({ length: a.length + 1 }, () => Array(b.length + 1).fill(0));
25
+ for (let i = 0; i <= a.length; i++) matrix[i][0] = i;
26
+ for (let j = 0; j <= b.length; j++) matrix[0][j] = j;
27
+ for (let i = 1; i <= a.length; i++) {
28
+ for (let j = 1; j <= b.length; j++) {
29
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
30
+ matrix[i][j] = Math.min(
31
+ matrix[i - 1][j] + 1,
32
+ matrix[i][j - 1] + 1,
33
+ matrix[i - 1][j - 1] + cost
34
+ );
35
+ }
36
+ }
37
+ return matrix[a.length][b.length];
38
+ }
39
+
40
+ function suggestCommand(input) {
41
+ const candidates = [...KNOWN_COMMANDS, ...Object.keys(COMMAND_ALIASES)];
42
+ let best = null;
43
+ let bestDistance = Infinity;
44
+ for (const candidate of candidates) {
45
+ const distance = levenshtein(input, candidate);
46
+ if (distance < bestDistance) {
47
+ best = candidate;
48
+ bestDistance = distance;
49
+ }
50
+ }
51
+ return bestDistance <= 3 ? best : null;
52
+ }
53
+
54
+ function parseArgs(rawArgs) {
55
+ const flags = [];
56
+ let command = 'audit';
57
+ let threshold = null;
58
+ let out = null;
59
+ let planFile = null;
60
+ let only = [];
61
+ let commandSet = false;
62
+
63
+ for (let i = 0; i < rawArgs.length; i++) {
64
+ const arg = rawArgs[i];
65
+
66
+ if (arg === '--threshold' || arg === '--out' || arg === '--plan' || arg === '--only') {
67
+ const value = rawArgs[i + 1];
68
+ if (!value || value.startsWith('--')) {
69
+ throw new Error(`${arg} requires a value`);
70
+ }
71
+ if (arg === '--threshold') threshold = value;
72
+ if (arg === '--out') out = value;
73
+ if (arg === '--plan') planFile = value;
74
+ if (arg === '--only') only = value.split(',').map(item => item.trim()).filter(Boolean);
75
+ i++;
76
+ continue;
77
+ }
78
+
79
+ if (arg.startsWith('--threshold=')) {
80
+ threshold = arg.split('=')[1];
81
+ continue;
82
+ }
83
+
84
+ if (arg.startsWith('--out=')) {
85
+ out = arg.split('=').slice(1).join('=');
86
+ continue;
87
+ }
88
+
89
+ if (arg.startsWith('--plan=')) {
90
+ planFile = arg.split('=').slice(1).join('=');
91
+ continue;
92
+ }
93
+
94
+ if (arg.startsWith('--only=')) {
95
+ only = arg.split('=').slice(1).join('=').split(',').map(item => item.trim()).filter(Boolean);
96
+ continue;
97
+ }
98
+
99
+ if (arg.startsWith('--')) {
100
+ flags.push(arg);
101
+ continue;
102
+ }
103
+
104
+ if (!commandSet) {
105
+ command = arg;
106
+ commandSet = true;
107
+ }
108
+ }
109
+
110
+ const normalizedCommand = COMMAND_ALIASES[command] || command;
111
+
112
+ return { flags, command, normalizedCommand, threshold, out, planFile, only };
113
+ }
10
114
 
11
115
  const HELP = `
12
116
  claudex-setup v${version}
@@ -15,23 +119,63 @@ const HELP = `
15
119
 
16
120
  Usage:
17
121
  npx claudex-setup Run audit on current directory
122
+ npx claudex-setup discover Discover the highest-value improvements
18
123
  npx claudex-setup audit Same as above
124
+ npx claudex-setup starter Alias for setup
19
125
  npx claudex-setup setup Apply recommended configuration
20
126
  npx claudex-setup setup --auto Apply all without prompts
21
- npx claudex-setup deep-review AI-powered config review (uses Claude Code or API key)
127
+ npx claudex-setup augment Repo-aware augment plan (no writes)
128
+ npx claudex-setup suggest-only Structured suggestion report (no writes)
129
+ npx claudex-setup plan Exportable proposal bundles with file previews
130
+ npx claudex-setup apply Apply ready proposal bundles with rollback manifest
131
+ npx claudex-setup governance Profiles, hooks, and pilot rollout guidance
132
+ npx claudex-setup benchmark Measure before/after impact in an isolated temp copy
133
+ npx claudex-setup deep-review AI-powered config review (uses Claude Code or API key)
22
134
  npx claudex-setup interactive Step-by-step guided wizard
23
135
  npx claudex-setup watch Monitor changes and re-audit live
24
136
  npx claudex-setup badge Generate shields.io badge markdown
25
137
 
26
138
  Options:
139
+ --threshold N Exit with code 1 if score is below N (useful for CI)
140
+ --out FILE Write JSON or markdown output to a file
141
+ --plan FILE Load a previously exported plan file
142
+ --only A,B Limit plan/apply to selected proposal ids or technique keys
143
+ --dry-run Preview apply without writing files
27
144
  --verbose Show all recommendations (not just critical/high)
28
145
  --json Output as JSON (for CI pipelines)
29
- --insights Enable anonymous usage insights (off by default)
146
+ --auto Apply all generated setup files without prompting
147
+ --insights Enable anonymous usage insights (off by default)
30
148
  --help Show this help
31
149
  --version Show version
150
+
151
+ Examples:
152
+ npx claudex-setup
153
+ npx claudex-setup augment
154
+ npx claudex-setup suggest-only --json
155
+ npx claudex-setup plan --out claudex-plan.json
156
+ npx claudex-setup apply --plan claudex-plan.json --only hooks,commands
157
+ npx claudex-setup governance --json
158
+ npx claudex-setup benchmark --out benchmark.md
159
+ npx claudex-setup --json --threshold 60
160
+ npx claudex-setup setup --auto
161
+ npx claudex-setup interactive
162
+
163
+ Exit codes:
164
+ 0 Success
165
+ 1 Error, unknown command, or score below --threshold
32
166
  `;
33
167
 
34
168
  async function main() {
169
+ let parsed;
170
+ try {
171
+ parsed = parseArgs(args);
172
+ } catch (err) {
173
+ console.error(`\n Error: ${err.message}\n`);
174
+ process.exit(1);
175
+ }
176
+
177
+ const { flags, command, normalizedCommand } = parsed;
178
+
35
179
  if (flags.includes('--help') || command === 'help') {
36
180
  console.log(HELP);
37
181
  process.exit(0);
@@ -46,9 +190,29 @@ async function main() {
46
190
  verbose: flags.includes('--verbose'),
47
191
  json: flags.includes('--json'),
48
192
  auto: flags.includes('--auto'),
193
+ dryRun: flags.includes('--dry-run'),
194
+ threshold: parsed.threshold !== null ? Number(parsed.threshold) : null,
195
+ out: parsed.out,
196
+ planFile: parsed.planFile,
197
+ only: parsed.only,
49
198
  dir: process.cwd()
50
199
  };
51
200
 
201
+ if (options.threshold !== null && (!Number.isFinite(options.threshold) || options.threshold < 0 || options.threshold > 100)) {
202
+ console.error('\n Error: --threshold must be a number between 0 and 100.\n');
203
+ process.exit(1);
204
+ }
205
+
206
+ if (!KNOWN_COMMANDS.includes(normalizedCommand)) {
207
+ const suggestion = suggestCommand(command);
208
+ console.error(`\n Error: Unknown command '${command}'.`);
209
+ if (suggestion) {
210
+ console.error(` Did you mean '${suggestion}'?`);
211
+ }
212
+ console.error(' Run claudex-setup --help for usage.\n');
213
+ process.exit(1);
214
+ }
215
+
52
216
  if (!require('fs').existsSync(options.dir)) {
53
217
  console.error(`\n Error: Directory not found: ${options.dir}`);
54
218
  console.error(' Run claudex-setup from inside your project directory.\n');
@@ -56,14 +220,14 @@ async function main() {
56
220
  }
57
221
 
58
222
  try {
59
- if (command === 'badge') {
223
+ if (normalizedCommand === 'badge') {
60
224
  const { getBadgeMarkdown } = require('../src/badge');
61
225
  const result = await audit({ ...options, silent: true });
62
226
  console.log(getBadgeMarkdown(result.score));
63
227
  console.log('');
64
228
  console.log('Add this to your README.md');
65
229
  process.exit(0);
66
- } else if (command === 'insights' || command === 'learn') {
230
+ } else if (normalizedCommand === 'insights') {
67
231
  const https = require('https');
68
232
  const url = 'https://claudex-insights.claudex.workers.dev/v1/stats';
69
233
  https.get(url, (res) => {
@@ -98,19 +262,58 @@ async function main() {
98
262
  console.log(' Could not reach insights server. Run locally: npx claudex-setup');
99
263
  });
100
264
  return; // keep process alive for http
101
- } else if (command === 'deep-review' || command === 'review') {
265
+ } else if (normalizedCommand === 'augment' || normalizedCommand === 'suggest-only') {
266
+ const report = await analyzeProject({ ...options, mode: normalizedCommand });
267
+ printAnalysis(report, options);
268
+ } else if (normalizedCommand === 'plan') {
269
+ const bundle = await buildProposalBundle(options);
270
+ let artifact = null;
271
+ if (options.out) {
272
+ artifact = writePlanFile(bundle, options.out);
273
+ }
274
+ printProposalBundle(bundle, options);
275
+ if (options.out && !options.json) {
276
+ console.log(` Plan written to ${options.out}`);
277
+ if (artifact) {
278
+ console.log(` Activity log: ${artifact.relativePath}`);
279
+ }
280
+ console.log('');
281
+ }
282
+ } else if (normalizedCommand === 'apply') {
283
+ const result = await applyProposalBundle(options);
284
+ printApplyResult(result, options);
285
+ } else if (normalizedCommand === 'governance') {
286
+ const summary = getGovernanceSummary();
287
+ printGovernanceSummary(summary, options);
288
+ } else if (normalizedCommand === 'benchmark') {
289
+ const report = await runBenchmark(options);
290
+ if (options.out) {
291
+ writeBenchmarkReport(report, options.out);
292
+ }
293
+ printBenchmark(report, options);
294
+ if (options.out && !options.json) {
295
+ console.log(` Benchmark report written to ${options.out}`);
296
+ console.log('');
297
+ }
298
+ } else if (normalizedCommand === 'deep-review') {
102
299
  const { deepReview } = require('../src/deep-review');
103
300
  await deepReview(options);
104
- } else if (command === 'interactive' || command === 'wizard') {
301
+ } else if (normalizedCommand === 'interactive') {
105
302
  const { interactive } = require('../src/interactive');
106
303
  await interactive(options);
107
- } else if (command === 'watch') {
304
+ } else if (normalizedCommand === 'watch') {
108
305
  const { watch } = require('../src/watch');
109
306
  await watch(options);
110
- } else if (command === 'setup') {
307
+ } else if (normalizedCommand === 'setup') {
111
308
  await setup(options);
112
309
  } else {
113
- await audit(options);
310
+ const result = await audit(options);
311
+ if (options.threshold !== null && result.score < options.threshold) {
312
+ if (!options.json) {
313
+ console.error(` Threshold failed: score ${result.score}/100 is below required ${options.threshold}/100.\n`);
314
+ }
315
+ process.exit(1);
316
+ }
114
317
  }
115
318
  } catch (err) {
116
319
  console.error(`\n Error: ${err.message}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudex-setup",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "Audit and optimize any project for Claude Code. Powered by 1107 verified techniques.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -0,0 +1,60 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ function timestampId() {
5
+ return new Date().toISOString().replace(/[:.]/g, '-');
6
+ }
7
+
8
+ function ensureArtifactDirs(dir) {
9
+ const root = path.join(dir, '.claude', 'claudex-setup');
10
+ const activityDir = path.join(root, 'activity');
11
+ const rollbackDir = path.join(root, 'rollbacks');
12
+ fs.mkdirSync(activityDir, { recursive: true });
13
+ fs.mkdirSync(rollbackDir, { recursive: true });
14
+ return { root, activityDir, rollbackDir };
15
+ }
16
+
17
+ function writeJson(filePath, payload) {
18
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
19
+ fs.writeFileSync(filePath, JSON.stringify(payload, null, 2), 'utf8');
20
+ }
21
+
22
+ function writeActivityArtifact(dir, type, payload) {
23
+ const id = timestampId();
24
+ const { activityDir } = ensureArtifactDirs(dir);
25
+ const filePath = path.join(activityDir, `${id}-${type}.json`);
26
+ writeJson(filePath, {
27
+ id,
28
+ type,
29
+ createdAt: new Date().toISOString(),
30
+ ...payload,
31
+ });
32
+ return {
33
+ id,
34
+ filePath,
35
+ relativePath: path.relative(dir, filePath),
36
+ };
37
+ }
38
+
39
+ function writeRollbackArtifact(dir, payload) {
40
+ const id = timestampId();
41
+ const { rollbackDir } = ensureArtifactDirs(dir);
42
+ const filePath = path.join(rollbackDir, `${id}.json`);
43
+ writeJson(filePath, {
44
+ id,
45
+ createdAt: new Date().toISOString(),
46
+ rollbackType: 'delete-created-files',
47
+ ...payload,
48
+ });
49
+ return {
50
+ id,
51
+ filePath,
52
+ relativePath: path.relative(dir, filePath),
53
+ };
54
+ }
55
+
56
+ module.exports = {
57
+ ensureArtifactDirs,
58
+ writeActivityArtifact,
59
+ writeRollbackArtifact,
60
+ };