safeword 0.8.7 → 0.8.10

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.
Files changed (40) hide show
  1. package/dist/{check-PPVIEF3Q.js → check-4HX4SNVV.js} +27 -27
  2. package/dist/check-4HX4SNVV.js.map +1 -0
  3. package/dist/{chunk-W66Z3C5H.js → chunk-FJYRWU2V.js} +5 -5
  4. package/dist/chunk-FJYRWU2V.js.map +1 -0
  5. package/dist/chunk-POPS3ZRQ.js +1219 -0
  6. package/dist/chunk-POPS3ZRQ.js.map +1 -0
  7. package/dist/cli.js +5 -9
  8. package/dist/cli.js.map +1 -1
  9. package/dist/{diff-S3ICSYQY.js → diff-7QIV6Z5B.js} +14 -16
  10. package/dist/diff-7QIV6Z5B.js.map +1 -0
  11. package/dist/index.d.ts +2 -7
  12. package/dist/reset-RP7AGR2Y.js +73 -0
  13. package/dist/reset-RP7AGR2Y.js.map +1 -0
  14. package/dist/setup-ZYRPDTQI.js +91 -0
  15. package/dist/setup-ZYRPDTQI.js.map +1 -0
  16. package/dist/upgrade-K2FFESUH.js +76 -0
  17. package/dist/upgrade-K2FFESUH.js.map +1 -0
  18. package/package.json +2 -2
  19. package/templates/SAFEWORD.md +5 -2
  20. package/templates/commands/cleanup-zombies.md +48 -0
  21. package/templates/guides/cli-reference.md +9 -11
  22. package/templates/guides/zombie-process-cleanup.md +40 -24
  23. package/templates/scripts/cleanup-zombies.sh +222 -0
  24. package/dist/check-PPVIEF3Q.js.map +0 -1
  25. package/dist/chunk-34PU3QZI.js +0 -1047
  26. package/dist/chunk-34PU3QZI.js.map +0 -1
  27. package/dist/chunk-3OK3NQEW.js +0 -476
  28. package/dist/chunk-3OK3NQEW.js.map +0 -1
  29. package/dist/chunk-BFBUEJDH.js +0 -88
  30. package/dist/chunk-BFBUEJDH.js.map +0 -1
  31. package/dist/chunk-W66Z3C5H.js.map +0 -1
  32. package/dist/diff-S3ICSYQY.js.map +0 -1
  33. package/dist/reset-ZST2SGZ2.js +0 -74
  34. package/dist/reset-ZST2SGZ2.js.map +0 -1
  35. package/dist/setup-ANAIEP3D.js +0 -100
  36. package/dist/setup-ANAIEP3D.js.map +0 -1
  37. package/dist/sync-V6D7QTMO.js +0 -9
  38. package/dist/sync-V6D7QTMO.js.map +0 -1
  39. package/dist/upgrade-QFIGWZ5I.js +0 -76
  40. package/dist/upgrade-QFIGWZ5I.js.map +0 -1
