4runr-cursor-setup 0.1.11 → 0.1.15
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 +77 -0
- package/dist/cli.js +2 -1
- package/dist/commands/doctor.js +136 -8
- package/package.json +2 -1
- package/scripts/smoke.ps1 +87 -0
package/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# 4runr-cursor-setup
|
|
2
|
+
|
|
3
|
+
## Golden Path
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
# Repair / normalize any repo (safe)
|
|
7
|
+
npx -y 4runr-cursor-setup@latest doctor --fix --purge-orphans --strict
|
|
8
|
+
|
|
9
|
+
# Install core commands
|
|
10
|
+
npx -y 4runr-cursor-setup@latest add core
|
|
11
|
+
|
|
12
|
+
# CI / health check (fails if issues)
|
|
13
|
+
npx -y 4runr-cursor-setup@latest doctor --strict
|
|
14
|
+
|
|
15
|
+
# Machine-readable audit (for automation)
|
|
16
|
+
npx -y 4runr-cursor-setup@latest doctor --json
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Contract / Invariants
|
|
20
|
+
|
|
21
|
+
- Tool never overwrites un-managed files (even with `--force`)
|
|
22
|
+
- `--force` overwrites only tool-managed files (marker + manifest)
|
|
23
|
+
- `remove <group>` deletes only manifest-owned files (marker + manifest entry)
|
|
24
|
+
- `doctor --purge-orphans` deletes only marker files not in manifest
|
|
25
|
+
- `doctor --strict` exits non-zero only when final issues exist
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# List available command groups
|
|
31
|
+
npx -y 4runr-cursor-setup@latest list
|
|
32
|
+
|
|
33
|
+
# Add a command group
|
|
34
|
+
npx -y 4runr-cursor-setup@latest add <group> [--force] [--dry-run]
|
|
35
|
+
|
|
36
|
+
# Remove a command group
|
|
37
|
+
npx -y 4runr-cursor-setup@latest remove <group> [--dry-run]
|
|
38
|
+
|
|
39
|
+
# Audit and repair
|
|
40
|
+
npx -y 4runr-cursor-setup@latest doctor [--fix] [--strict] [--json] [--dry-run] [--purge-orphans]
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Available Groups
|
|
44
|
+
|
|
45
|
+
- `core`: Essential 4runr commands (start, close)
|
|
46
|
+
- `planning`: Planning commands (phase, task)
|
|
47
|
+
- `governance`: Governance commands (decision, scope-change)
|
|
48
|
+
- `debugging`: Debugging commands (repro, verify)
|
|
49
|
+
|
|
50
|
+
## Troubleshooting
|
|
51
|
+
|
|
52
|
+
### npx Cache Issues
|
|
53
|
+
|
|
54
|
+
If `npx -y 4runr-cursor-setup@latest` fails with `ETARGET` but the version exists on npm:
|
|
55
|
+
|
|
56
|
+
This is an npx cache desync. Clear the cache and retry:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm cache clean --force
|
|
60
|
+
Remove-Item -Recurse -Force "$env:LOCALAPPDATA\npm-cache\_npx" -ErrorAction SilentlyContinue
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Development
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# Build
|
|
67
|
+
npm run build
|
|
68
|
+
|
|
69
|
+
# Run tests
|
|
70
|
+
npm test
|
|
71
|
+
|
|
72
|
+
# Run smoke test
|
|
73
|
+
npm run test:smoke
|
|
74
|
+
|
|
75
|
+
# Development mode
|
|
76
|
+
npm run dev
|
|
77
|
+
```
|
package/dist/cli.js
CHANGED
|
@@ -9,7 +9,7 @@ function usage() {
|
|
|
9
9
|
console.log(" list");
|
|
10
10
|
console.log(" add <group> [--force] [--dry-run]");
|
|
11
11
|
console.log(" remove <group> [--dry-run]");
|
|
12
|
-
console.log(" doctor [--fix] [--strict] [--json] [--dry-run] [--purge-orphans]");
|
|
12
|
+
console.log(" doctor [--fix] [--strict] [--json] [--dry-run] [--purge-orphans] [--plan]");
|
|
13
13
|
console.log("");
|
|
14
14
|
console.log("Groups: " + Object.keys(GROUPS).join(", "));
|
|
15
15
|
console.log("");
|
|
@@ -69,6 +69,7 @@ if (cmd === "doctor") {
|
|
|
69
69
|
strict: has("--strict"),
|
|
70
70
|
dryRun: has("--dry-run"),
|
|
71
71
|
purgeOrphans: has("--purge-orphans"),
|
|
72
|
+
plan: has("--plan"),
|
|
72
73
|
});
|
|
73
74
|
// Exit with the exit code set by doctor (or 0 if not set)
|
|
74
75
|
process.exit(process.exitCode || 0);
|
package/dist/commands/doctor.js
CHANGED
|
@@ -185,24 +185,52 @@ export function doctor(cwd, toolVersion, opts) {
|
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
187
|
const hasErrors = issues.some(i => i.level === "error");
|
|
188
|
+
// Generate plan if requested
|
|
189
|
+
let planItems = [];
|
|
190
|
+
if (opts.plan) {
|
|
191
|
+
planItems = generatePlan(issues, m || null);
|
|
192
|
+
}
|
|
188
193
|
if (opts.json) {
|
|
189
194
|
const jsonOutput = { ok: !hasErrors, issues };
|
|
190
195
|
if (purgedInfo) {
|
|
191
196
|
jsonOutput.purged = { count: purgedInfo.count, files: purgedInfo.files };
|
|
192
197
|
}
|
|
198
|
+
if (opts.plan) {
|
|
199
|
+
jsonOutput.plan = planItems;
|
|
200
|
+
}
|
|
193
201
|
console.log(JSON.stringify(jsonOutput, null, 2));
|
|
194
202
|
}
|
|
195
203
|
else {
|
|
196
|
-
if (
|
|
197
|
-
|
|
204
|
+
if (opts.plan) {
|
|
205
|
+
// Plan output mode
|
|
206
|
+
if (planItems.length === 0) {
|
|
207
|
+
console.log("PLAN (doctor)");
|
|
208
|
+
console.log("No actions needed.");
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
console.log("PLAN (doctor)");
|
|
212
|
+
for (let i = 0; i < planItems.length; i++) {
|
|
213
|
+
const item = planItems[i];
|
|
214
|
+
console.log(`${i + 1}) ${item.code}: ${item.detail}`);
|
|
215
|
+
for (const step of item.steps) {
|
|
216
|
+
console.log(` - ${step}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
198
221
|
else {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
222
|
+
// Normal output mode
|
|
223
|
+
if (issues.length === 0)
|
|
224
|
+
console.log("✅ doctor: OK");
|
|
225
|
+
else {
|
|
226
|
+
console.log("doctor report:");
|
|
227
|
+
for (const i of issues) {
|
|
228
|
+
let line = `- ${i.level.toUpperCase()} ${i.code}: ${i.detail}`;
|
|
229
|
+
if (i.hint) {
|
|
230
|
+
line += `\n ${i.hint}`;
|
|
231
|
+
}
|
|
232
|
+
console.log(line);
|
|
204
233
|
}
|
|
205
|
-
console.log(line);
|
|
206
234
|
}
|
|
207
235
|
}
|
|
208
236
|
}
|
|
@@ -212,3 +240,103 @@ export function doctor(cwd, toolVersion, opts) {
|
|
|
212
240
|
if (hasErrors)
|
|
213
241
|
process.exitCode = 1;
|
|
214
242
|
}
|
|
243
|
+
function inferGroupFromPath(filePath, manifest) {
|
|
244
|
+
// Try to find the file in manifest to get templateId
|
|
245
|
+
if (manifest) {
|
|
246
|
+
const fileEntry = manifest.files.find((f) => f.path === filePath);
|
|
247
|
+
if (fileEntry && fileEntry.templateId) {
|
|
248
|
+
// templateId format: "commands/{group}/{filename}"
|
|
249
|
+
const parts = fileEntry.templateId.split("/");
|
|
250
|
+
if (parts.length >= 2 && parts[0] === "commands") {
|
|
251
|
+
return parts[1];
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// Try to infer from filename pattern (e.g., "4runr-start.md" -> "core")
|
|
256
|
+
const filename = path.basename(filePath);
|
|
257
|
+
if (filename.startsWith("4runr-")) {
|
|
258
|
+
// Map common patterns (this is a fallback, not perfect)
|
|
259
|
+
if (filename.includes("start") || filename.includes("close"))
|
|
260
|
+
return "core";
|
|
261
|
+
if (filename.includes("task") || filename.includes("phase"))
|
|
262
|
+
return "planning";
|
|
263
|
+
if (filename.includes("decision") || filename.includes("scope"))
|
|
264
|
+
return "governance";
|
|
265
|
+
if (filename.includes("repro") || filename.includes("verify"))
|
|
266
|
+
return "debugging";
|
|
267
|
+
}
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
function generatePlan(issues, manifest) {
|
|
271
|
+
const planItems = [];
|
|
272
|
+
for (const issue of issues) {
|
|
273
|
+
let planItem = null;
|
|
274
|
+
switch (issue.code) {
|
|
275
|
+
case "MISSING_MANIFEST":
|
|
276
|
+
planItem = {
|
|
277
|
+
code: issue.code,
|
|
278
|
+
detail: issue.detail,
|
|
279
|
+
action: "Create baseline manifest",
|
|
280
|
+
steps: [
|
|
281
|
+
"npx -y 4runr-cursor-setup@latest doctor --fix"
|
|
282
|
+
]
|
|
283
|
+
};
|
|
284
|
+
break;
|
|
285
|
+
case "ORPHAN_MARKER_FILE":
|
|
286
|
+
planItem = {
|
|
287
|
+
code: issue.code,
|
|
288
|
+
detail: issue.detail,
|
|
289
|
+
action: "Purge orphan marker files",
|
|
290
|
+
steps: [
|
|
291
|
+
"npx -y 4runr-cursor-setup@latest doctor --purge-orphans",
|
|
292
|
+
"npx -y 4runr-cursor-setup@latest doctor --strict"
|
|
293
|
+
]
|
|
294
|
+
};
|
|
295
|
+
break;
|
|
296
|
+
case "COLLISION_UNMANAGED_FILE":
|
|
297
|
+
const group = inferGroupFromPath(issue.detail, manifest);
|
|
298
|
+
const groupPlaceholder = group || "<group>";
|
|
299
|
+
planItem = {
|
|
300
|
+
code: issue.code,
|
|
301
|
+
detail: issue.detail,
|
|
302
|
+
action: "Resolve collision at managed path",
|
|
303
|
+
steps: [
|
|
304
|
+
`Manual: rename or move ${issue.detail}`,
|
|
305
|
+
`Then run: npx -y 4runr-cursor-setup@latest add ${groupPlaceholder}`
|
|
306
|
+
]
|
|
307
|
+
};
|
|
308
|
+
break;
|
|
309
|
+
case "MANIFEST_ENTRY_MISSING_ON_DISK":
|
|
310
|
+
const missingGroup = inferGroupFromPath(issue.detail, manifest);
|
|
311
|
+
const missingGroupPlaceholder = missingGroup || "<group>";
|
|
312
|
+
planItem = {
|
|
313
|
+
code: issue.code,
|
|
314
|
+
detail: issue.detail,
|
|
315
|
+
action: "Restore missing tool-managed file",
|
|
316
|
+
steps: [
|
|
317
|
+
`Run: npx -y 4runr-cursor-setup@latest add ${missingGroupPlaceholder}`,
|
|
318
|
+
`If still missing: npx -y 4runr-cursor-setup@latest add ${missingGroupPlaceholder} --force`
|
|
319
|
+
]
|
|
320
|
+
};
|
|
321
|
+
break;
|
|
322
|
+
case "MANIFEST_FILE_MODIFIED":
|
|
323
|
+
const modifiedGroup = inferGroupFromPath(issue.detail, manifest);
|
|
324
|
+
const modifiedGroupPlaceholder = modifiedGroup || "<group>";
|
|
325
|
+
planItem = {
|
|
326
|
+
code: issue.code,
|
|
327
|
+
detail: issue.detail,
|
|
328
|
+
action: "Resolve modified managed file",
|
|
329
|
+
steps: [
|
|
330
|
+
`Manual: keep file as-is (tool will not overwrite), OR`,
|
|
331
|
+
`Manual: delete ${issue.detail} if you want template restored`,
|
|
332
|
+
`Then run: npx -y 4runr-cursor-setup@latest add ${modifiedGroupPlaceholder}`
|
|
333
|
+
]
|
|
334
|
+
};
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
if (planItem) {
|
|
338
|
+
planItems.push(planItem);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return planItems;
|
|
342
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "4runr-cursor-setup",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "node --test test/*.test.js",
|
|
8
|
+
"test:smoke": "powershell -ExecutionPolicy Bypass -File scripts/smoke.ps1",
|
|
8
9
|
"dev": "tsx src/cli.ts",
|
|
9
10
|
"build": "tsc",
|
|
10
11
|
"prepublishOnly": "npm run build",
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Smoke test for 4runr-cursor-setup golden path
|
|
2
|
+
# Runs in a temp directory to avoid dirtying the repo
|
|
3
|
+
|
|
4
|
+
$ErrorActionPreference = "Stop"
|
|
5
|
+
|
|
6
|
+
# Get the repo root (where package.json lives)
|
|
7
|
+
$repoRoot = Split-Path -Parent $PSScriptRoot
|
|
8
|
+
$cliPath = Join-Path $repoRoot "dist\cli.js"
|
|
9
|
+
|
|
10
|
+
if (-not (Test-Path $cliPath)) {
|
|
11
|
+
Write-Host "ERROR: dist/cli.js not found. Run 'npm run build' first." -ForegroundColor Red
|
|
12
|
+
exit 1
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
# Create temp directory for testing
|
|
16
|
+
$tempDir = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), "4runr-smoke-$(New-Guid)")
|
|
17
|
+
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
Write-Host "=== Smoke Test: Golden Path ===" -ForegroundColor Cyan
|
|
21
|
+
Write-Host "Test directory: $tempDir" -ForegroundColor Gray
|
|
22
|
+
Write-Host ""
|
|
23
|
+
|
|
24
|
+
# Step 1: doctor --fix --purge-orphans --strict
|
|
25
|
+
Write-Host "Step 1: doctor --fix --purge-orphans --strict" -ForegroundColor Yellow
|
|
26
|
+
Push-Location $tempDir
|
|
27
|
+
$exitCode = 0
|
|
28
|
+
node $cliPath doctor --fix --purge-orphans --strict 2>&1 | Out-String | Write-Host
|
|
29
|
+
if ($LASTEXITCODE -ne 0) {
|
|
30
|
+
Write-Host "FAILED: doctor --fix --purge-orphans --strict exited with code $LASTEXITCODE" -ForegroundColor Red
|
|
31
|
+
$exitCode = 1
|
|
32
|
+
} else {
|
|
33
|
+
Write-Host "PASSED: doctor --fix --purge-orphans --strict" -ForegroundColor Green
|
|
34
|
+
}
|
|
35
|
+
Pop-Location
|
|
36
|
+
Write-Host ""
|
|
37
|
+
|
|
38
|
+
if ($exitCode -ne 0) {
|
|
39
|
+
exit $exitCode
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# Step 2: add core
|
|
43
|
+
Write-Host "Step 2: add core" -ForegroundColor Yellow
|
|
44
|
+
Push-Location $tempDir
|
|
45
|
+
node $cliPath add core 2>&1 | Out-String | Write-Host
|
|
46
|
+
if ($LASTEXITCODE -ne 0) {
|
|
47
|
+
Write-Host "FAILED: add core exited with code $LASTEXITCODE" -ForegroundColor Red
|
|
48
|
+
$exitCode = 1
|
|
49
|
+
} else {
|
|
50
|
+
Write-Host "PASSED: add core" -ForegroundColor Green
|
|
51
|
+
}
|
|
52
|
+
Pop-Location
|
|
53
|
+
Write-Host ""
|
|
54
|
+
|
|
55
|
+
if ($exitCode -ne 0) {
|
|
56
|
+
exit $exitCode
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# Step 3: doctor --strict (should pass after fix + add)
|
|
60
|
+
Write-Host "Step 3: doctor --strict" -ForegroundColor Yellow
|
|
61
|
+
Push-Location $tempDir
|
|
62
|
+
node $cliPath doctor --strict 2>&1 | Out-String | Write-Host
|
|
63
|
+
if ($LASTEXITCODE -ne 0) {
|
|
64
|
+
Write-Host "FAILED: doctor --strict exited with code $LASTEXITCODE" -ForegroundColor Red
|
|
65
|
+
$exitCode = 1
|
|
66
|
+
} else {
|
|
67
|
+
Write-Host "PASSED: doctor --strict" -ForegroundColor Green
|
|
68
|
+
}
|
|
69
|
+
Pop-Location
|
|
70
|
+
Write-Host ""
|
|
71
|
+
|
|
72
|
+
if ($exitCode -ne 0) {
|
|
73
|
+
exit $exitCode
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
Write-Host "=== All smoke tests passed ===" -ForegroundColor Green
|
|
77
|
+
exit 0
|
|
78
|
+
|
|
79
|
+
} catch {
|
|
80
|
+
Write-Host "ERROR: $($_.Exception.Message)" -ForegroundColor Red
|
|
81
|
+
exit 1
|
|
82
|
+
} finally {
|
|
83
|
+
# Cleanup temp directory
|
|
84
|
+
if (Test-Path $tempDir) {
|
|
85
|
+
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
|
|
86
|
+
}
|
|
87
|
+
}
|