@manukyalo/scopelock 2.3.0 → 3.0.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/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # scopelock
1
+ # scopelock v3.0
2
2
 
3
3
  **Anti-hallucination scope locking for AI coding agents.**
4
4
 
@@ -12,86 +12,101 @@ npm install -g @manukyalo/scopelock
12
12
 
13
13
  ## Features
14
14
 
15
- - **File-level locks**: Run `scopelock lock src/auth.ts` to make the file read-only for agents.
16
- - **Function-level locks**: Don't want to lock the whole file? Lock specific AST functions so agents can only edit adjacent code.
17
- - **Dependency Lockdown**: Zero-trust dependency management. Automatically locks `package.json` and other dependency manifests on init to prevent silent dependency drift.
15
+ - **File & Function Locks**: Run `scopelock lock src/auth.ts` or `scopelock lock src/auth.ts:validateToken` to make code read-only for agents.
16
+ - **Production Path Locks (Seal)**: Use `scopelock seal` for critical paths like billing. Agents cannot override this; it requires explicit human sign-off via `scopelock unseal --human-approved=<ticket>`.
17
+ - **Blast Radius Map**: Prevent scope creep *before* it happens. Run `scopelock impact <file>` to see every file that imports a target file before you touch it.
18
+ - **Dependency Lockdown**: Zero-trust dependency management. Automatically locks `package.json` on init to prevent silent dependency drift.
18
19
  - **Secret Sentinel**: A hard-blocking pre-commit scanner that physically prevents agents from committing AWS keys, Stripe tokens, or `.env` leaks.
19
- - **Zero dependencies**: Written in pure Node.js. Install it anywhere without bloating your `node_modules`.
20
+ - **Test Coverage Gate**: Run `scopelock guard --tests` to block any source code changes that aren't accompanied by tests.
21
+ - **Rollback Snapshots**: Run `scopelock save` before an agent starts working, and `scopelock restore` to obliterate any rogue changes instantly.
20
22
 
21
23
  ---
22
24
 
23
25
  ## Commands
24
26
 
25
27
  ```bash
26
- scopelock init Scan repo and generate .scopelock.json
27
- scopelock lock <file>[:<func>] [reason] Lock a file or a specific function
28
- scopelock unlock <file>[:<func>] <reason> Unlock (reason is mandatory)
29
- scopelock allow-secret <file> <reason> Bypass Secret Sentinel for a specific file
30
- scopelock context [task] Generate AI context block for a task
31
- scopelock check Check git diff for scope violations and secret leaks
32
- scopelock status Show manifest summary
28
+ scopelock init Scan repo and generate .scopelock.json
29
+ scopelock lock <file>[:<func>] [reason] Lock a file or a specific function
30
+ scopelock unlock <file>[:<func>] <reason> Unlock (reason is mandatory)
31
+ scopelock seal <file> <reason> Permanent production-path lock (no override)
32
+ scopelock unseal <file> --human-approved=<ticket> <reason> Release a seal
33
+ scopelock impact <file> Show all files that import this file
34
+ scopelock trust <file> <reason> Bypass Secret Sentinel for a specific file
35
+ scopelock save Auto-snapshot repo state before an agent session
36
+ scopelock restore Rollback to the last snapshot
37
+ scopelock context [task] Generate AI context block for a task
38
+ scopelock guard [--tests] Check git diff for violations and secret leaks
39
+ scopelock status Show manifest summary
33
40
  ```
34
41
 
35
- ### `scopelock init`
36
- Scan the repo and generate `.scopelock.json`. Automatically ignores `node_modules`, `.git`, `.next`, `dist`, `build`, `out`, `coverage`, and other build artifacts.
37
-
38
- ### `scopelock lock <file>[:<function>] [reason]`
39
- Lock a whole file or a specific named function.
42
+ ### `scopelock lock` & `unlock`
43
+ Lock a whole file or a specific named function. Unlock requires a reason that gets logged to history.
40
44
 
41
45
  ```bash
42
- # Lock a whole file
43
46
  scopelock lock src/lib/supabase.ts "production client — stable"
44
-
45
- # Lock a specific function (validates it exists before locking)
46
47
  scopelock lock src/auth/token.ts:validateToken "tested — do not touch"
48
+ scopelock unlock src/auth/token.ts:validateToken "fixing JWT expiry edge case"
47
49
  ```
48
50
 
49
- ### `scopelock unlock <file>[:<function>] <reason>`
50
- Unlock a file or function. Reason is mandatory and logged.
51
+ ### `scopelock seal` & `unseal`
52
+ For files that should *never* be touched without human oversight (e.g., `/billing`, `/migrations`). Seals cannot be removed by `unlock`.
51
53
 
52
54
  ```bash
53
- scopelock unlock src/auth/token.ts:validateToken "fixing JWT expiry edge case"
55
+ scopelock seal src/billing/stripe.ts "core billing logic"
56
+ scopelock unseal src/billing/stripe.ts --human-approved=PR-123 "updating webhook"
54
57
  ```
55
58
 
56
- ### `scopelock allow-secret <file> <reason>`
57
- Bypass the Secret Sentinel hard-block for a specific file (e.g., when intentionally committing a mock test key).
59
+ ### `scopelock impact`
60
+ Before making a change, see the blast radius. Outputs a list of all files in the repository that import the target file.
58
61
 
59
62
  ```bash
60
- scopelock allow-secret test/run.js "this is a mock stripe key for testing"
63
+ scopelock impact src/utils/auth.ts
61
64
  ```
62
65
 
63
- ### `scopelock check`
64
- Two-tier scope violation check against `git diff HEAD`. Exits non-zero on violations or secret leaks. Wire this up as a `pre-commit` hook.
66
+ ### `scopelock guard`
67
+ Two-tier scope violation check against `git diff HEAD`. Exits non-zero on violations or secret leaks. Wire this up as a `pre-commit` hook. Add `--tests` to strictly enforce test coverage for any changed logic.
65
68
 
66
69
  ```bash
67
- scopelock check
70
+ scopelock guard
71
+ scopelock guard --tests
68
72
  ```
69
73
 
70
- ### `scopelock context [task]`
71
- Output a token-efficient AI context block with all locks clearly flagged.
74
+ ### `scopelock save` & `restore`
75
+ Never fear an agent hallucination destroying your workspace again. `save` stores a snapshot in git stash that survives hard resets. `restore` obliterates the working directory and cleanly reverts to the snapshot.
72
76
 
73
77
  ```bash
74
- scopelock context "Update the login page"
78
+ scopelock save
79
+ scopelock restore
75
80
  ```
81
+
82
+ ### `scopelock trust`
83
+ Bypass the Secret Sentinel hard-block for a specific file (e.g., when intentionally committing a mock test key).
84
+
85
+ ```bash
86
+ scopelock trust test/run.js "this is a mock stripe key for testing"
76
87
  ```
77
- [SCOPE CONTEXT]
78
- Task: Update the login page
79
88
 
80
- Status:
81
- 🔒 locked — 1 file(s)
82
- ✏️ active — 0 file(s)
83
- ⬜ unscoped — 14 file(s)
89
+ ### `scopelock context`
90
+ Output a token-efficient AI context block with all locks clearly flagged for the agent's system prompt.
84
91
 
85
- Locked files:
86
- src/lib/supabase.ts
92
+ ```bash
93
+ scopelock context "Update the login page"
87
94
  ```
88
95
 
89
- ## Agent Skill
96
+ ## Agent Skills (Godmode)
97
+
98
+ `scopelock` ships with 7 native AI Agent Skills located in the `skills/` folder.
99
+ If you use an agent framework (like Antigravity or Cline) that supports Markdown skills, point it to these folders to automatically teach the agent how to use `scopelock` safely.
90
100
 