@@ -0,0 +1,76 @@
1
+ import {
2
+ compareVersions
3
+ } from "./chunk-FJYRWU2V.js";
4
+ import {
5
+ SAFEWORD_SCHEMA,
6
+ createProjectContext,
7
+ error,
8
+ exists,
9
+ header,
10
+ info,
11
+ listItem,
12
+ readFileSafe,
13
+ reconcile,
14
+ success,
15
+ warn
16
+ } from "./chunk-POPS3ZRQ.js";
17
+ import {
18
+ VERSION
19
+ } from "./chunk-ORQHKDT2.js";
20
+
21
+ // src/commands/upgrade.ts
22
+ import nodePath from "path";
23
+ function getProjectVersion(safewordDirectory) {
24
+ const versionPath = nodePath.join(safewordDirectory, "version");
25
+ return readFileSafe(versionPath)?.trim() ?? "0.0.0";
26
+ }
27
+ function printUpgradeSummary(result, projectVersion) {
28
+ header("Upgrade Complete");
29
+ info(`
30
+ Version: v${projectVersion} \u2192 v${VERSION}`);
31
+ if (result.created.length > 0) {
32
+ info("\nCreated:");
33
+ for (const file of result.created) listItem(file);
34
+ }
35
+ if (result.updated.length > 0) {
36
+ info("\nUpdated:");
37
+ for (const file of result.updated) listItem(file);
38
+ }
39
+ if (result.packagesToRemove.length > 0) {
40
+ warn(`
41
+ ${result.packagesToRemove.length} package(s) are now bundled in eslint-plugin-safeword:`);
42
+ for (const pkg of result.packagesToRemove) listItem(pkg);
43
+ info("\nIf you don't use these elsewhere, you can remove them:");
44
+ listItem(`npm uninstall ${result.packagesToRemove.join(" ")}`);
45
+ }
46
+ success(`
47
+ Safeword upgraded to v${VERSION}`);
48
+ }
49
+ async function upgrade() {
50
+ const cwd = process.cwd();
51
+ const safewordDirectory = nodePath.join(cwd, ".safeword");
52
+ if (!exists(safewordDirectory)) {
53
+ error("Not configured. Run `safeword setup` first.");
54
+ process.exit(1);
55
+ }
56
+ const projectVersion = getProjectVersion(safewordDirectory);
57
+ if (compareVersions(VERSION, projectVersion) < 0) {
58
+ error(`CLI v${VERSION} is older than project v${projectVersion}.`);
59
+ error("Update the CLI first: npm install -g safeword");
60
+ process.exit(1);
61
+ }
62
+ header("Safeword Upgrade");
63
+ info(`Upgrading from v${projectVersion} to v${VERSION}`);
64
+ try {
65
+ const ctx = createProjectContext(cwd);
66
+ const result = await reconcile(SAFEWORD_SCHEMA, "upgrade", ctx);
67
+ printUpgradeSummary(result, projectVersion);
68
+ } catch (error_) {
69
+ error(`Upgrade failed: ${error_ instanceof Error ? error_.message : "Unknown error"}`);
70
+ process.exit(1);
71
+ }
72
+ }
73
+ export {
74
+ upgrade
75
+ };
76
+ //# sourceMappingURL=upgrade-K2FFESUH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/upgrade.ts"],"sourcesContent":["/**\n * Upgrade command - Update safeword configuration to latest version\n *\n * Uses reconcile() with mode='upgrade' to update all managed files.\n */\n\nimport nodePath from 'node:path';\n\nimport { reconcile, type ReconcileResult } from '../reconcile.js';\nimport { SAFEWORD_SCHEMA } from '../schema.js';\nimport { createProjectContext } from '../utils/context.js';\nimport { exists, readFileSafe } from '../utils/fs.js';\nimport { error, header, info, listItem, success, warn } from '../utils/output.js';\nimport { compareVersions } from '../utils/version.js';\nimport { VERSION } from '../version.js';\n\nfunction getProjectVersion(safewordDirectory: string): string {\n const versionPath = nodePath.join(safewordDirectory, 'version');\n return readFileSafe(versionPath)?.trim() ?? '0.0.0';\n}\n\nfunction printUpgradeSummary(result: ReconcileResult, projectVersion: string): void {\n header('Upgrade Complete');\n info(`\\nVersion: v${projectVersion} → v${VERSION}`);\n\n if (result.created.length > 0) {\n info('\\nCreated:');\n for (const file of result.created) listItem(file);\n }\n\n if (result.updated.length > 0) {\n info('\\nUpdated:');\n for (const file of result.updated) listItem(file);\n }\n\n if (result.packagesToRemove.length > 0) {\n warn(`\\n${result.packagesToRemove.length} package(s) are now bundled in eslint-plugin-safeword:`);\n for (const pkg of result.packagesToRemove) listItem(pkg);\n info(\"\\nIf you don't use these elsewhere, you can remove them:\");\n listItem(`npm uninstall ${result.packagesToRemove.join(' ')}`);\n }\n\n success(`\\nSafeword upgraded to v${VERSION}`);\n}\n\nexport async function upgrade(): Promise<void> {\n const cwd = process.cwd();\n const safewordDirectory = nodePath.join(cwd, '.safeword');\n\n if (!exists(safewordDirectory)) {\n error('Not configured. Run `safeword setup` first.');\n process.exit(1);\n }\n\n const projectVersion = getProjectVersion(safewordDirectory);\n\n if (compareVersions(VERSION, projectVersion) < 0) {\n error(`CLI v${VERSION} is older than project v${projectVersion}.`);\n error('Update the CLI first: npm install -g safeword');\n process.exit(1);\n }\n\n header('Safeword Upgrade');\n info(`Upgrading from v${projectVersion} to v${VERSION}`);\n\n try {\n const ctx = createProjectContext(cwd);\n const result = await reconcile(SAFEWORD_SCHEMA, 'upgrade', ctx);\n printUpgradeSummary(result, projectVersion);\n } catch (error_) {\n error(`Upgrade failed: ${error_ instanceof Error ? error_.message : 'Unknown error'}`);\n process.exit(1);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAMA,OAAO,cAAc;AAUrB,SAAS,kBAAkB,mBAAmC;AAC5D,QAAM,cAAc,SAAS,KAAK,mBAAmB,SAAS;AAC9D,SAAO,aAAa,WAAW,GAAG,KAAK,KAAK;AAC9C;AAEA,SAAS,oBAAoB,QAAyB,gBAA8B;AAClF,SAAO,kBAAkB;AACzB,OAAK;AAAA,YAAe,cAAc,YAAO,OAAO,EAAE;AAElD,MAAI,OAAO,QAAQ,SAAS,GAAG;AAC7B,SAAK,YAAY;AACjB,eAAW,QAAQ,OAAO,QAAS,UAAS,IAAI;AAAA,EAClD;AAEA,MAAI,OAAO,QAAQ,SAAS,GAAG;AAC7B,SAAK,YAAY;AACjB,eAAW,QAAQ,OAAO,QAAS,UAAS,IAAI;AAAA,EAClD;AAEA,MAAI,OAAO,iBAAiB,SAAS,GAAG;AACtC,SAAK;AAAA,EAAK,OAAO,iBAAiB,MAAM,wDAAwD;AAChG,eAAW,OAAO,OAAO,iBAAkB,UAAS,GAAG;AACvD,SAAK,0DAA0D;AAC/D,aAAS,iBAAiB,OAAO,iBAAiB,KAAK,GAAG,CAAC,EAAE;AAAA,EAC/D;AAEA,UAAQ;AAAA,wBAA2B,OAAO,EAAE;AAC9C;AAEA,eAAsB,UAAyB;AAC7C,QAAM,MAAM,QAAQ,IAAI;AACxB,QAAM,oBAAoB,SAAS,KAAK,KAAK,WAAW;AAExD,MAAI,CAAC,OAAO,iBAAiB,GAAG;AAC9B,UAAM,6CAA6C;AACnD,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,iBAAiB,kBAAkB,iBAAiB;AAE1D,MAAI,gBAAgB,SAAS,cAAc,IAAI,GAAG;AAChD,UAAM,QAAQ,OAAO,2BAA2B,cAAc,GAAG;AACjE,UAAM,+CAA+C;AACrD,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,SAAO,kBAAkB;AACzB,OAAK,mBAAmB,cAAc,QAAQ,OAAO,EAAE;AAEvD,MAAI;AACF,UAAM,MAAM,qBAAqB,GAAG;AACpC,UAAM,SAAS,MAAM,UAAU,iBAAiB,WAAW,GAAG;AAC9D,wBAAoB,QAAQ,cAAc;AAAA,EAC5C,SAAS,QAAQ;AACf,UAAM,mBAAmB,kBAAkB,QAAQ,OAAO,UAAU,eAAe,EAAE;AACrF,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "safeword",
3
- "version": "0.8.7",
3
+ "version": "0.8.10",
4
4
  "description": "CLI for setting up and managing safeword development environments",
