@manukyalo/scopelock 2.2.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 +59 -44
- package/bin/scopelock.js +57 -19
- package/package.json +1 -1
- package/skills/blast-radius/SKILL.md +56 -0
- package/skills/dependency-lockdown/SKILL.md +37 -0
- package/skills/production-path-lock/SKILL.md +62 -0
- package/skills/rollback-snapshot/SKILL.md +44 -0
- package/skills/scope-enforcement/SKILL.md +7 -7
- package/skills/secret-sentinel/SKILL.md +51 -0
- package/skills/test-coverage-gate/SKILL.md +3 -3
- package/src/blast.js +160 -0
- package/src/git.js +167 -166
- package/src/manifest.js +84 -21
- package/test/run.js +54 -25
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}
|
|
90
|
-
assert(violation1.includes('VIOLATION'), '
|
|
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}
|
|
95
|
-
assert(check1.includes('passed'), '
|
|
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}
|
|
101
|
-
assert(secretViolation.includes('SECRET LEAK'), '
|
|
102
|
-
assert(secretViolation.includes('Stripe Secret Key'), '
|
|
103
|
-
|
|
104
|
-
console.log('\n--- Test 8: Secret Sentinel bypass (
|
|
105
|
-
run(`${CLI}
|
|
106
|
-
const secretCheck = run(`${CLI}
|
|
107
|
-
assert(secretCheck.includes('passed'), '
|
|
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}
|
|
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}
|
|
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}
|
|
149
|
-
assert(check3.includes('passed'), '
|
|
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,27 +159,56 @@ 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}
|
|
163
|
-
assert(testGateOut.includes('TEST GATE VIOLATION'), '
|
|
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}
|
|
169
|
-
assert(testGatePass.includes('passed'), '
|
|
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}
|
|
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}
|
|
179
|
-
assert(!fs.existsSync('rogue.js'), '
|
|
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, '
|
|
181
|
+
assert(m5.lastSnapshot === null, 'restore clears the snapshot marker');
|
|
182
|
+
|
|
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
|
+
const m6 = JSON.parse(fs.readFileSync('.scopelock.json', 'utf8'));
|
|
186
|
+
assert(m6.files['app.js'].status === 'sealed', 'seal sets status to sealed');
|
|
187
|
+
|
|
188
|
+
console.log('\n--- Test 20: seal blocks regular unlock ---');
|
|
189
|
+
const superUnlockOut = run(`${CLI} unlock app.js "trying to bypass"`, true);
|
|
190
|
+
assert(superUnlockOut.includes('SEALED'), 'regular unlock is blocked on sealed file');
|
|
191
|
+
|
|
192
|
+
console.log('\n--- Test 21: seal blocks scopelock guard ---');
|
|
193
|
+
fs.appendFileSync('app.js', '\n// rogue addition\n');
|
|
194
|
+
const superCheckOut = run(`${CLI} guard`, true);
|
|
195
|
+
assert(superCheckOut.includes('SEALED'), 'guard reports SEALED violation');
|
|
196
|
+
run('git restore app.js');
|
|
197
|
+
|
|
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
|
+
const m7 = JSON.parse(fs.readFileSync('.scopelock.json', 'utf8'));
|
|
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
|
+
|
|
205
|
+
console.log('\n--- Test 23: Blast Radius Map (impact) ---');
|
|
206
|
+
// Make app.js import readme.txt by creating an importer
|
|
207
|
+
fs.writeFileSync('importer.js', `import { something } from './app';\n`);
|
|
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');
|
|
182
211
|
|
|
183
212
|
// ─── Done ─────────────────────────────────────────────────────────────────────
|
|
184
213
|
|
|
185
|
-
console.log('\n✅ All
|
|
214
|
+
console.log('\n✅ All 23 tests passed.\n');
|