91
- `scopelock` includes a native Agent Skill.
92
- If you use an agent framework (like Antigravity or Cline) that supports Markdown skills, point it to `skills/scope-enforcement/SKILL.md` to automatically teach the agent how to use `scopelock` safely.
101
+ The skills map directly to features:
102
+ - `scope-enforcement`
103
+ - `dependency-lockdown`
104
+ - `secret-sentinel`
105
+ - `test-coverage-gate`
106
+ - `rollback-snapshot`
107
+ - `blast-radius`
108
+ - `production-path-lock`
93
109
 
94
110
  ## Data Model
95
111
  All state is stored in `.scopelock.json` at the root of your repo.
96
-
97
112
  The manifest is project state, not a personal config. Commit it so your whole team — and all their AI agents — share the same scope boundaries.
package/bin/scopelock.js CHANGED
@@ -55,60 +55,60 @@ switch (command) {
55
55
  break;
56
56
  }
57
57
 
58
- case 'check':
59
- git.check(args);
58
+ case 'guard':
59
+ git.guard(args);
60
60
  break;
61
61
 
62
62
  case 'status':
63
63
  manifest.status();
64
64
  break;
65
65
 
66
- case 'blast-radius': {
66
+ case 'impact': {
67
67
  const target = args[0];
68
68
  if (!target) {
69
- console.error('Usage: scopelock blast-radius <file>');
69
+ console.error('Usage: scopelock impact <file>');
70
70
  process.exit(1);
71
71
  }
72
72
  blast.printBlastRadius(target);
73
73
  break;
74
74
  }
75
75
 
76
- case 'superlock': {
76
+ case 'seal': {
77
77
  const target = args[0];
78
- const reason = args.slice(1).join(' ') || 'production path — superlock applied';
78
+ const reason = args.slice(1).join(' ') || 'production path — seal applied';
79
79
  if (!target) {
80
- console.error('Usage: scopelock superlock <file> <reason>');
80
+ console.error('Usage: scopelock seal <file> <reason>');
81
81
  process.exit(1);
82
82
  }
83
- manifest.superlock(target, reason);
83
+ manifest.seal(target, reason);
84
84
  break;
85
85
  }
86
86
 
87
- case 'sudo-unlock': {
87
+ case 'unseal': {
88
88
  const target = args[0];
89
89
  const ticketArg = args.find(a => a.startsWith('--human-approved='));
90
90
  const ticket = ticketArg ? ticketArg.replace('--human-approved=', '') : null;
91
91
  const reason = args.filter(a => !a.startsWith('--')).slice(1).join(' ');
92
92
  if (!target || !ticket || !reason) {
93
- console.error('Usage: scopelock sudo-unlock <file> --human-approved=<ticket> <reason>');
93
+ console.error('Usage: scopelock unseal <file> --human-approved=<ticket> <reason>');
94
94
  process.exit(1);
95
95
  }
96
- manifest.sudoUnlock(target, ticket, reason);
96
+ manifest.unseal(target, ticket, reason);
97
97
  break;
98
98
  }
99
99
 
100
- case 'allow-secret': {
100
+ case 'trust': {
101
101
  const target = args[0];
102
102
  const reason = args.slice(1).join(' ');
103
103
  if (!target || !reason) {
104
- console.error('Usage: scopelock allow-secret <file> <reason>');
104
+ console.error('Usage: scopelock trust <file> <reason>');
105
105
  process.exit(1);
106
106
  }
107
- manifest.allowSecret(target, reason);
107
+ manifest.trust(target, reason);
108
108
  break;
109
109
  }
110
110
 
111
- case 'snapshot': {
111
+ case 'save': {
112
112
  try {
113
113
  const ts = new Date().toISOString().replace(/[:.]/g, '-');
114
114
  const manifestObj = manifest.getManifest();
@@ -129,7 +129,7 @@ switch (command) {
129
129
  }
130
130
 
131
131
  manifest.saveManifest(manifestObj);
132
- console.log(`✅ Snapshot created. Run 'scopelock revert' to obliterate agent changes and restore this state.`);
132
+ console.log(`✅ Snapshot created. Run 'scopelock restore' to obliterate agent changes and restore this state.`);
133
133
  } catch (e) {
134
134
  console.error('Failed to create snapshot.', e.stderr || e.message);
135
135
  process.exit(1);
@@ -137,11 +137,11 @@ switch (command) {
137
137
  break;
138
138
  }
139
139
 
140
- case 'revert': {
140
+ case 'restore': {
141
141
  try {
142
142
  const manifestObj = manifest.getManifest();
143
143
  if (!manifestObj.lastSnapshot) {
144
- console.error('❌ No snapshot found. Run `scopelock snapshot` first.');
144
+ console.error('❌ No snapshot found. Run `scopelock save` first.');
145
145
  process.exit(1);
146
146
  }
147
147
  console.log(`Obliterating agent mess...`);
@@ -174,14 +174,14 @@ Usage:
174
174
  scopelock init Scan repo and generate .scopelock.json
175
175
  scopelock lock <file>[:<func>] [reason] Lock a file or a specific function
176
176
  scopelock unlock <file>[:<func>] <reason> Unlock (reason is mandatory)
177
- scopelock superlock <file> <reason> Permanent production-path lock (no override)
178
- scopelock sudo-unlock <file> --human-approved=<ticket> <reason> Release a superlock
179
- scopelock blast-radius <file> Show all files that import this file
180
- scopelock allow-secret <file> <reason> Bypass Secret Sentinel for a specific file
181
- scopelock snapshot Auto-snapshot repo state before an agent session
182
- scopelock revert Rollback to the last snapshot
177
+ scopelock seal <file> <reason> Permanent production-path lock (no override)
178
+ scopelock unseal <file> --human-approved=<ticket> <reason> Release a seal
179
+ scopelock impact <file> Show all files that import this file
180
+ scopelock trust <file> <reason> Bypass Secret Sentinel for a specific file
181
+ scopelock save Auto-snapshot repo state before an agent session
182
+ scopelock restore Rollback to the last snapshot
183
183
  scopelock context [task] Generate AI context block for a task
184
- scopelock check [--require-tests] Check git diff for violations and secret leaks
184
+ scopelock guard [--tests] Check git diff for violations and secret leaks
185
185
  scopelock status Show manifest summary
186
186
 
187
187
 
@@ -190,7 +190,7 @@ Examples:
190
190
  scopelock lock src/auth.ts:validateToken "stable — do not touch"
191
191
  scopelock unlock src/auth.ts:validateToken "need to fix JWT expiry bug"
192
192
  scopelock context "Fix the broken checkout flow"
193
- scopelock check
193
+ scopelock guard
194
194
  `);
195
195
  process.exit(1);
196
196
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@manukyalo/scopelock",
3
- "version": "2.3.0",
3
+ "version": "3.0.0",
4
4
  "description": "Anti-hallucination scope locking for AI coding agents.",
5
5
  "main": "bin/scopelock.js",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: blast-radius-map
3
- description: "Godmode Skill: Before modifying any file, run 'scopelock blast-radius <file>' to see every other file that imports it. Prevents scope creep by making the full impact of a change visible BEFORE the agent writes a single line."
3
+ description: "Godmode Skill: Before modifying any file, run 'scopelock impact <file>' to see every other file that imports it. Prevents scope creep by making the full impact of a change visible BEFORE the agent writes a single line."
4
4
  ---
5
5
 
6
6
  ## Overview
@@ -15,7 +15,7 @@ This skill instructs the agent to **check before it touches**, not after.
15
15
 
16
16
  1. Run the blast radius check first:
17
17
  ```bash
18
- scopelock blast-radius src/utils/auth.ts
18
+ scopelock impact src/utils/auth.ts
19
19
  ```
20
20
 
21
21
  2. Read the output:
@@ -49,7 +49,7 @@ This skill instructs the agent to **check before it touches**, not after.
49
49
 
50
50
  5. Make your change to the target file only.
51
51
 
52
- 6. Run `scopelock check` to verify no locked files were touched.
52
+ 6. Run `scopelock guard` to verify no locked files were touched.
53
53
 
54
54
  ## Why this exists
55
55
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: dependency-lockdown
3
- description: "Godmode Skill: Prevents AI agents from silently adding or upgrading npm/pip/cargo packages. Dependency manifest files (package.json, requirements.txt, etc.) are automatically locked by scopelock on init. Any diff touching these files will fail scopelock check until explicitly unlocked with a reason."
3
+ description: "Godmode Skill: Prevents AI agents from silently adding or upgrading npm/pip/cargo packages. Dependency manifest files (package.json, requirements.txt, etc.) are automatically locked by scopelock on init. Any diff touching these files will fail scopelock guard until explicitly unlocked with a reason."
4
4
  ---
5
5
 
6
6
  ## Overview
@@ -22,7 +22,7 @@ On `scopelock init`, the following files are **automatically locked** without an
22
22
  scopelock unlock package.json "adding zod for runtime validation of API responses"
23
23
  ```
24
24
  2. Make your change (add the dependency to the manifest).
25
- 3. Run `scopelock check` to confirm the change is authorized.
25
+ 3. Run `scopelock guard` to confirm the change is authorized.
26
26
  4. Re-lock the manifest immediately after:
27
27
  ```bash
28
28
  scopelock lock package.json "dependencies updated and reviewed"
@@ -1,56 +1,56 @@
1
1
  ---
2
2
  name: production-path-lock
3
- description: "Godmode Skill: A permanent, override-resistant lock for critical production paths like /auth, /billing, /migrations. Regular 'scopelock unlock' cannot bypass it. Requires a second human's explicit sign-off via 'scopelock sudo-unlock' with a ticket."
3
+ description: "Godmode Skill: A permanent, override-resistant lock for critical production paths like /auth, /billing, /migrations. Regular 'scopelock unlock' cannot bypass it. Requires a second human's explicit sign-off via 'scopelock unseal' with a ticket."
4
4
  ---
5
5
 
6
6
  ## Overview
7
7
 
8
- Some files must never be touched by an AI agent without explicit human sign-off. Your `/auth` logic, billing handlers, and database migration files are not negotiable. A `superlock` makes this a physical constraint, not a policy.
8
+ Some files must never be touched by an AI agent without explicit human sign-off. Your `/auth` logic, billing handlers, and database migration files are not negotiable. A `seal` makes this a physical constraint, not a policy.
9
9
 
10
- A superlocked file cannot be overridden by `scopelock unlock`. The agent must stop, escalate to a human, and provide a traceable ticket before any modification is allowed.
10
+ A sealed file cannot be overridden by `scopelock unlock`. The agent must stop, escalate to a human, and provide a traceable ticket before any modification is allowed.
11
11
 
12
- ## Recommended Files to Superlock on Every Project
12
+ ## Recommended Files to Seal on Every Project
13
13
 
14
14
  ```bash
15
15
  # Authentication
16
- scopelock superlock src/auth/token.ts "core auth — requires senior review"
17
- scopelock superlock src/middleware/auth.ts "core auth — requires senior review"
16
+ scopelock seal src/auth/token.ts "core auth — requires senior review"
17
+ scopelock seal src/middleware/auth.ts "core auth — requires senior review"
18
18
 
19
19
  # Billing & Payments
20
- scopelock superlock src/billing/stripe.ts "billing — requires finance team approval"
21
- scopelock superlock src/billing/webhooks.ts "billing — requires finance team approval"
20
+ scopelock seal src/billing/stripe.ts "billing — requires finance team approval"
21
+ scopelock seal src/billing/webhooks.ts "billing — requires finance team approval"
22
22
 
23
23
  # Database Migrations
24
- scopelock superlock migrations/ "schema changes — requires DBA review"
24
+ scopelock seal migrations/ "schema changes — requires DBA review"
25
25
 
26
26
  # Security-critical config
27
- scopelock superlock .env.production "production secrets — never touch"
27
+ scopelock seal .env.production "production secrets — never touch"
28
28
  ```
29
29
 
30
30
  ## Agent Protocol
31
31
 
32
- ### When you encounter a superlocked file:
32
+ ### When you encounter a sealed file:
33
33
  ```
34
- ❌ 'src/auth/token.ts' is SUPERLOCKED and cannot be unlocked with 'scopelock unlock'.
34
+ ❌ 'src/auth/token.ts' is SEALED and cannot be unlocked with 'scopelock unlock'.
35
35
  This path is a protected production route.
36
- Use: scopelock sudo-unlock src/auth/token.ts --human-approved=<ticket> <reason>
36
+ Use: scopelock unseal src/auth/token.ts --human-approved=<ticket> <reason>
37
37
  ```
38
38
 
39
39
  1. **STOP.** Do not attempt to work around the lock.
40
40
  2. **Escalate.** Tell the human you cannot proceed without their explicit approval.
41
41
  3. **The human must run:**
42
42
  ```bash
43
- scopelock sudo-unlock src/auth/token.ts --human-approved=JIRA-123 "fixing JWT expiry bug approved in PR-456"
43
+ scopelock unseal src/auth/token.ts --human-approved=JIRA-123 "fixing JWT expiry bug approved in PR-456"
44
44
  ```
45
- 4. Only after you see the `SUDO-UNLOCKED` confirmation may you proceed.
46
- 5. Re-superlock the file immediately after your change is committed:
45
+ 4. Only after you see the `UNSEALED` confirmation may you proceed.
46
+ 5. Re-seal the file immediately after your change is committed:
47
47
  ```bash
48
- scopelock superlock src/auth/token.ts "re-locked after JWT fix — JIRA-123"
48
+ scopelock seal src/auth/token.ts "re-locked after JWT fix — JIRA-123"
49
49
  ```
50
50
 
51
51
  ## The Audit Trail
52
52
 
53
- Every `sudo-unlock` is permanently logged in `.scopelock.json` with:
53
+ Every `unseal` is permanently logged in `.scopelock.json` with:
54
54
  - The timestamp
55
55
  - The human-approved ticket number
56
56
  - The full reason string
@@ -59,4 +59,4 @@ This creates a traceable, auditable record of every time a production path was m
59
59
 
60
60
  ## Why this exists
61
61
 
62
- `locked` is a request. `superlocked` is a wall. Some files need a wall.
62
+ `locked` is a request. `sealed` is a wall. Some files need a wall.
@@ -5,19 +5,19 @@ description: "Godmode Skill: Creates a one-command safety net before every agent
5
5
 
6
6
  ## Overview
7
7
 
8
- Before you let an agent loose on a codebase, you need a guaranteed escape hatch. `scopelock snapshot` creates that escape hatch using git's native stash mechanism. It is instant, requires no external tools, and can restore the repo to its exact pre-session state in under one second.
8
+ Before you let an agent loose on a codebase, you need a guaranteed escape hatch. `scopelock save` creates that escape hatch using git's native stash mechanism. It is instant, requires no external tools, and can restore the repo to its exact pre-session state in under one second.
9
9
 
10
10
  ## How Storage Works
11
11
 
12
12
  - The snapshot is stored in **git's local stash** (`.git/refs/stash`) — it never leaves your machine and is never pushed to GitHub.
13
- - The `.scopelock.json` manifest stores a pointer (`lastSnapshot: "dirty" | "clean"`) so `scopelock revert` knows what to restore.
13
+ - The `.scopelock.json` manifest stores a pointer (`lastSnapshot: "dirty" | "clean"`) so `scopelock restore` knows what to restore.
14
14
  - If you clone the repo on a new machine, the snapshot is gone — this is correct. Snapshots are session-scoped, not repository-scoped.
15
15
 
16
16
  ## Agent Protocol
17
17
 
18
18
  ### At the start of EVERY agent session:
19
19
  ```bash
20
- scopelock snapshot
20
+ scopelock save
21
21
  ```
22
22
  This is the first command you run, before writing a single line of code.
23
23
 
@@ -26,7 +26,7 @@ Simply commit your work normally. The stash will remain in git until git's garba
26
26
 
27
27
  ### If the agent goes rogue:
28
28
  ```bash
29
- scopelock revert
29
+ scopelock restore
30
30
  ```
31
31
  This command:
32
32
  1. Runs `git reset --hard HEAD` — obliterates all tracked file changes.
@@ -10,7 +10,7 @@ AI coding agents default to the shortest path to a passing result — which freq
10
10
  This skill uses `scopelock` — a zero-dependency Node.js CLI — to enforce two tiers of scope protection:
11
11
 
12
12
  - **File-level:** An entire file is locked. No agent may touch it.
13
- - **Function-level:** A specific function within a file is locked. The file may be edited, but that function's body is off-limits. `scopelock check` detects if any changed line falls inside the locked function's boundaries.
13
+ - **Function-level:** A specific function within a file is locked. The file may be edited, but that function's body is off-limits. `scopelock guard` detects if any changed line falls inside the locked function's boundaries.
14
14
 
15
15
  The skill operates at three checkpoints: **Session Start**, **Pre-Commit**, and **Post-Task Review**.
16
16
 
@@ -64,7 +64,7 @@ scopelock lock src/auth/token.ts:validateToken "tested, do not touch"
64
64
 
65
65
  **Step 4: Run the scope check.**
66
66
  ```bash
67
- scopelock check
67
+ scopelock guard
68
68
  ```
69
69
 
70
70
  Two-tier enforcement:
@@ -87,7 +87,7 @@ git restore <file>
87
87
  *Genuinely required change:*
88
88
  ```bash
89
89
  scopelock unlock src/auth/token.ts:validateToken "fixing JWT expiry edge case"
90
- scopelock check # must pass before committing
90
+ scopelock guard # must pass before committing
91
91
  ```
92
92
 
93
93
  ---
@@ -108,7 +108,7 @@ scopelock lock src/auth/token.ts:validateToken "fixed and re-locked"
108
108
 
109
109
  ```bash
110
110
  echo '#!/bin/sh
111
- scopelock check' > .git/hooks/pre-commit
111
+ scopelock guard' > .git/hooks/pre-commit
112
112
  chmod +x .git/hooks/pre-commit
113
113
  ```
114
114
 
@@ -123,14 +123,14 @@ chmod +x .git/hooks/pre-commit
123
123
  | "The agent said it needed to modify that function." | Then re-evaluate scope, not the lock. If you can't write a specific reason, the modification isn't justified. |
124
124
  | "Function-level locking is overkill." | File-level locking doesn't stop an agent from rewriting a critical function inside a file marked `active`. |
125
125
  | "The parser didn't detect my function." | Use file-level locking instead. Flag the miss — it's a parser bug, not a reason to skip protection. |
126
- | "Running `scopelock check` slows my commit flow." | Under 300ms. The alternative is debugging a hallucinated refactor for hours. |
126
+ | "Running `scopelock guard` slows my commit flow." | Under 300ms. The alternative is debugging a hallucinated refactor for hours. |
127
127
  | ".scopelock.json shouldn't be committed." | Commit it. It's shared project state — your whole team and their agents benefit. |
128
128
 
129
129
  ---
130
130
 
131
131
  ## Red Flags
132
132
 
133
- - `scopelock check` reports violations in more than 2 files — significant scope drift.
133
+ - `scopelock guard` reports violations in more than 2 files — significant scope drift.
134
134
  - An unlock entry has a vague reason string (e.g., "fix", "update") — bypassed without thinking.
135
135
  - The agent proposes unlocking multiple files at once with one generic reason — batch rationalization.
136
136
  - A locked function is reported as `function-missing` after a diff — it was deleted or renamed.
@@ -144,7 +144,7 @@ A session is compliant when ALL of the following are true:
144
144
 
145
145
  - [ ] `.scopelock.json` exists and `scopelock status` shows the expected locks.
146
146
  - [ ] `scopelock context "<task>"` output was injected into the agent before any code was written.
147
- - [ ] `scopelock check` exits `0` before every commit in this session.
147
+ - [ ] `scopelock guard` exits `0` before every commit in this session.
148
148
  - [ ] Every `unlock` entry has a specific, task-justified reason string.
149
149
  - [ ] No diff contains modifications to locked files or lines inside locked function bodies that were not explicitly unlocked.
150
150
  - [ ] All modified files and functions have been reviewed for re-locking post-task.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: secret-sentinel
3
- description: "Godmode Skill: Physically blocks AI agents from committing API keys, tokens, or .env leaks. scopelock check scans every added line in the git diff for high-entropy secrets before the commit is allowed. This is a hard block — not a warning."
3
+ description: "Godmode Skill: Physically blocks AI agents from committing API keys, tokens, or .env leaks. scopelock guard scans every added line in the git diff for high-entropy secrets before the commit is allowed. This is a hard block — not a warning."
4
4
  ---
5
5
 
6
6
  ## Overview
@@ -20,7 +20,7 @@ The Secret Sentinel scans every newly added line for:
20
20
 
21
21
  ### Before every commit:
22
22
  ```bash
23
- scopelock check
23
+ scopelock guard
24
24
  ```
25
25
  If a secret is detected, you will see:
26
26
  ```
@@ -42,7 +42,7 @@ If a secret is detected, you will see:
42
42
  ### Intentional exception (mock/test keys only):
43
43
  If you are intentionally committing a **mock** key for testing purposes, a human must explicitly authorize it:
44
44
  ```bash
45
- scopelock allow-secret test/fixtures/mock.ts "contains a mock stripe key for unit tests — not a real key"
45
+ scopelock trust test/fixtures/mock.ts "contains a mock stripe key for unit tests — not a real key"
46
46
  ```
47
47
  This bypass is logged permanently in `.scopelock.json` for audit purposes.
48
48
 
@@ -1,19 +1,19 @@
1
1
  ---
2
2
  name: test-coverage-gate
3
- description: "Godmode Skill: Forces the AI agent to write or update test files whenever it modifies application logic. Run 'scopelock check --require-tests' before committing to enforce this rule."
3
+ description: "Godmode Skill: Forces the AI agent to write or update test files whenever it modifies application logic. Run 'scopelock guard --tests' before committing to enforce this rule."
4
4
  ---
5
5
 
6
6
  ## Overview
7
7
 
8
8
  This skill teaches the agent how to navigate the `scopelock` Test Coverage Gate.
9
- When `--require-tests` is active, the CLI will hard-block any commit that modifies a source file (`.js`, `.ts`, `.py`, etc.) if a corresponding test file (`.test.ts`, `.spec.js`, `test/`) is not also modified in the same diff.
9
+ When `--tests` is active, the CLI will hard-block any commit that modifies a source file (`.js`, `.ts`, `.py`, etc.) if a corresponding test file (`.test.ts`, `.spec.js`, `test/`) is not also modified in the same diff.
10
10
 
11
11
  ## Agent Protocol
12
12
 
13
13
  1. **Before writing code**: If you are about to modify application logic, understand that your changes will be rejected unless you also provide test coverage.
14
14
  2. **Write the code**: Implement the requested feature or fix.
15
15
  3. **Write the test**: You *must* update the corresponding test file or create a new one. The file path must contain `.test.`, `.spec.`, or be inside a `test/` or `__tests__/` directory.
16
- 4. **Validation**: Run `scopelock check --require-tests` to mathematically verify your diff will pass the coverage gate.
16
+ 4. **Validation**: Run `scopelock guard --tests` to mathematically verify your diff will pass the coverage gate.
17
17
  5. **Ship**: Only after the test gate is passed are you allowed to commit or mark the task as complete.
18
18
 
19
19
  ## Why this exists
package/src/git.js CHANGED
@@ -1,167 +1,167 @@
1
- 'use strict';
2
-
3
- /**
4
- * src/git.js
5
- *
6
- * Scope violation checker — V2.
7
- *
8
- * Two tiers of enforcement:
9
- * 1. File-level: Any changed file whose manifest status is 'locked' → violation.
10
- * 2. Function-level: Any changed file that has locked functions →
11
- * parse the diff for changed line numbers, re-extract
12
- * function boundaries from the current file, and flag
13
- * any changed line that falls inside a locked function.
14
- *
15
- * Exits 1 if any violation found. Wireable as a pre-commit hook.
16
- */
17
-
18
- const { execSync } = require('child_process');
19
- const { getManifest } = require('./manifest');
20
- const { getChangedLines } = require('./diff');
21
- const { extractFunctions } = require('./parser');
22
- const { detectSecret } = require('./secrets');
23
-
24
- function check(args = []) {
25
- const requireTests = args.includes('--require-tests');
26
- const manifest = getManifest();
27
- let diffOutput;
28
-
29
- try {
30
- diffOutput = execSync('git diff HEAD --name-only', {
31
- encoding: 'utf8',
32
- stdio: ['pipe', 'pipe', 'pipe'],
33
- });
34
- } catch (err) {
35
- console.error('git error — are you inside a git repository with at least one commit?');
36
- console.error(err.message);
37
- process.exit(1);
38
- }
39
-
40
- const changedFiles = diffOutput
41
- .split('\n')
42
- .map(f => f.trim())
43
- .filter(f => f.length > 0);
44
-
45
- if (changedFiles.length === 0) {
46
- console.log('✅ Scope check passed — no changes detected.');
47
- return;
48
- }
49
-
50
- const violations = [];
51
-
52
- // ── Tier -1: Test Coverage Gate ───────────────────────────────────────────
53
- if (requireTests) {
54
- const hasSourceChanges = changedFiles.some(f =>
55
- !f.includes('.test.') && !f.includes('.spec.') && !f.includes('/test/') && !f.includes('/__tests__/') &&
56
- (f.endsWith('.js') || f.endsWith('.ts') || f.endsWith('.py') || f.endsWith('.go') || f.endsWith('.rs'))
57
- );
58
- const hasTestChanges = changedFiles.some(f =>
59
- f.includes('.test.') || f.includes('.spec.') || f.includes('/test/') || f.includes('/__tests__/')
60
- );
61
- if (hasSourceChanges && !hasTestChanges) {
62
- violations.push({
63
- type: 'test-gate',
64
- file: 'N/A',
65
- message: 'TEST GATE VIOLATION: Source logic was modified, but no tests were added or updated. You must write tests to pass `--require-tests`.'
66
- });
67
- }
68
- }
69
-
70
- for (const file of changedFiles) {
71
- const normalizedFile = file.replace(/\\/g, '/');
72
- const entry = manifest.files[normalizedFile];
73
-
74
- const changedLines = getChangedLines(normalizedFile);
75
-
76
- // ── Tier 0: Secret Sentinel ─────────────────────────────────────────────
77
- if (changedLines.size > 0) {
78
- // Check if this file has explicitly allowed secrets
79
- const hasOverride = manifest.allowedSecrets && manifest.allowedSecrets[normalizedFile];
80
- if (!hasOverride) {
81
- for (const [lineNum, content] of changedLines.entries()) {
82
- const secretType = detectSecret(content);
83
- if (secretType) {
84
- violations.push({
85
- type: 'secret',
86
- file: normalizedFile,
87
- message: `SECRET LEAK [${secretType}] detected in '${normalizedFile}' on line ${lineNum}.`,
88
- });
89
- break; // One secret violation per file is enough
90
- }
91
- }
92
- }
93
- }
94
-
95
- // ── Tier 1: File-level lock ─────────────────────────────────────────────
96
- if (entry && (entry.status === 'locked' || entry.status === 'superlocked')) {
97
- const label = entry.status === 'superlocked' ? 'SUPERLOCKED' : 'LOCKED';
98
- violations.push({
99
- type: 'file',
100
- file: normalizedFile,
101
- message: `File '${normalizedFile}' is ${label}.`,
102
- });
103
- continue; // No need to check functions if the whole file is locked
104
- }
105
-
106
- // ── Tier 2: Function-level lock ─────────────────────────────────────────
107
- if (!entry || !entry.functions) continue;
108
-
109
- const lockedFunctions = Object.entries(entry.functions)
110
- .filter(([, fnData]) => fnData.status === 'locked')
111
- .map(([name]) => name);
112
-
113
- if (lockedFunctions.length === 0) continue;
114
-
115
- // Re-extract function boundaries from the current on-disk file
116
- const currentFunctions = extractFunctions(normalizedFile);
117
-
118
- for (const lockedFnName of lockedFunctions) {
119
- const fn = currentFunctions.find(f => f.name === lockedFnName);
120
- if (!fn) {
121
- // Function was deleted or renamed — this itself is a violation
122
- violations.push({
123
- type: 'function-missing',
124
- file: normalizedFile,
125
- fn: lockedFnName,
126
- message: `Locked function '${lockedFnName}' in '${normalizedFile}' was removed or renamed.`,
127
- });
128
- continue;
129
- }
130
-
131
- // Check if any changed line falls within the function's boundaries
132
- for (const line of changedLines.keys()) {
133
- if (line >= fn.startLine && line <= fn.endLine) {
134
- violations.push({
135
- type: 'function',
136
- file: normalizedFile,
137
- fn: lockedFnName,
138
- message:
139
- `Locked function '${lockedFnName}' in '${normalizedFile}' was modified ` +
140
- `(changed line ${line} is inside [${fn.startLine}–${fn.endLine}]).`,
141
- });
142
- break; // One violation per function is enough
143
- }
144
- }
145
- }
146
- }
147
-
148
- // ── Report ────────────────────────────────────────────────────────────────
149
- if (violations.length === 0) {
150
- console.log('✅ Scope check passed — no locked files or functions were modified.');
151
- return;
152
- }
153
-
154
- console.error(`\n❌ Scope violations detected:\n`);
155
- for (const v of violations) {
156
- console.error(` VIOLATION: ${v.message}`);
157
- }
158
- console.error(
159
- `\n${violations.length} violation(s) found.\n` +
160
- ` • Revert unintentional changes with: git restore <file>\n` +
161
- ` • Explicitly unlock with: scopelock unlock <file>[:<function>] "<reason>"`
162
- );
163
-
164
- process.exit(1);
165
- }
166
-
167
- module.exports = { check };
1
+ 'use strict';
2
+
3
+ /**
4
+ * src/git.js
5
+ *
6
+ * Scope violation checker — V2.
7
+ *
8
+ * Two tiers of enforcement:
9
+ * 1. File-level: Any changed file whose manifest status is 'locked' → violation.
10
+ * 2. Function-level: Any changed file that has locked functions →
11
+ * parse the diff for changed line numbers, re-extract
12
+ * function boundaries from the current file, and flag
13
+ * any changed line that falls inside a locked function.
14
+ *
15
+ * Exits 1 if any violation found. Wireable as a pre-commit hook.
16
+ */
17
+
18
+ const { execSync } = require('child_process');
19
+ const { getManifest } = require('./manifest');
20
+ const { getChangedLines } = require('./diff');
21
+ const { extractFunctions } = require('./parser');
22
+ const { detectSecret } = require('./secrets');
23
+
24
+ function guard(args = []) {
25
+ const requireTests = args.includes('--tests');
26
+ const manifest = getManifest();
27
+ let diffOutput;
28
+
29
+ try {
30
+ diffOutput = execSync('git diff HEAD --name-only', {
31
+ encoding: 'utf8',
32
+ stdio: ['pipe', 'pipe', 'pipe'],
33
+ });
34
+ } catch (err) {
35
+ console.error('git error — are you inside a git repository with at least one commit?');
36
+ console.error(err.message);
37
+ process.exit(1);
38
+ }
39
+
40
+ const changedFiles = diffOutput
41
+ .split('\n')
42
+ .map(f => f.trim())
43
+ .filter(f => f.length > 0);
44
+
45
+ if (changedFiles.length === 0) {
46
+ console.log('✅ Scope guard passed — no changes detected.');
47
+ return;
48
+ }
49
+
50
+ const violations = [];
51
+
52
+ // ── Tier -1: Test Coverage Gate ───────────────────────────────────────────
53
+ if (requireTests) {
54
+ const hasSourceChanges = changedFiles.some(f =>
55
+ !f.includes('.test.') && !f.includes('.spec.') && !f.includes('/test/') && !f.includes('/__tests__/') &&
56
+ (f.endsWith('.js') || f.endsWith('.ts') || f.endsWith('.py') || f.endsWith('.go') || f.endsWith('.rs'))
57
+ );
58
+ const hasTestChanges = changedFiles.some(f =>
59
+ f.includes('.test.') || f.includes('.spec.') || f.includes('/test/') || f.includes('/__tests__/')
60
+ );
61
+ if (hasSourceChanges && !hasTestChanges) {
62
+ violations.push({
63
+ type: 'test-gate',
64
+ file: 'N/A',
65
+ message: 'TEST GATE VIOLATION: Source logic was modified, but no tests were added or updated. You must write tests to pass `--tests`.'
66
+ });
67
+ }
68
+ }
69
+
70
+ for (const file of changedFiles) {
71
+ const normalizedFile = file.replace(/\\/g, '/');
72
+ const entry = manifest.files[normalizedFile];
73
+
74
+ const changedLines = getChangedLines(normalizedFile);
75
+
76
+ // ── Tier 0: Secret Sentinel ─────────────────────────────────────────────
77
+ if (changedLines.size > 0) {
78
+ // Check if this file has explicitly allowed secrets
79
+ const hasOverride = manifest.allowedSecrets && manifest.allowedSecrets[normalizedFile];
80
+ if (!hasOverride) {
81
+ for (const [lineNum, content] of changedLines.entries()) {
82
+ const secretType = detectSecret(content);
83
+ if (secretType) {
84
+ violations.push({
85
+ type: 'secret',
86
+ file: normalizedFile,
87
+ message: `SECRET LEAK [${secretType}] detected in '${normalizedFile}' on line ${lineNum}.`,
88
+ });
89
+ break; // One secret violation per file is enough
90
+ }
91
+ }
92
+ }
93
+ }
94
+
95
+ // ── Tier 1: File-level lock ─────────────────────────────────────────────
96
+ if (entry && (entry.status === 'locked' || entry.status === 'sealed')) {
97
+ const label = entry.status === 'sealed' ? 'SEALED' : 'LOCKED';
98
+ violations.push({
99
+ type: 'file',
100
+ file: normalizedFile,
101
+ message: `File '${normalizedFile}' is ${label}.`,
102
+ });
103
+ continue; // No need to check functions if the whole file is locked
104
+ }
105
+
106
+ // ── Tier 2: Function-level lock ─────────────────────────────────────────
107
+ if (!entry || !entry.functions) continue;
108
+
109
+ const lockedFunctions = Object.entries(entry.functions)
110
+ .filter(([, fnData]) => fnData.status === 'locked')
111
+ .map(([name]) => name);
112
+
113
+ if (lockedFunctions.length === 0) continue;
114
+
115
+ // Re-extract function boundaries from the current on-disk file
116
+ const currentFunctions = extractFunctions(normalizedFile);
117
+
118
+ for (const lockedFnName of lockedFunctions) {
119
+ const fn = currentFunctions.find(f => f.name === lockedFnName);
120
+ if (!fn) {
121
+ // Function was deleted or renamed — this itself is a violation
122
+ violations.push({
123
+ type: 'function-missing',
124
+ file: normalizedFile,
125
+ fn: lockedFnName,
126
+ message: `Locked function '${lockedFnName}' in '${normalizedFile}' was removed or renamed.`,
127
+ });
128
+ continue;
129
+ }
130
+
131
+ // Check if any changed line falls within the function's boundaries
132
+ for (const line of changedLines.keys()) {
133
+ if (line >= fn.startLine && line <= fn.endLine) {
134
+ violations.push({
135
+ type: 'function',
136
+ file: normalizedFile,
137
+ fn: lockedFnName,
138
+ message:
139
+ `Locked function '${lockedFnName}' in '${normalizedFile}' was modified ` +
140
+ `(changed line ${line} is inside [${fn.startLine}–${fn.endLine}]).`,
141
+ });
142
+ break; // One violation per function is enough
143
+ }
144
+ }
145
+ }
146
+ }
147
+
148
+ // ── Report ────────────────────────────────────────────────────────────────
149
+ if (violations.length === 0) {
150
+ console.log('✅ Scope guard passed — no locked files or functions were modified.');
151
+ return;
152
+ }
153
+
154
+ console.error(`\n❌ Scope violations detected:\n`);
155
+ for (const v of violations) {
156
+ console.error(` VIOLATION: ${v.message}`);
157
+ }
158
+ console.error(
159
+ `\n${violations.length} violation(s) found.\n` +
160
+ ` • Revert unintentional changes with: git restore <file>\n` +
161
+ ` • Explicitly unlock with: scopelock unlock <file>[:<function>] "<reason>"`
162
+ );
163
+
164
+ process.exit(1);
165
+ }
166
+
167
+ module.exports = { guard };
package/src/manifest.js CHANGED
@@ -178,39 +178,39 @@ function lock(target, reason = 'manually locked') {
178
178
  }
179
179
 
180
180
  /**
181
- * Superlock a file — permanent, override-resistant production path lock.
182
- * Cannot be removed by 'unlock'. Requires 'sudo-unlock' with a human-approved ticket.
181
+ * Seal a file — permanent, override-resistant production path lock.
182
+ * Cannot be removed by 'unlock'. Requires 'unseal' with a human-approved ticket.
183
183
  *
184
184
  * @param {string} file File path
185
185
  * @param {string} reason Mandatory reason string
186
186
  */
187
- function superlock(file, reason) {
187
+ function seal(file, reason) {
188
188
  const relativePath = file.replace(/\\/g, '/');
189
189
  const manifest = getManifest();
190
190
  ensureFileEntry(manifest, relativePath);
191
191
  const entry = manifest.files[relativePath];
192
192
 
193
- entry.status = 'superlocked';
193
+ entry.status = 'sealed';
194
194
  entry.history.push({
195
195
  timestamp: new Date().toISOString(),
196
- action: 'superlocked',
196
+ action: 'sealed',
197
197
  reason,
198
198
  });
199
199
  saveManifest(manifest);
200
- console.log(`🔐 SUPERLOCKED ${relativePath}. Only 'scopelock sudo-unlock' with a human-approved ticket can release this.`);
200
+ console.log(`🔐 SEALED ${relativePath}. Only 'scopelock unseal' with a human-approved ticket can release this.`);
201
201
  }
202
202
 
203
203
  /**
204
- * Remove a superlock from a file. Requires an explicit human-approved ticket string.
205
- * This is the only command that can override a 'superlocked' file.
204
+ * Remove a seal from a file. Requires an explicit human-approved ticket string.
205
+ * This is the only command that can override a 'sealed' file.
206
206
  *
207
207
  * @param {string} file File path
208
208
  * @param {string} ticket Human-approved ticket (e.g. "JIRA-123" or "PR-456")
209
209
  * @param {string} reason Mandatory reason string
210
210
  */
211
- function sudoUnlock(file, ticket, reason) {
211
+ function unseal(file, ticket, reason) {
212
212
  if (!ticket || !reason) {
213
- console.error('Usage: scopelock sudo-unlock <file> --human-approved=<ticket> <reason>');
213
+ console.error('Usage: scopelock unseal <file> --human-approved=<ticket> <reason>');
214
214
  process.exit(1);
215
215
  }
216
216
  const relativePath = file.replace(/\\/g, '/');
@@ -218,20 +218,20 @@ function sudoUnlock(file, ticket, reason) {
218
218
  ensureFileEntry(manifest, relativePath);
219
219
  const entry = manifest.files[relativePath];
220
220
 
221
- if (entry.status !== 'superlocked') {
222
- console.error(`'${relativePath}' is not superlocked. Use 'scopelock unlock' instead.`);
221
+ if (entry.status !== 'sealed') {
222
+ console.error(`'${relativePath}' is not sealed. Use 'scopelock unlock' instead.`);
223
223
  process.exit(1);
224
224
  }
225
225
 
226
226
  entry.status = 'active';
227
227
  entry.history.push({
228
228
  timestamp: new Date().toISOString(),
229
- action: 'sudo-unlocked',
229
+ action: 'unsealed',
230
230
  humanApproved: ticket,
231
231
  reason,
232
232
  });
233
233
  saveManifest(manifest);
234
- console.log(`🔓 SUDO-UNLOCKED ${relativePath}. Ticket: ${ticket}. Reason: ${reason}`);
234
+ console.log(`🔓 UNSEALED ${relativePath}. Ticket: ${ticket}. Reason: ${reason}`);
235
235
  }
236
236
 
237
237
  /**
@@ -263,12 +263,12 @@ function unlock(target, reason) {
263
263
  saveManifest(manifest);
264
264
  console.log(`🔓 Unlocked function '${funcName}' in ${relativePath}. Reason: ${reason}`);
265
265
  } else {
266
- // Guard against bypassing a superlock with a normal unlock
267
- if (entry.status === 'superlocked') {
266
+ // Guard against bypassing a seal with a normal unlock
267
+ if (entry.status === 'sealed') {
268
268
  console.error(
269
- `❌ '${relativePath}' is SUPERLOCKED and cannot be unlocked with 'scopelock unlock'.\n` +
269
+ `❌ '${relativePath}' is SEALED and cannot be unlocked with 'scopelock unlock'.\n` +
270
270
  ` This path is a protected production route.\n` +
271
- ` Use: scopelock sudo-unlock ${relativePath} --human-approved=<ticket> <reason>`
271
+ ` Use: scopelock unseal ${relativePath} --human-approved=<ticket> <reason>`
272
272
  );
273
273
  process.exit(1);
274
274
  }
@@ -286,7 +286,7 @@ function status() {
286
286
  const manifest = getManifest();
287
287
  const files = Object.entries(manifest.files);
288
288
 
289
- const superlocked = files.filter(([, v]) => v.status === 'superlocked');
289
+ const sealed = files.filter(([, v]) => v.status === 'sealed');
290
290
  const locked = files.filter(([, v]) => v.status === 'locked');
291
291
  const active = files.filter(([, v]) => v.status === 'active');
292
292
  const unscoped = files.filter(([, v]) => v.status === 'unscoped');
@@ -299,10 +299,16 @@ function status() {
299
299
  }
300
300
 
301
301
  console.log(`\n📋 scopelock status\n`);
302
+ console.log(` 🛡️ sealed — ${sealed.length} file(s)`);
302
303
  console.log(` 🔒 locked — ${locked.length} file(s), ${lockedFnCount} function(s)`);
303
304
  console.log(` ✏️ active — ${active.length} file(s)`);
304
305
  console.log(` ⬜ unscoped — ${unscoped.length} file(s)\n`);
305
306
 
307
+ if (sealed.length > 0) {
308
+ console.log('\nSealed files:');
309
+ sealed.forEach(([f]) => console.log(` ${f}`));
310
+ }
311
+
306
312
  if (locked.length > 0) {
307
313
  console.log(`Locked files:`);
308
314
  for (const [filePath, data] of locked) {
@@ -335,30 +341,20 @@ function status() {
335
341
  }
336
342
 
337
343
  /**
338
- * Allow a secret to be committed in a specific file.
344
+ * Trust a file to contain a mock secret, bypassing the Secret Sentinel.
339
345
  *
340
- * @param {string} file "<file>"
341
- * @param {string} reason Mandatory reason string.
346
+ * @param {string} file
347
+ * @param {string} reason
342
348
  */
343
- function allowSecret(file, reason) {
349
+ function trust(file, reason) {
344
350
  const relativePath = file.replace(/\\/g, '/');
345
351
  const manifest = getManifest();
346
-
347
- if (!manifest.allowedSecrets) {
348
- manifest.allowedSecrets = {};
349
- }
350
-
351
- if (!manifest.allowedSecrets[relativePath]) {
352
- manifest.allowedSecrets[relativePath] = [];
353
- }
354
-
355
- manifest.allowedSecrets[relativePath].push({
352
+ manifest.allowedSecrets[relativePath] = {
356
353
  timestamp: new Date().toISOString(),
357
- reason
358
- });
359
-
354
+ reason,
355
+ };
360
356
  saveManifest(manifest);
361
357
  console.log(`⚠️ Secret Sentinel bypassed for ${relativePath}. Reason: ${reason}`);
362
358
  }
363
359
 
364
- module.exports = { init, lock, unlock, superlock, sudoUnlock, allowSecret, status, getManifest, saveManifest };
360
+ module.exports = { init, lock, unlock, seal, unseal, trust, status, getManifest, saveManifest };
package/test/run.js CHANGED
@@ -86,25 +86,25 @@ assert(m2.files['readme.txt'].status === 'locked', 'readme.txt is locked');
86
86
 
87
87
  console.log('\n--- Test 5: File-level violation detection ---');
88
88
  fs.appendFileSync('readme.txt', 'AI hallucinated this line.\n');
89
- const violation1 = run(`${CLI} check`, true);
90
- assert(violation1.includes('VIOLATION'), 'check detects locked file modification');
89
+ const violation1 = run(`${CLI} guard`, true);
90
+ assert(violation1.includes('VIOLATION'), 'guard detects locked file modification');
91
91
 
92
92
  console.log('\n--- Test 6: File-level unlock clears violation ---');
93
93
  run(`${CLI} unlock readme.txt "intentional update to docs"`);
94
- const check1 = run(`${CLI} check`);
95
- assert(check1.includes('passed'), 'check passes after unlock');
94
+ const check1 = run(`${CLI} guard`);
95
+ assert(check1.includes('passed'), 'guard passes after unlock');
96
96
  run('git restore readme.txt'); // clean up
97
97
 
98
98
  console.log('\n--- Test 7: Secret Sentinel detection ---');
99
99
  fs.appendFileSync('readme.txt', 'const stripe_key = "sk_test_12345abcdeABCDE12345abcd";\n');
100
- const secretViolation = run(`${CLI} check`, true);
101
- assert(secretViolation.includes('SECRET LEAK'), 'check detects leaked stripe key');
102
- assert(secretViolation.includes('Stripe Secret Key'), 'check identifies secret type');
103
-
104
- console.log('\n--- Test 8: Secret Sentinel bypass (allow-secret) ---');
105
- run(`${CLI} allow-secret readme.txt "it is a mock key for tests"`);
106
- const secretCheck = run(`${CLI} check`);
107
- assert(secretCheck.includes('passed'), 'allow-secret successfully bypasses secret sentinel');
100
+ const secretViolation = run(`${CLI} guard`, true);
101
+ assert(secretViolation.includes('SECRET LEAK'), 'guard detects leaked stripe key');
102
+ assert(secretViolation.includes('Stripe Secret Key'), 'guard identifies secret type');
103
+
104
+ console.log('\n--- Test 8: Secret Sentinel bypass (trust) ---');
105
+ run(`${CLI} trust readme.txt "it is a mock key for tests"`);
106
+ const secretCheck = run(`${CLI} guard`);
107
+ assert(secretCheck.includes('passed'), 'trust successfully bypasses secret sentinel');
108
108
  run('git restore readme.txt'); // clean up
109
109
 
110
110
  console.log('\n--- Test 9: Function-level lock ---');
@@ -126,7 +126,7 @@ function workInProgress() {
126
126
  return 'actively being edited -- new change';
127
127
  }
128
128
  `.trimStart());
129
- const check2 = run(`${CLI} check`);
129
+ const check2 = run(`${CLI} guard`);
130
130
  assert(check2.includes('passed'), 'change outside locked function does not trigger violation');
131
131
 
132
132
  console.log('\n--- Test 11: Change INSIDE locked function — violation ---');
@@ -139,14 +139,14 @@ function workInProgress() {
139
139
  return 'actively being edited -- new change';
140
140
  }
141
141
  `.trimStart());
142
- const violation2 = run(`${CLI} check`, true);
142
+ const violation2 = run(`${CLI} guard`, true);
143
143
  assert(violation2.includes('VIOLATION'), 'change inside locked function triggers violation');
144
144
  assert(violation2.includes('stableFunc'), 'violation names the locked function');
145
145
 
146
146
  console.log('\n--- Test 12: Function unlock clears function-level violation ---');
147
147
  run(`${CLI} unlock app.js:stableFunc "need to update return value for new API"`);
148
- const check3 = run(`${CLI} check`);
149
- assert(check3.includes('passed'), 'check passes after function unlock');
148
+ const check3 = run(`${CLI} guard`);
149
+ assert(check3.includes('passed'), 'guard passes after function unlock');
150
150
 
151
151
  console.log('\n--- Test 13: Lock unknown function fails gracefully ---');
152
152
  const badLock = run(`${CLI} lock app.js:doesNotExist "testing"`, true);
@@ -159,55 +159,55 @@ assert(ctx.includes('SCOPE CONTEXT'), 'context output contains header');
159
159
  console.log('\n--- Test 15: Test Coverage Gate (Missing Tests) ---');
160
160
  fs.writeFileSync('feature.js', 'console.log("new logic");\n');
161
161
  run('git add feature.js');
162
- const testGateOut = run(`${CLI} check --require-tests`, true);
163
- assert(testGateOut.includes('TEST GATE VIOLATION'), 'check catches missing tests when flag is used');
162
+ const testGateOut = run(`${CLI} guard --tests`, true);
163
+ assert(testGateOut.includes('TEST GATE VIOLATION'), 'guard catches missing tests when flag is used');
164
164
 
165
165
  console.log('\n--- Test 16: Test Coverage Gate (Tests Provided) ---');
166
166
  fs.writeFileSync('feature.test.js', 'console.log("test for logic");\n');
167
167
  run('git add feature.test.js');
168
- const testGatePass = run(`${CLI} check --require-tests`);
169
- assert(testGatePass.includes('passed'), 'check passes when test files accompany source files');
168
+ const testGatePass = run(`${CLI} guard --tests`);
169
+ assert(testGatePass.includes('passed'), 'guard passes when test files accompany source files');
170
170
 
171
171
  console.log('\n--- Test 17: Rollback Snapshot Creation ---');
172
- run(`${CLI} snapshot`);
172
+ run(`${CLI} save`);
173
173
  const m4 = JSON.parse(fs.readFileSync('.scopelock.json', 'utf8'));
174
174
  assert(m4.lastSnapshot === 'clean' || m4.lastSnapshot === 'dirty', 'snapshot state is tracked in manifest');
175
175
 
176
176
  console.log('\n--- Test 18: Rollback Revert ---');
177
177
  fs.writeFileSync('rogue.js', 'I am a rogue agent destroying things');
178
- run(`${CLI} revert`);
179
- assert(!fs.existsSync('rogue.js'), 'revert destroys untracked rogue files');
178
+ run(`${CLI} restore`);
179
+ assert(!fs.existsSync('rogue.js'), 'restore destroys untracked rogue files');
180
180
  const m5 = JSON.parse(fs.readFileSync('.scopelock.json', 'utf8'));
181
- assert(m5.lastSnapshot === null, 'revert clears the snapshot marker');
181
+ assert(m5.lastSnapshot === null, 'restore clears the snapshot marker');
182
182
 
183
- console.log('\n--- Test 19: Production Path Lock (superlock) ---');
184
- run(`${CLI} superlock app.js "core auth logic — requires PR approval to modify"`);
183
+ console.log('\n--- Test 19: Production Path Lock (seal) ---');
184
+ run(`${CLI} seal app.js "core auth logic — requires PR approval to modify"`);
185
185
  const m6 = JSON.parse(fs.readFileSync('.scopelock.json', 'utf8'));
186
- assert(m6.files['app.js'].status === 'superlocked', 'superlock sets status to superlocked');
186
+ assert(m6.files['app.js'].status === 'sealed', 'seal sets status to sealed');
187
187
 
188
- console.log('\n--- Test 20: superlock blocks regular unlock ---');
188
+ console.log('\n--- Test 20: seal blocks regular unlock ---');
189
189
  const superUnlockOut = run(`${CLI} unlock app.js "trying to bypass"`, true);
190
- assert(superUnlockOut.includes('SUPERLOCKED'), 'regular unlock is blocked on superlocked file');
190
+ assert(superUnlockOut.includes('SEALED'), 'regular unlock is blocked on sealed file');
191
191
 
192
- console.log('\n--- Test 21: superlock blocks scopelock check ---');
192
+ console.log('\n--- Test 21: seal blocks scopelock guard ---');
193
193
  fs.appendFileSync('app.js', '\n// rogue addition\n');
194
- const superCheckOut = run(`${CLI} check`, true);
195
- assert(superCheckOut.includes('SUPERLOCKED'), 'check reports SUPERLOCKED violation');
194
+ const superCheckOut = run(`${CLI} guard`, true);
195
+ assert(superCheckOut.includes('SEALED'), 'guard reports SEALED violation');
196
196
  run('git restore app.js');
197
197
 
198
- console.log('\n--- Test 22: sudo-unlock releases a superlock with ticket ---');
199
- run(`${CLI} sudo-unlock app.js --human-approved=JIRA-999 "approved by senior eng for critical hotfix"`);
198
+ console.log('\n--- Test 22: unseal releases a seal with ticket ---');
199
+ run(`${CLI} unseal app.js --human-approved=JIRA-999 "approved by senior eng for critical hotfix"`);
200
200
  const m7 = JSON.parse(fs.readFileSync('.scopelock.json', 'utf8'));
201
- assert(m7.files['app.js'].status === 'active', 'sudo-unlock transitions superlocked to active');
202
- const sudoHistory = m7.files['app.js'].history.find(h => h.action === 'sudo-unlocked');
203
- assert(sudoHistory && sudoHistory.humanApproved === 'JIRA-999', 'sudo-unlock logs the human-approved ticket');
201
+ assert(m7.files['app.js'].status === 'active', 'unseal transitions sealed to active');
202
+ const sudoHistory = m7.files['app.js'].history.find(h => h.action === 'unsealed');
203
+ assert(sudoHistory && sudoHistory.humanApproved === 'JIRA-999', 'unseal logs the human-approved ticket');
204
204
 
205
- console.log('\n--- Test 23: Blast Radius Map ---');
205
+ console.log('\n--- Test 23: Blast Radius Map (impact) ---');
206
206
  // Make app.js import readme.txt by creating an importer
207
207
  fs.writeFileSync('importer.js', `import { something } from './app';\n`);
208
- const blastOut = run(`${CLI} blast-radius app.js`);
209
- assert(blastOut.includes('Blast Radius'), 'blast-radius outputs the report header');
210
- assert(blastOut.includes('importer.js'), 'blast-radius correctly identifies importer.js as a dependent');
208
+ const blastOut = run(`${CLI} impact app.js`);
209
+ assert(blastOut.includes('Blast Radius'), 'impact outputs the report header');
210
+ assert(blastOut.includes('importer.js'), 'impact correctly identifies importer.js as a dependent');
211
211
 
212
212
  // ─── Done ─────────────────────────────────────────────────────────────────────
213
213