5
5
  "type": "module",
6
6
  "bin": {
@@ -23,7 +23,7 @@
23
23
  "build": "tsup",
24
24
  "dev": "tsup --watch",
25
25
  "test": "vitest run",
26
- "test:e2e": "vitest run tests/e2e/",
26
+ "test:integration": "vitest run tests/integration/",
27
27
  "test:watch": "vitest",
28
28
  "test:coverage": "vitest run --coverage",
29
29
  "typecheck": "tsc --noEmit",
@@ -86,6 +86,7 @@ Training data is stale. Follow this sequence:
86
86
  | Test definitions | `.safeword/planning/test-definitions/` | `feature-*.md` (L2 features only) |
87
87
  | Design docs | `.safeword/planning/design/` | Complex features (3+ components) |
88
88
  | Issues | `.safeword/planning/issues/` | Issue tracking |
89
+ | Execution plans | `.safeword/planning/plans/` | LLM-ready task breakdowns |
89
90
 
90
91
  **Artifact Levels:**
91
92
 
@@ -311,7 +312,9 @@ When markdown lint reports MD040 (missing language), choose:
311
312
 
312
313
  1. **Clarity → Simplicity → Correctness** (in that order)
313
314
  2. **Test what you can test**—never ask user to verify
314
- 3. **RED → GREEN → REFACTOR**—never skip steps
315
+ 3. **ALWAYS USE STRICT TDD: RED → GREEN → REFACTOR**—never skip steps
315
316
  4. **Commit after each GREEN phase**
316
317
  5. **Read the matching guide** when a trigger fires
317
- 6. **End every response** with: `{"proposedChanges": bool, "madeChanges": bool, "askedQuestion": bool}`
318
+ 6. **Always read the latest documentation for the relevant tool**
319
+ 7. **AVOID BLOAT**
320
+ 8. **End every response** with: `{"proposedChanges": bool, "madeChanges": bool, "askedQuestion": bool}`
@@ -0,0 +1,48 @@
1
+ ---
2
+ description: Kill zombie dev servers and test processes for this project
3
+ ---
4
+
5
+ # Cleanup Zombies
6
+
7
+ Kill zombie processes (dev servers, Playwright browsers, test runners) for the current project only. Safe to use in multi-project environments.
8
+
9
+ ## Instructions
10
+
11
+ Run the cleanup script with `--dry-run` first to preview what will be killed:
12
+
13
+ ```bash
14
+ ./.safeword/scripts/cleanup-zombies.sh --dry-run
15
+ ```
16
+
17
+ If the output looks correct, run without `--dry-run`:
18
+
19
+ ```bash
20
+ ./.safeword/scripts/cleanup-zombies.sh
21
+ ```
22
+
23
+ ## What It Does
24
+
25
+ 1. **Auto-detects framework** - Finds port from vite.config.ts, next.config.js, etc. (checks root, `packages/*/`, `apps/*/` for monorepos)
26
+ 2. **Kills by port** - Dev server port AND test port (port + 1000)
27
+ 3. **Kills test processes** - Playwright, Chromium, Electron (scoped to this project)
28
+ 4. **Multi-project safe** - Only kills processes matching this project's directory
29
+
30
+ ## Manual Override
31
+
32
+ If auto-detection fails or you need a specific port:
33
+
34
+ ```bash
35
+ # Explicit port
36
+ ./.safeword/scripts/cleanup-zombies.sh 5173
37
+
38
+ # Port + additional pattern
39
+ ./.safeword/scripts/cleanup-zombies.sh 5173 "electron"
40
+ ```
41
+
42
+ ## When to Use
43
+
44
+ - Port already in use when starting dev server
45
+ - Tests hanging or failing due to zombie processes
46
+ - Switching between projects
47
+ - Before running E2E tests
48
+ - After interrupted test runs
@@ -4,14 +4,13 @@ Commands for managing safeword in projects.
4
4
 
5
5
  ## Commands
6
6
 
7
- | Command | Purpose |
8
- | ----------------------------- | ---------------------------------------------- |
9
- | `npx safeword@latest setup` | Install safeword in current project |
10
- | `npx safeword@latest check` | Check project health and versions |
11
- | `npx safeword@latest upgrade` | Upgrade to latest version |
12
- | `npx safeword@latest diff` | Preview changes before upgrading |
13
- | `npx safeword sync` | Sync linting plugins with project dependencies |
14
- | `npx safeword reset` | Remove safeword from project |
7
+ | Command | Purpose |
8
+ | ----------------------------- | --------------------------------- |
9
+ | `npx safeword@latest setup` | Install safeword in current project |
10
+ | `npx safeword@latest check` | Check project health and versions |
11
+ | `npx safeword@latest upgrade` | Upgrade to latest version |
12
+ | `npx safeword@latest diff` | Preview changes before upgrading |
13
+ | `npx safeword reset` | Remove safeword from project |
15
14
 
16
15
  ## When to Use
17
16
 
@@ -21,7 +20,6 @@ Commands for managing safeword in projects.
21
20
  | Check if update available | `npx safeword@latest check` |
22
21
  | Update after CLI release | `npx safeword@latest upgrade` |
23
22
  | See what upgrade changes | `npx safeword@latest diff` |
24
- | Added/removed framework | `npx safeword sync` |
25
23
  | Remove safeword completely | `npx safeword reset --full` |
26
24
 
27
25
  ## Options
@@ -32,12 +30,12 @@ Common flags:
32
30
 
33
31
  - `-y, --yes` - Skip confirmations (setup, reset)
34
32
  - `-v, --verbose` - Show detailed output (diff)
35
- - `-q, --quiet` - Suppress output (sync)
33
+ - `--offline` - Skip remote version check (check)
34
+ - `--full` - Also remove linting config + packages (reset)
36
35
 
37
36
  ---
38
37
 
39
38
  ## Key Takeaways
40
39
 
41
40
  - Always use `@latest` for setup/check/upgrade/diff to get current CLI
42
- - Run `sync` after adding/removing frameworks to update linting plugins
43
41
  - Use `diff` before `upgrade` to preview changes
@@ -4,6 +4,24 @@
4
4
 
5
5
  ---
6
6
 
7
+ ## Quick Start
8
+
9
+ Use the built-in cleanup script:
10
+
11
+ ```bash
12
+ # Preview what would be killed (safe)
13
+ ./.safeword/scripts/cleanup-zombies.sh --dry-run
14
+
15
+ # Kill zombie processes
16
+ ./.safeword/scripts/cleanup-zombies.sh
17
+ ```
18
+
19
+ The script auto-detects your framework (Vite, Next.js, etc.) and kills only processes belonging to this project.
20
+
21
+ For manual control or debugging, see the detailed sections below.
22
+
23
+ ---
24
+
7
25
  ## The Problem
8
26
 
9
27
  When running dev servers and E2E tests across multiple projects, zombie processes accumulate:
@@ -52,35 +70,32 @@ sleep 2
52
70
 
53
71
  ---
54
72
 
55
- ## Project-Specific Cleanup Script
73
+ ## Built-in Cleanup Script
56
74
 
57
- For frequent cleanup needs, create `scripts/cleanup.sh` in each project:
75
+ Safeword includes a cleanup script at `.safeword/scripts/cleanup-zombies.sh`:
58
76
 
59
77
  ```bash
60
- #!/bin/bash
61
- # scripts/cleanup.sh - Kill only THIS project's processes
62
-
63
- DEV_PORT=3000 # Dev server port (change per project)
64
- TEST_PORT=$((DEV_PORT + 1000)) # Test server port (Playwright managed)
65
- PROJECT_DIR="$(pwd)"
78
+ # Auto-detect framework and clean up
79
+ ./.safeword/scripts/cleanup-zombies.sh
66
80
 
67
- echo "Cleaning up $PROJECT_DIR (dev: $DEV_PORT, test: $TEST_PORT)..."
81
+ # Preview first (recommended)
82
+ ./.safeword/scripts/cleanup-zombies.sh --dry-run
68
83
 
69
- # Kill both dev and test servers by port
70
- lsof -ti:$DEV_PORT -ti:$TEST_PORT | xargs kill -9 2>/dev/null
84
+ # Explicit port override
85
+ ./.safeword/scripts/cleanup-zombies.sh 5173
71
86
 
72
- # Kill Playwright browsers for this project
73
- ps aux | grep -E "(playwright|chromium)" | grep "$PROJECT_DIR" | grep -v grep | awk '{print $2}' | xargs kill -9 2>/dev/null
74
-
75
- # Kill test runners
76
- pgrep -f "playwright test.*$(basename $PROJECT_DIR)" | xargs kill -9 2>/dev/null
77
-
78
- echo "Cleanup complete!"
87
+ # Port + additional pattern
88
+ ./.safeword/scripts/cleanup-zombies.sh 5173 "electron"
79
89
  ```
80
90
 
81
- **Make executable:** `chmod +x scripts/cleanup.sh`
91
+ **Features:**
92
+
93
+ - Auto-detects port from config files (vite.config.ts, next.config.js, etc.)
94
+ - Kills dev port AND test port (port + 1000)
95
+ - Scopes all pattern matching to current project directory
96
+ - `--dry-run` shows what would be killed without killing
82
97
 
83
- **Usage:** `./scripts/cleanup.sh`
98
+ **Supported frameworks:** Vite, Next.js, Nuxt, SvelteKit, Astro, Angular
84
99
 
85
100
  ---
86
101
 
@@ -197,9 +212,10 @@ ps aux | grep "/Users/alex/projects/my-project"
197
212
 
198
213
  | Situation | Command |
199
214
  | ---------------------------------------- | ---------------------------------------------------------------- |
215
+ | Quick cleanup (recommended) | `./.safeword/scripts/cleanup-zombies.sh` |
216
+ | Preview before killing | `./.safeword/scripts/cleanup-zombies.sh --dry-run` |
200
217
  | Kill dev + test servers (use your ports) | `lsof -ti:$DEV_PORT -ti:$TEST_PORT \| xargs kill -9 2>/dev/null` |
201
218
  | Kill Playwright (this project) | `pkill -f "playwright.*$(pwd)"` |
202
- | Kill all for this project | `./scripts/cleanup.sh` |
203
219
  | Check what's on port | `lsof -i:3000` |
204
220
  | Find zombie processes | `ps aux \| grep -E "(node\|playwright\|chromium)"` |
205
221
  | Preview what `pkill -f` would kill | `pgrep -f "pattern"` (verify before running pkill) |
@@ -224,7 +240,7 @@ ps aux | grep "/Users/alex/projects/my-project"
224
240
 
225
241
  ## Key Takeaways
226
242
 
227
- - Use port-based cleanup, not `killall node` (affects all projects)
228
- - Filter by project directory: `pkill -f "playwright.*$(pwd)"`
243
+ - Use `./.safeword/scripts/cleanup-zombies.sh` for quick, safe cleanup
244
+ - Always preview with `--dry-run` first when unsure
245
+ - Never use `killall node` (affects all projects)
229
246
  - Clean up before AND after development sessions
230
- - Create project-specific `scripts/cleanup.sh` for consistent cleanup
@@ -0,0 +1,222 @@
1
+ #!/bin/bash
2
+ # Cleanup zombie processes: Kill dev servers and test processes for THIS project
3
+ #
4
+ # Use when: Port in use after tests, dev server won't start, zombie node/playwright
5
+ # processes, need to clean up before running tests, switching between projects
6
+ #
7
+ # Auto-detection: Checks root, packages/*/, apps/*/ for framework configs (monorepo support)
8
+ #
9
+ # Usage: ./cleanup-zombies.sh [port] [pattern]
10
+ # Example: ./cleanup-zombies.sh # auto-detect from config files
11
+ # Example: ./cleanup-zombies.sh 5173 # explicit port
12
+ # Example: ./cleanup-zombies.sh 5173 "vite" # port + pattern
13
+ # Example: ./cleanup-zombies.sh --dry-run # preview what would be killed
14
+ # Example: ./cleanup-zombies.sh --dry-run 5173 # preview with explicit port
15
+
16
+ set -e
17
+
18
+ # Colors for output
19
+ GREEN='\033[0;32m'
20
+ YELLOW='\033[0;33m'
21
+ NC='\033[0m' # No Color
22
+
23
+ # Parse arguments
24
+ DRY_RUN=false
25
+ PORT=""
26
+ PATTERN=""
27
+
28
+ for arg in "$@"; do
29
+ case "$arg" in
30
+ --dry-run)
31
+ DRY_RUN=true
32
+ ;;
33
+ --help|-h)
34
+ echo "Usage: $0 [--dry-run] [port] [pattern]"
35
+ echo ""
36
+ echo "Cleanup zombie processes for the current project."
37
+ echo ""
38
+ echo "Options:"
39
+ echo " --dry-run Show what would be killed without killing"
40
+ echo " --help Show this help message"
41
+ echo ""
42
+ echo "Arguments:"
43
+ echo " port Port number (auto-detected if not provided)"
44
+ echo " pattern Additional process pattern to match"
45
+ echo ""
46
+ echo "Examples:"
47
+ echo " $0 # Auto-detect port from config files"
48
+ echo " $0 5173 # Kill processes on port 5173"
49
+ echo " $0 5173 electron # Port 5173 + electron processes"
50
+ echo " $0 --dry-run # Preview without killing"
51
+ exit 0
52
+ ;;
53
+ *)
54
+ if [ -z "$PORT" ] && [[ "$arg" =~ ^[0-9]+$ ]]; then
55
+ PORT="$arg"
56
+ elif [ -z "$PATTERN" ]; then
57
+ PATTERN="$arg"
58
+ fi
59
+ ;;
60
+ esac
61
+ done
62
+
63
+ # Check if any file matching pattern exists (supports globs)
64
+ has_config() {
65
+ local pattern=$1
66
+ # Check root first, then common monorepo locations
67
+ compgen -G "$pattern" >/dev/null 2>&1 && return 0
68
+ compgen -G "packages/*/$pattern" >/dev/null 2>&1 && return 0
69
+ compgen -G "apps/*/$pattern" >/dev/null 2>&1 && return 0
70
+ return 1
71
+ }
72
+
73
+ # Auto-detect port from framework config files
74
+ detect_port() {
75
+ if has_config "vite.config.*"; then
76
+ echo "5173"
77
+ elif has_config "next.config.*"; then
78
+ echo "3000"
79
+ elif has_config "nuxt.config.*"; then
80
+ echo "3000"
81
+ elif has_config "svelte.config.js"; then
82
+ echo "5173"
83
+ elif has_config "astro.config.*"; then
84
+ echo "4321"
85
+ elif has_config "angular.json"; then
86
+ echo "4200"
87
+ else
88
+ echo ""
89
+ fi
90
+ }
91
+
92
+ # Auto-detect framework pattern
93
+ detect_pattern() {
94
+ if has_config "vite.config.*"; then
95
+ echo "vite"
96
+ elif has_config "next.config.*"; then
97
+ echo "next"
98
+ elif has_config "nuxt.config.*"; then
99
+ echo "nuxt"
100
+ else
101
+ echo ""
102
+ fi
103
+ }
104
+
105
+ # Use auto-detection if not provided
106
+ if [ -z "$PORT" ]; then
107
+ PORT=$(detect_port)
108
+ fi
109
+
110
+ if [ -z "$PATTERN" ]; then
111
+ PATTERN=$(detect_pattern)
112
+ fi
113
+
114
+ PROJECT_DIR="$(pwd)"
115
+ PROJECT_NAME="$(basename "$PROJECT_DIR")"
116
+
117
+ echo "Cleanup zombies for: $PROJECT_NAME"
118
+ echo " Directory: $PROJECT_DIR"
119
+ [ -n "$PORT" ] && echo " Port: $PORT (+ test port $((PORT + 1000)))"
120
+ [ -n "$PATTERN" ] && echo " Pattern: $PATTERN"
121
+ $DRY_RUN && echo -e " ${YELLOW}DRY RUN - no processes will be killed${NC}"
122
+ echo ""
123
+
124
+ # Track what we find/kill
125
+ FOUND_COUNT=0
126
+ KILLED_COUNT=0
127
+
128
+ # Function to find and optionally kill processes by port
129
+ cleanup_port() {
130
+ local port=$1
131
+ local pids
132
+ pids=$(lsof -ti:"$port" 2>/dev/null || true)
133
+
134
+ if [ -n "$pids" ]; then
135
+ local count
136
+ count=$(echo "$pids" | wc -l | tr -d ' ')
137
+ FOUND_COUNT=$((FOUND_COUNT + count))
138
+
139
+ echo "Port $port: $count process(es)"
140
+ for pid in $pids; do
141
+ local cmd
142
+ cmd=$(ps -p "$pid" -o command= 2>/dev/null | head -c 80 || echo "unknown")
143
+ echo " PID $pid: $cmd"
144
+ done
145
+
146
+ if [ "$DRY_RUN" = false ]; then
147
+ echo "$pids" | xargs kill -9 2>/dev/null || true
148
+ KILLED_COUNT=$((KILLED_COUNT + count))
149
+ fi
150
+ fi
151
+ }
152
+
153
+ # Function to find and optionally kill processes by pattern (scoped to project)
154
+ cleanup_pattern() {
155
+ local pattern=$1
156
+ local pids
157
+ # Match pattern AND project directory for safety
158
+ pids=$(pgrep -f "$pattern.*$PROJECT_DIR" 2>/dev/null || pgrep -f "$PROJECT_DIR.*$pattern" 2>/dev/null || true)
159
+
160
+ if [ -n "$pids" ]; then
161
+ local count
162
+ count=$(echo "$pids" | wc -l | tr -d ' ')
163
+ FOUND_COUNT=$((FOUND_COUNT + count))
164
+
165
+ echo "Pattern '$pattern' (project-scoped): $count process(es)"
166
+ for pid in $pids; do
167
+ local cmd
168
+ cmd=$(ps -p "$pid" -o command= 2>/dev/null | head -c 80 || echo "unknown")
169
+ echo " PID $pid: $cmd"
170
+ done
171
+
172
+ if [ "$DRY_RUN" = false ]; then
173
+ echo "$pids" | xargs kill -9 2>/dev/null || true
174
+ KILLED_COUNT=$((KILLED_COUNT + count))
175
+ fi
176
+ fi
177
+ }
178
+
179
+ # 1. Kill by port (dev server)
180
+ if [ -n "$PORT" ]; then
181
+ cleanup_port "$PORT"
182
+
183
+ # Also kill test port (dev port + 1000, common convention)
184
+ TEST_PORT=$((PORT + 1000))
185
+ cleanup_port "$TEST_PORT"
186
+ fi
187
+
188
+ # 2. Kill Playwright/test processes scoped to this project
189
+ cleanup_pattern "playwright"
190
+ cleanup_pattern "chromium"
191
+ cleanup_pattern "electron"
192
+
193
+ # 3. Kill framework-specific processes scoped to this project
194
+ if [ -n "$PATTERN" ]; then
195
+ cleanup_pattern "$PATTERN"
196
+ fi
197
+
198
+ # 4. Wait for cleanup
199
+ if [ "$DRY_RUN" = false ] && [ "$KILLED_COUNT" -gt 0 ]; then
200
+ sleep 2
201
+ fi
202
+
203
+ # 5. Summary
204
+ echo ""
205
+ if [ "$FOUND_COUNT" -eq 0 ]; then
206
+ echo -e "${GREEN}No zombie processes found - already clean!${NC}"
207
+ elif [ "$DRY_RUN" = true ]; then
208
+ echo -e "${YELLOW}Found $FOUND_COUNT process(es) that would be killed${NC}"
209
+ echo " Run without --dry-run to kill them"
210
+ else
211
+ echo -e "${GREEN}Killed $KILLED_COUNT process(es)${NC}"
212
+
213
+ # Verify port is free
214
+ if [ -n "$PORT" ]; then
215
+ if lsof -i:"$PORT" >/dev/null 2>&1; then
216
+ echo -e "${YELLOW}Warning: Port $PORT still in use${NC}"
217
+ lsof -i:"$PORT"
218
+ else
219
+ echo " Port $PORT is now free"
220
+ fi
221
+ fi
222
+ fi
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/commands/check.ts"],"sourcesContent":["/**\n * Check command - Verify project health and configuration\n *\n * Uses reconcile() with dryRun to detect missing files and configuration issues.\n */\n\nimport { join } from 'node:path';\nimport { VERSION } from '../version.js';\nimport { exists, readFileSafe } from '../utils/fs.js';\nimport { info, success, warn, header, keyValue } from '../utils/output.js';\nimport { isNewerVersion } from '../utils/version.js';\nimport { createProjectContext } from '../utils/context.js';\nimport { reconcile } from '../reconcile.js';\nimport { SAFEWORD_SCHEMA } from '../schema.js';\n\nexport interface CheckOptions {\n offline?: boolean;\n}\n\n/** Check for missing files from write actions */\nfunction findMissingFiles(cwd: string, actions: { type: string; path: string }[]): string[] {\n const issues: string[] = [];\n for (const action of actions) {\n if (action.type === 'write' && !exists(join(cwd, action.path))) {\n issues.push(`Missing: ${action.path}`);\n }\n }\n return issues;\n}\n\n/** Check for missing text patch markers */\nfunction findMissingPatches(\n cwd: string,\n actions: { type: string; path: string; definition?: { marker: string } }[],\n): string[] {\n const issues: string[] = [];\n for (const action of actions) {\n if (action.type !== 'text-patch') continue;\n\n const fullPath = join(cwd, action.path);\n if (!exists(fullPath)) {\n issues.push(`${action.path} file missing`);\n } else {\n const content = readFileSafe(fullPath) ?? '';\n if (action.definition && !content.includes(action.definition.marker)) {\n issues.push(`${action.path} missing safeword link`);\n }\n }\n }\n return issues;\n}\n\ninterface HealthStatus {\n configured: boolean;\n projectVersion: string | null;\n cliVersion: string;\n updateAvailable: boolean;\n latestVersion: string | null;\n issues: string[];\n missingPackages: string[];\n}\n\n/**\n * Check for latest version from npm (with timeout)\n */\nasync function checkLatestVersion(timeout = 3000): Promise<string | null> {\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n const response = await fetch('https://registry.npmjs.org/safeword/latest', {\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) return null;\n\n const data = (await response.json()) as { version?: string };\n return data.version ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Check project configuration health using reconcile dryRun\n */\nasync function checkHealth(cwd: string): Promise<HealthStatus> {\n const safewordDir = join(cwd, '.safeword');\n\n // Check if configured\n if (!exists(safewordDir)) {\n return {\n configured: false,\n projectVersion: null,\n cliVersion: VERSION,\n updateAvailable: false,\n latestVersion: null,\n issues: [],\n missingPackages: [],\n };\n }\n\n // Read project version\n const versionPath = join(safewordDir, 'version');\n const projectVersion = readFileSafe(versionPath)?.trim() ?? null;\n\n // Use reconcile with dryRun to detect issues\n const ctx = createProjectContext(cwd);\n const result = await reconcile(SAFEWORD_SCHEMA, 'upgrade', ctx, { dryRun: true });\n\n // Collect issues from write actions and text patches\n const issues: string[] = [\n ...findMissingFiles(cwd, result.actions),\n ...findMissingPatches(cwd, result.actions),\n ];\n\n // Check for missing .claude/settings.json\n if (!exists(join(cwd, '.claude', 'settings.json'))) {\n issues.push('Missing: .claude/settings.json');\n }\n\n return {\n configured: true,\n projectVersion,\n cliVersion: VERSION,\n updateAvailable: false,\n latestVersion: null,\n issues,\n missingPackages: result.packagesToInstall,\n };\n}\n\n/** Check for CLI updates and report status */\nasync function reportUpdateStatus(health: HealthStatus): Promise<void> {\n info('\\nChecking for updates...');\n const latestVersion = await checkLatestVersion();\n\n if (!latestVersion) {\n warn(\"Couldn't check for updates (offline?)\");\n return;\n }\n\n health.latestVersion = latestVersion;\n health.updateAvailable = isNewerVersion(health.cliVersion, latestVersion);\n\n if (health.updateAvailable) {\n warn(`Update available: v${latestVersion}`);\n info('Run `npm install -g safeword` to upgrade');\n } else {\n success('CLI is up to date');\n }\n}\n\n/** Compare project version vs CLI version and report */\nfunction reportVersionMismatch(health: HealthStatus): void {\n if (!health.projectVersion) return;\n\n if (isNewerVersion(health.cliVersion, health.projectVersion)) {\n warn(`Project config (v${health.projectVersion}) is newer than CLI (v${health.cliVersion})`);\n info('Consider upgrading the CLI');\n } else if (isNewerVersion(health.projectVersion, health.cliVersion)) {\n info(`\\nUpgrade available for project config`);\n info(\n `Run \\`safeword upgrade\\` to update from v${health.projectVersion} to v${health.cliVersion}`,\n );\n }\n}\n\n/** Report issues or success */\nfunction reportHealthSummary(health: HealthStatus): void {\n if (health.issues.length > 0) {\n header('Issues Found');\n for (const issue of health.issues) {\n warn(issue);\n }\n info('\\nRun `safeword upgrade` to repair configuration');\n return;\n }\n\n if (health.missingPackages.length > 0) {\n header('Missing Packages');\n info(`${health.missingPackages.length} linting packages not installed`);\n info('Run `safeword sync` to install missing packages');\n return;\n }\n\n success('\\nConfiguration is healthy');\n}\n\nexport async function check(options: CheckOptions): Promise<void> {\n const cwd = process.cwd();\n\n header('Safeword Health Check');\n\n const health = await checkHealth(cwd);\n\n // Not configured\n if (!health.configured) {\n info('Not configured. Run `safeword setup` to initialize.');\n return;\n }\n\n // Show versions\n keyValue('Safeword CLI', `v${health.cliVersion}`);\n keyValue('Project config', health.projectVersion ? `v${health.projectVersion}` : 'unknown');\n\n // Check for updates (unless offline)\n if (options.offline) {\n info('\\nSkipped update check (offline mode)');\n } else {\n await reportUpdateStatus(health);\n }\n\n reportVersionMismatch(health);\n reportHealthSummary(health);\n}\n"],"mappings":"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","names":[]}