speclock 2.5.0 → 3.5.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 +99 -5
- package/package.json +15 -3
- package/src/cli/index.js +314 -1
- package/src/core/auth.js +341 -0
- package/src/core/compliance.js +1 -1
- package/src/core/crypto.js +158 -0
- package/src/core/engine.js +62 -0
- package/src/core/policy.js +719 -0
- package/src/core/sso.js +386 -0
- package/src/core/storage.js +23 -4
- package/src/core/telemetry.js +281 -0
- package/src/dashboard/index.html +338 -0
- package/src/mcp/http-server.js +248 -3
- package/src/mcp/server.js +172 -1
package/README.md
CHANGED
|
@@ -59,6 +59,12 @@ No other tool does this. Not Claude's native memory. Not Mem0. Not CLAUDE.md fil
|
|
|
59
59
|
| Drift detection | No | No | No | **Yes — scans changes against locks** |
|
|
60
60
|
| CI/CD integration | No | No | No | **Yes — GitHub Actions** |
|
|
61
61
|
| **Hard enforcement (block violations)** | No | No | No | **Yes — hard mode blocks above threshold** |
|
|
62
|
+
| **API Key Auth + RBAC** | No | No | No | **Yes** |
|
|
63
|
+
| **Encrypted Storage (AES-256)** | No | No | No | **Yes** |
|
|
64
|
+
| **Policy-as-Code DSL** | No | No | No | **Yes — declarative YAML rules** |
|
|
65
|
+
| **OAuth/OIDC SSO** | No | No | No | **Yes — Okta, Azure AD, Auth0** |
|
|
66
|
+
| **Admin Dashboard** | No | No | No | **Yes — real-time web UI** |
|
|
67
|
+
| **Telemetry & Analytics** | No | No | No | **Yes — opt-in usage insights** |
|
|
62
68
|
| Multi-agent timeline | No | No | No | **Yes** |
|
|
63
69
|
| Cross-platform | Claude only | MCP only | Tool-specific | **Universal (MCP + npm)** |
|
|
64
70
|
|
|
@@ -193,10 +199,10 @@ Result: NO CONFLICT (confidence: 7%)
|
|
|
193
199
|
| Mode | Platforms | How It Works |
|
|
194
200
|
|------|-----------|--------------|
|
|
195
201
|
| **MCP Remote** | Lovable, bolt.diy, Base44 | Connect via URL — no install needed |
|
|
196
|
-
| **MCP Local** | Claude Code, Cursor, Windsurf, Cline | `npx speclock serve` —
|
|
202
|
+
| **MCP Local** | Claude Code, Cursor, Windsurf, Cline | `npx speclock serve` — 31 tools via MCP |
|
|
197
203
|
| **npm File-Based** | Bolt.new, Aider, Rocket.new | `npx speclock setup` — AI reads SPECLOCK.md + uses CLI |
|
|
198
204
|
|
|
199
|
-
##
|
|
205
|
+
## 31 MCP Tools
|
|
200
206
|
|
|
201
207
|
### Memory Management
|
|
202
208
|
| Tool | Purpose |
|
|
@@ -258,6 +264,13 @@ Result: NO CONFLICT (confidence: 7%)
|
|
|
258
264
|
| `speclock_semantic_audit` | Semantic pre-commit: analyze code changes vs locks |
|
|
259
265
|
| `speclock_override_history` | View lock override history for audit review |
|
|
260
266
|
|
|
267
|
+
### Enterprise Platform (v3.5)
|
|
268
|
+
| Tool | Purpose |
|
|
269
|
+
|------|---------|
|
|
270
|
+
| `speclock_policy_evaluate` | Evaluate policy-as-code rules against proposed actions |
|
|
271
|
+
| `speclock_policy_manage` | CRUD for policy rules — list, add, remove, init, export |
|
|
272
|
+
| `speclock_telemetry` | View opt-in telemetry summary and usage analytics |
|
|
273
|
+
|
|
261
274
|
## Auto-Guard: Locks That Actually Work
|
|
262
275
|
|
|
263
276
|
When you add a lock, SpecLock **automatically finds and guards related files**:
|
|
@@ -332,6 +345,16 @@ speclock override <lockId> <reason> # Override a lock with justification
|
|
|
332
345
|
speclock overrides [--lock <id>] # Show override history
|
|
333
346
|
speclock audit-semantic # Semantic pre-commit audit
|
|
334
347
|
|
|
348
|
+
# Enterprise Platform (v3.5)
|
|
349
|
+
speclock policy list # List policy rules
|
|
350
|
+
speclock policy init # Initialize policy-as-code
|
|
351
|
+
speclock policy add <name> --files <pattern> --actions <types> --enforce <level>
|
|
352
|
+
speclock policy evaluate --files <f> --type <t> # Evaluate against rules
|
|
353
|
+
speclock policy export # Export policy as portable YAML
|
|
354
|
+
speclock telemetry status # View telemetry summary
|
|
355
|
+
speclock sso status # Show SSO configuration
|
|
356
|
+
speclock sso configure # Configure OAuth/OIDC SSO
|
|
357
|
+
|
|
335
358
|
# Other
|
|
336
359
|
speclock status # Show brain summary
|
|
337
360
|
speclock serve [--project <path>] # Start MCP server
|
|
@@ -400,6 +423,76 @@ Hard mode: AI is BLOCKED — cannot proceed (MCP returns isError:
|
|
|
400
423
|
- **Escalation**: Lock overridden 3+ times → auto-creates a review note
|
|
401
424
|
- **Semantic pre-commit**: Parses actual git diff content, runs semantic analysis against locks
|
|
402
425
|
|
|
426
|
+
## Security & Access Control (v3.0)
|
|
427
|
+
|
|
428
|
+
### API Key Authentication
|
|
429
|
+
SHA-256 hashed keys stored server-side. HTTP transport uses `Authorization: Bearer <key>` headers. MCP transport authenticates via the `SPECLOCK_API_KEY` environment variable. Keys are never stored in plaintext.
|
|
430
|
+
|
|
431
|
+
### RBAC (4 Roles)
|
|
432
|
+
| Role | Permissions |
|
|
433
|
+
|------|-------------|
|
|
434
|
+
| **viewer** | Read-only access to context, locks, decisions, and events |
|
|
435
|
+
| **developer** | Read + override locks with a documented reason |
|
|
436
|
+
| **architect** | Read + write (add/remove locks, decisions) + override |
|
|
437
|
+
| **admin** | Full access — manage keys, roles, enforcement settings, and all operations |
|
|
438
|
+
|
|
439
|
+
### AES-256-GCM Encryption
|
|
440
|
+
Transparent encrypt-on-write / decrypt-on-read for `brain.json` and `events.log`. Encryption key is derived via PBKDF2 from the `SPECLOCK_ENCRYPTION_KEY` environment variable. Authenticated encryption (GCM) ensures both confidentiality and integrity. **HIPAA 2026 compliant.**
|
|
441
|
+
|
|
442
|
+
### Test Coverage
|
|
443
|
+
**330+ tests passing** across 6 test suites. Full coverage for authentication, authorization, encryption, semantic detection, audit chain integrity, policy-as-code, telemetry, and SSO.
|
|
444
|
+
|
|
445
|
+
## Enterprise Platform (v3.5)
|
|
446
|
+
|
|
447
|
+
### Policy-as-Code DSL
|
|
448
|
+
Declarative YAML-based policy rules for enterprise constraint enforcement:
|
|
449
|
+
|
|
450
|
+
```yaml
|
|
451
|
+
# .speclock/policy.yml
|
|
452
|
+
rules:
|
|
453
|
+
- name: "HIPAA PHI Protection"
|
|
454
|
+
match:
|
|
455
|
+
files: ["**/patient/**", "**/medical/**"]
|
|
456
|
+
actions: [delete, modify, export]
|
|
457
|
+
enforce: block
|
|
458
|
+
severity: critical
|
|
459
|
+
notify: ["security@company.com", "slack:#compliance"]
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
- **File pattern matching**: Glob patterns (`**/patient/**`, `src/api/*.js`)
|
|
463
|
+
- **Action-type filtering**: Block specific operations (delete, modify, create, export)
|
|
464
|
+
- **Enforcement levels**: `block` (hard stop), `warn` (advisory), `log` (record only)
|
|
465
|
+
- **Severity levels**: critical, high, medium, low
|
|
466
|
+
- **Notification hooks**: Email, Slack, webhook channels
|
|
467
|
+
- **Import/export**: Share policies between organizations
|
|
468
|
+
|
|
469
|
+
### OAuth/OIDC SSO
|
|
470
|
+
Enterprise single sign-on with corporate identity providers:
|
|
471
|
+
|
|
472
|
+
- **Providers**: Okta, Azure AD, Auth0, any OIDC-compliant IdP
|
|
473
|
+
- **PKCE flow**: Authorization Code with Proof Key (S256)
|
|
474
|
+
- **Role mapping**: Map OIDC groups/roles to SpecLock roles (viewer/developer/architect/admin)
|
|
475
|
+
- **Session management**: 8-hour TTL, token refresh, session listing/revocation
|
|
476
|
+
- **MCP June 2025 spec**: OAuth compliance
|
|
477
|
+
|
|
478
|
+
### Admin Dashboard
|
|
479
|
+
Real-time web UI served from the HTTP server:
|
|
480
|
+
|
|
481
|
+
- **Access**: `http://localhost:PORT/dashboard`
|
|
482
|
+
- **Views**: Lock overview, violation timeline, session history, health score
|
|
483
|
+
- **Metrics**: Enforcement mode, auth status, encryption status, audit chain integrity
|
|
484
|
+
- **Zero dependencies**: Vanilla HTML/JS — no framework overhead
|
|
485
|
+
- **Auto-refresh**: Updates every 30 seconds
|
|
486
|
+
|
|
487
|
+
### Telemetry & Analytics
|
|
488
|
+
Opt-in usage insights for product improvement:
|
|
489
|
+
|
|
490
|
+
- **Disabled by default** — enable with `SPECLOCK_TELEMETRY=true`
|
|
491
|
+
- **Tracks**: Tool usage counts, conflict detection rates, response times, feature adoption
|
|
492
|
+
- **Never tracks**: Lock content, project names, any PII
|
|
493
|
+
- **Local storage**: `.speclock/telemetry.json` with optional remote flush
|
|
494
|
+
- **30-day rolling window**: Daily stats with trend analysis
|
|
495
|
+
|
|
403
496
|
---
|
|
404
497
|
|
|
405
498
|
## Architecture
|
|
@@ -410,14 +503,15 @@ Hard mode: AI is BLOCKED — cannot proceed (MCP returns isError:
|
|
|
410
503
|
└──────────────┬──────────────────┬────────────────────┘
|
|
411
504
|
│ │
|
|
412
505
|
MCP Protocol File-Based (npm)
|
|
413
|
-
(
|
|
506
|
+
(31 tool calls) (reads SPECLOCK.md +
|
|
414
507
|
.speclock/context/latest.md,
|
|
415
508
|
runs CLI commands)
|
|
416
509
|
│ │
|
|
417
510
|
┌──────────────▼──────────────────▼────────────────────┐
|
|
418
511
|
│ SpecLock Core Engine │
|
|
419
512
|
│ Memory | Tracking | Enforcement | Git | Intelligence │
|
|
420
|
-
│ Audit | Compliance | License
|
|
513
|
+
│ Audit | Compliance | License | Auth | RBAC │
|
|
514
|
+
│ AES-256-GCM Encryption (brain.json, events.log) │
|
|
421
515
|
└──────────────────────┬───────────────────────────────┘
|
|
422
516
|
│
|
|
423
517
|
.speclock/
|
|
@@ -447,4 +541,4 @@ MIT License - see [LICENSE](LICENSE) file.
|
|
|
447
541
|
|
|
448
542
|
---
|
|
449
543
|
|
|
450
|
-
*SpecLock
|
|
544
|
+
*SpecLock v3.0.0 — Semantic conflict detection + enterprise audit & compliance. 100% detection, 0% false positives. HMAC audit chain, SOC 2/HIPAA exports. Hard enforcement mode. API Key Auth + RBAC. AES-256-GCM encrypted storage. 300 tests passing. Because remembering isn't enough — AI needs to respect boundaries.*
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "speclock",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "AI constraint engine with hard enforcement, semantic pre-commit, HMAC audit chain, SOC 2/HIPAA compliance. 100% detection, 0% false positives.
|
|
3
|
+
"version": "3.5.0",
|
|
4
|
+
"description": "AI constraint engine with Policy-as-Code DSL, OAuth/OIDC SSO, admin dashboard, telemetry, API key auth, RBAC, AES-256-GCM encryption, hard enforcement, semantic pre-commit, HMAC audit chain, SOC 2/HIPAA compliance. 100% detection, 0% false positives. 31 MCP tools + CLI. Enterprise platform.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/mcp/server.js",
|
|
7
7
|
"bin": {
|
|
@@ -36,7 +36,18 @@
|
|
|
36
36
|
"hipaa",
|
|
37
37
|
"compliance",
|
|
38
38
|
"audit-trail",
|
|
39
|
-
"hmac"
|
|
39
|
+
"hmac",
|
|
40
|
+
"encryption",
|
|
41
|
+
"aes-256",
|
|
42
|
+
"api-key",
|
|
43
|
+
"authentication",
|
|
44
|
+
"rbac",
|
|
45
|
+
"policy-as-code",
|
|
46
|
+
"sso",
|
|
47
|
+
"oauth",
|
|
48
|
+
"oidc",
|
|
49
|
+
"dashboard",
|
|
50
|
+
"telemetry"
|
|
40
51
|
],
|
|
41
52
|
"author": "Sandeep Roy (https://github.com/sgroy10)",
|
|
42
53
|
"license": "MIT",
|
|
@@ -59,6 +70,7 @@
|
|
|
59
70
|
"files": [
|
|
60
71
|
"bin/",
|
|
61
72
|
"src/",
|
|
73
|
+
"src/dashboard/",
|
|
62
74
|
"README.md",
|
|
63
75
|
"SPECLOCK-INSTRUCTIONS.md",
|
|
64
76
|
"LICENSE"
|
package/src/cli/index.js
CHANGED
|
@@ -33,6 +33,34 @@ import {
|
|
|
33
33
|
import { generateContext } from "../core/context.js";
|
|
34
34
|
import { readBrain } from "../core/storage.js";
|
|
35
35
|
import { installHook, removeHook } from "../core/hooks.js";
|
|
36
|
+
import {
|
|
37
|
+
isAuthEnabled,
|
|
38
|
+
enableAuth,
|
|
39
|
+
disableAuth,
|
|
40
|
+
createApiKey,
|
|
41
|
+
rotateApiKey,
|
|
42
|
+
revokeApiKey,
|
|
43
|
+
listApiKeys,
|
|
44
|
+
} from "../core/auth.js";
|
|
45
|
+
import { isEncryptionEnabled } from "../core/crypto.js";
|
|
46
|
+
import {
|
|
47
|
+
initPolicy,
|
|
48
|
+
addPolicyRule,
|
|
49
|
+
removePolicyRule,
|
|
50
|
+
listPolicyRules,
|
|
51
|
+
evaluatePolicy,
|
|
52
|
+
exportPolicy,
|
|
53
|
+
importPolicy,
|
|
54
|
+
} from "../core/policy.js";
|
|
55
|
+
import {
|
|
56
|
+
isTelemetryEnabled,
|
|
57
|
+
getTelemetrySummary,
|
|
58
|
+
} from "../core/telemetry.js";
|
|
59
|
+
import {
|
|
60
|
+
isSSOEnabled,
|
|
61
|
+
getSSOConfig,
|
|
62
|
+
saveSSOConfig,
|
|
63
|
+
} from "../core/sso.js";
|
|
36
64
|
|
|
37
65
|
// --- Argument parsing ---
|
|
38
66
|
|
|
@@ -88,7 +116,7 @@ function refreshContext(root) {
|
|
|
88
116
|
|
|
89
117
|
function printHelp() {
|
|
90
118
|
console.log(`
|
|
91
|
-
SpecLock
|
|
119
|
+
SpecLock v3.5.0 — AI Constraint Engine (Policy-as-Code + SSO + Dashboard + Telemetry + Auth + RBAC + Encryption)
|
|
92
120
|
Developed by Sandeep Roy (github.com/sgroy10)
|
|
93
121
|
|
|
94
122
|
Usage: speclock <command> [options]
|
|
@@ -136,10 +164,33 @@ Options:
|
|
|
136
164
|
|
|
137
165
|
Templates: nextjs, react, express, supabase, stripe, security-hardened
|
|
138
166
|
|
|
167
|
+
Policy-as-Code (v3.5):
|
|
168
|
+
policy list List all policy rules
|
|
169
|
+
policy init Initialize policy-as-code
|
|
170
|
+
policy add --name <name> Add a policy rule (--files, --enforce, --severity)
|
|
171
|
+
policy remove <ruleId> Remove a policy rule
|
|
172
|
+
policy evaluate <action> Evaluate action against policy rules
|
|
173
|
+
policy export Export policy as YAML
|
|
174
|
+
telemetry [status] Show telemetry status and analytics
|
|
175
|
+
sso status Show SSO configuration
|
|
176
|
+
sso configure --issuer <url> Configure SSO (--client-id, --client-secret)
|
|
177
|
+
|
|
178
|
+
Security (v3.0):
|
|
179
|
+
auth status Show auth status and active keys
|
|
180
|
+
auth create-key --role <role> Create API key (viewer/developer/architect/admin)
|
|
181
|
+
auth rotate-key <keyId> Rotate an API key
|
|
182
|
+
auth revoke-key <keyId> Revoke an API key
|
|
183
|
+
auth list-keys List all API keys
|
|
184
|
+
auth enable Enable API key authentication
|
|
185
|
+
auth disable Disable authentication
|
|
186
|
+
encrypt [status] Show encryption status
|
|
187
|
+
|
|
139
188
|
Enterprise:
|
|
140
189
|
SPECLOCK_AUDIT_SECRET HMAC secret for audit chain (env var)
|
|
141
190
|
SPECLOCK_LICENSE_KEY License key for Pro/Enterprise features
|
|
142
191
|
SPECLOCK_LLM_KEY API key for LLM-powered conflict detection
|
|
192
|
+
SPECLOCK_ENCRYPTION_KEY Master key for AES-256-GCM encryption
|
|
193
|
+
SPECLOCK_API_KEY API key for MCP server auth
|
|
143
194
|
|
|
144
195
|
Examples:
|
|
145
196
|
npx speclock setup --goal "Build PawPalace pet shop" --template nextjs
|
|
@@ -186,6 +237,12 @@ function showStatus(root) {
|
|
|
186
237
|
}
|
|
187
238
|
|
|
188
239
|
console.log(`Recent changes: ${brain.state.recentChanges.length}`);
|
|
240
|
+
console.log(`Auth: ${isAuthEnabled(root) ? "enabled" : "disabled"}`);
|
|
241
|
+
console.log(`Encryption: ${isEncryptionEnabled() ? "enabled (AES-256-GCM)" : "disabled"}`);
|
|
242
|
+
const policyRules = listPolicyRules(root);
|
|
243
|
+
console.log(`Policy rules: ${policyRules.active}/${policyRules.total}`);
|
|
244
|
+
console.log(`Telemetry: ${isTelemetryEnabled() ? "enabled" : "disabled"}`);
|
|
245
|
+
console.log(`SSO: ${isSSOEnabled(root) ? "configured" : "not configured"}`);
|
|
189
246
|
console.log("");
|
|
190
247
|
}
|
|
191
248
|
|
|
@@ -778,6 +835,262 @@ Tip: When starting a new chat, tell the AI:
|
|
|
778
835
|
process.exit(result.blocked ? 1 : 0);
|
|
779
836
|
}
|
|
780
837
|
|
|
838
|
+
// --- AUTH (v3.0) ---
|
|
839
|
+
if (cmd === "auth") {
|
|
840
|
+
const sub = args[0];
|
|
841
|
+
if (!sub || sub === "status") {
|
|
842
|
+
const enabled = isAuthEnabled(root);
|
|
843
|
+
console.log(`\nAuth Status: ${enabled ? "ENABLED" : "DISABLED"}`);
|
|
844
|
+
if (enabled) {
|
|
845
|
+
const keys = listApiKeys(root);
|
|
846
|
+
const active = keys.keys.filter(k => k.active);
|
|
847
|
+
console.log(`Active keys: ${active.length}`);
|
|
848
|
+
for (const k of active) {
|
|
849
|
+
console.log(` ${k.id} — ${k.name} (${k.role}) — last used: ${k.lastUsed || "never"}`);
|
|
850
|
+
}
|
|
851
|
+
} else {
|
|
852
|
+
console.log("Run 'speclock auth create-key --role admin' to enable auth.");
|
|
853
|
+
}
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
if (sub === "create-key") {
|
|
857
|
+
const flags = parseFlags(args.slice(1));
|
|
858
|
+
const role = flags.role || "developer";
|
|
859
|
+
const name = flags.name || flags._.join(" ") || "";
|
|
860
|
+
const result = createApiKey(root, role, name);
|
|
861
|
+
if (!result.success) {
|
|
862
|
+
console.error(result.error);
|
|
863
|
+
process.exit(1);
|
|
864
|
+
}
|
|
865
|
+
console.log(`\nAPI Key Created`);
|
|
866
|
+
console.log("=".repeat(50));
|
|
867
|
+
console.log(`Key ID: ${result.keyId}`);
|
|
868
|
+
console.log(`Role: ${result.role}`);
|
|
869
|
+
console.log(`Name: ${result.name}`);
|
|
870
|
+
console.log(`\nRaw Key: ${result.rawKey}`);
|
|
871
|
+
console.log(`\nSave this key — it CANNOT be retrieved later.`);
|
|
872
|
+
console.log(`\nUsage:`);
|
|
873
|
+
console.log(` HTTP: Authorization: Bearer ${result.rawKey}`);
|
|
874
|
+
console.log(` MCP: Set SPECLOCK_API_KEY=${result.rawKey} in MCP config`);
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
if (sub === "rotate-key") {
|
|
878
|
+
const keyId = args[1];
|
|
879
|
+
if (!keyId) {
|
|
880
|
+
console.error("Usage: speclock auth rotate-key <keyId>");
|
|
881
|
+
process.exit(1);
|
|
882
|
+
}
|
|
883
|
+
const result = rotateApiKey(root, keyId);
|
|
884
|
+
if (!result.success) {
|
|
885
|
+
console.error(result.error);
|
|
886
|
+
process.exit(1);
|
|
887
|
+
}
|
|
888
|
+
console.log(`\nKey Rotated`);
|
|
889
|
+
console.log(`Old key: ${result.oldKeyId} (revoked)`);
|
|
890
|
+
console.log(`New key: ${result.newKeyId}`);
|
|
891
|
+
console.log(`Raw Key: ${result.rawKey}`);
|
|
892
|
+
console.log(`\nSave this key — it CANNOT be retrieved later.`);
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
if (sub === "revoke-key") {
|
|
896
|
+
const keyId = args[1];
|
|
897
|
+
if (!keyId) {
|
|
898
|
+
console.error("Usage: speclock auth revoke-key <keyId>");
|
|
899
|
+
process.exit(1);
|
|
900
|
+
}
|
|
901
|
+
const reason = args.slice(2).join(" ") || "manual";
|
|
902
|
+
const result = revokeApiKey(root, keyId, reason);
|
|
903
|
+
if (!result.success) {
|
|
904
|
+
console.error(result.error);
|
|
905
|
+
process.exit(1);
|
|
906
|
+
}
|
|
907
|
+
console.log(`Key revoked: ${result.keyId} (${result.name}, ${result.role})`);
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
if (sub === "list-keys") {
|
|
911
|
+
const result = listApiKeys(root);
|
|
912
|
+
console.log(`\nAPI Keys (auth ${result.enabled ? "enabled" : "disabled"}):`);
|
|
913
|
+
console.log("=".repeat(50));
|
|
914
|
+
if (result.keys.length === 0) {
|
|
915
|
+
console.log(" No keys configured.");
|
|
916
|
+
} else {
|
|
917
|
+
for (const k of result.keys) {
|
|
918
|
+
const status = k.active ? "active" : `revoked (${k.revokedAt?.substring(0, 10) || "unknown"})`;
|
|
919
|
+
console.log(` ${k.id} — ${k.name} (${k.role}) [${status}]`);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
if (sub === "enable") {
|
|
925
|
+
enableAuth(root);
|
|
926
|
+
console.log("Auth enabled. API keys are now required for HTTP access.");
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
if (sub === "disable") {
|
|
930
|
+
disableAuth(root);
|
|
931
|
+
console.log("Auth disabled. All operations allowed without keys.");
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
console.error("Usage: speclock auth <create-key|rotate-key|revoke-key|list-keys|enable|disable|status>");
|
|
935
|
+
process.exit(1);
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
// --- POLICY (v3.5) ---
|
|
939
|
+
if (cmd === "policy") {
|
|
940
|
+
const sub = args[0];
|
|
941
|
+
if (!sub || sub === "list") {
|
|
942
|
+
const result = listPolicyRules(root);
|
|
943
|
+
console.log(`\nPolicy Rules (${result.active}/${result.total} active):`);
|
|
944
|
+
console.log("=".repeat(50));
|
|
945
|
+
if (result.rules.length === 0) {
|
|
946
|
+
console.log(" No rules. Run 'speclock policy init' to create a policy.");
|
|
947
|
+
} else {
|
|
948
|
+
for (const r of result.rules) {
|
|
949
|
+
const status = r.active !== false ? "active" : "inactive";
|
|
950
|
+
console.log(` ${r.id} — ${r.name} [${r.enforce}/${r.severity}] (${status})`);
|
|
951
|
+
console.log(` Files: ${(r.match?.files || []).join(", ")}`);
|
|
952
|
+
console.log(` Actions: ${(r.match?.actions || []).join(", ")}`);
|
|
953
|
+
console.log("");
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
if (sub === "init") {
|
|
959
|
+
const result = initPolicy(root);
|
|
960
|
+
if (!result.success) { console.error(result.error); process.exit(1); }
|
|
961
|
+
console.log("Policy-as-code initialized. Edit .speclock/policy.yml to add rules.");
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
if (sub === "add") {
|
|
965
|
+
const flags = parseFlags(args.slice(1));
|
|
966
|
+
const name = flags.name || flags._.join(" ");
|
|
967
|
+
if (!name) { console.error("Usage: speclock policy add --name <name> --files '**/*.js' --enforce block"); process.exit(1); }
|
|
968
|
+
const rule = {
|
|
969
|
+
name,
|
|
970
|
+
description: flags.description || "",
|
|
971
|
+
match: {
|
|
972
|
+
files: flags.files ? flags.files.split(",").map(s => s.trim()) : ["**/*"],
|
|
973
|
+
actions: flags.actions ? flags.actions.split(",").map(s => s.trim()) : ["modify", "delete"],
|
|
974
|
+
},
|
|
975
|
+
enforce: flags.enforce || "warn",
|
|
976
|
+
severity: flags.severity || "medium",
|
|
977
|
+
notify: flags.notify ? flags.notify.split(",").map(s => s.trim()) : [],
|
|
978
|
+
};
|
|
979
|
+
const result = addPolicyRule(root, rule);
|
|
980
|
+
if (!result.success) { console.error(result.error); process.exit(1); }
|
|
981
|
+
console.log(`Policy rule added: "${result.rule.name}" (${result.ruleId}) [${result.rule.enforce}]`);
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
if (sub === "remove") {
|
|
985
|
+
const ruleId = args[1];
|
|
986
|
+
if (!ruleId) { console.error("Usage: speclock policy remove <ruleId>"); process.exit(1); }
|
|
987
|
+
const result = removePolicyRule(root, ruleId);
|
|
988
|
+
if (!result.success) { console.error(result.error); process.exit(1); }
|
|
989
|
+
console.log(`Policy rule removed: "${result.removed.name}"`);
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
if (sub === "evaluate") {
|
|
993
|
+
const text = args.slice(1).join(" ");
|
|
994
|
+
if (!text) { console.error("Usage: speclock policy evaluate 'what you plan to do'"); process.exit(1); }
|
|
995
|
+
const result = evaluatePolicy(root, { description: text, text, type: "modify" });
|
|
996
|
+
if (result.passed) {
|
|
997
|
+
console.log(`Policy check passed. ${result.rulesChecked} rules evaluated.`);
|
|
998
|
+
} else {
|
|
999
|
+
console.log(`\nPolicy Violations (${result.violations.length}):`);
|
|
1000
|
+
for (const v of result.violations) {
|
|
1001
|
+
console.log(` [${v.severity.toUpperCase()}] ${v.ruleName} (${v.enforce})`);
|
|
1002
|
+
if (v.matchedFiles.length) console.log(` Files: ${v.matchedFiles.join(", ")}`);
|
|
1003
|
+
}
|
|
1004
|
+
if (result.blocked) process.exit(1);
|
|
1005
|
+
}
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
if (sub === "export") {
|
|
1009
|
+
const result = exportPolicy(root);
|
|
1010
|
+
if (!result.success) { console.error(result.error); process.exit(1); }
|
|
1011
|
+
console.log(result.yaml);
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
console.error("Usage: speclock policy <list|init|add|remove|evaluate|export>");
|
|
1015
|
+
process.exit(1);
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
// --- TELEMETRY (v3.5) ---
|
|
1019
|
+
if (cmd === "telemetry") {
|
|
1020
|
+
const sub = args[0];
|
|
1021
|
+
if (sub === "status" || !sub) {
|
|
1022
|
+
const enabled = isTelemetryEnabled();
|
|
1023
|
+
console.log(`\nTelemetry: ${enabled ? "ENABLED" : "DISABLED"}`);
|
|
1024
|
+
if (!enabled) {
|
|
1025
|
+
console.log("Set SPECLOCK_TELEMETRY=true to enable anonymous usage analytics.");
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
const summary = getTelemetrySummary(root);
|
|
1029
|
+
console.log(`Total calls: ${summary.totalCalls}`);
|
|
1030
|
+
console.log(`Avg response: ${summary.avgResponseMs}ms`);
|
|
1031
|
+
console.log(`Sessions: ${summary.sessions.total}`);
|
|
1032
|
+
console.log(`Conflicts: ${summary.conflicts.total} (blocked: ${summary.conflicts.blocked})`);
|
|
1033
|
+
if (summary.topTools.length > 0) {
|
|
1034
|
+
console.log(`\nTop tools:`);
|
|
1035
|
+
for (const t of summary.topTools.slice(0, 5)) {
|
|
1036
|
+
console.log(` ${t.name}: ${t.count} calls`);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
console.error("Usage: speclock telemetry [status]");
|
|
1042
|
+
process.exit(1);
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// --- SSO (v3.5) ---
|
|
1046
|
+
if (cmd === "sso") {
|
|
1047
|
+
const sub = args[0];
|
|
1048
|
+
if (sub === "status" || !sub) {
|
|
1049
|
+
const enabled = isSSOEnabled(root);
|
|
1050
|
+
const config = getSSOConfig(root);
|
|
1051
|
+
console.log(`\nSSO: ${enabled ? "CONFIGURED" : "NOT CONFIGURED"}`);
|
|
1052
|
+
if (enabled) {
|
|
1053
|
+
console.log(`Issuer: ${config.issuer}`);
|
|
1054
|
+
console.log(`Client ID: ${config.clientId}`);
|
|
1055
|
+
console.log(`Redirect: ${config.redirectUri}`);
|
|
1056
|
+
console.log(`Default role: ${config.defaultRole}`);
|
|
1057
|
+
} else {
|
|
1058
|
+
console.log("Set SPECLOCK_SSO_ISSUER and SPECLOCK_SSO_CLIENT_ID to enable SSO.");
|
|
1059
|
+
}
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
if (sub === "configure") {
|
|
1063
|
+
const flags = parseFlags(args.slice(1));
|
|
1064
|
+
const config = getSSOConfig(root);
|
|
1065
|
+
if (flags.issuer) config.issuer = flags.issuer;
|
|
1066
|
+
if (flags["client-id"]) config.clientId = flags["client-id"];
|
|
1067
|
+
if (flags["client-secret"]) config.clientSecret = flags["client-secret"];
|
|
1068
|
+
if (flags["redirect-uri"]) config.redirectUri = flags["redirect-uri"];
|
|
1069
|
+
if (flags["default-role"]) config.defaultRole = flags["default-role"];
|
|
1070
|
+
saveSSOConfig(root, config);
|
|
1071
|
+
console.log("SSO configuration saved.");
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
console.error("Usage: speclock sso <status|configure> [--issuer URL --client-id ID]");
|
|
1075
|
+
process.exit(1);
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
// --- ENCRYPT STATUS (v3.0) ---
|
|
1079
|
+
if (cmd === "encrypt") {
|
|
1080
|
+
const sub = args[0];
|
|
1081
|
+
if (sub === "status" || !sub) {
|
|
1082
|
+
const enabled = isEncryptionEnabled();
|
|
1083
|
+
console.log(`\nEncryption: ${enabled ? "ENABLED (AES-256-GCM)" : "DISABLED"}`);
|
|
1084
|
+
if (!enabled) {
|
|
1085
|
+
console.log("Set SPECLOCK_ENCRYPTION_KEY env var to enable encryption.");
|
|
1086
|
+
console.log("All data will be encrypted at rest (brain.json + events.log).");
|
|
1087
|
+
}
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
console.error("Usage: speclock encrypt [status]");
|
|
1091
|
+
process.exit(1);
|
|
1092
|
+
}
|
|
1093
|
+
|
|
781
1094
|
// --- STATUS ---
|
|
782
1095
|
if (cmd === "status") {
|
|
783
1096
|
showStatus(root);
|