scc-universal 1.1.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/.claude-plugin/plugin.json +44 -0
- package/.cursor/agents/deep-researcher.md +142 -0
- package/.cursor/agents/doc-updater.md +219 -0
- package/.cursor/agents/eval-runner.md +335 -0
- package/.cursor/agents/learning-engine.md +210 -0
- package/.cursor/agents/loop-operator.md +245 -0
- package/.cursor/agents/refactor-cleaner.md +119 -0
- package/.cursor/agents/sf-admin-agent.md +127 -0
- package/.cursor/agents/sf-agentforce-agent.md +126 -0
- package/.cursor/agents/sf-apex-agent.md +117 -0
- package/.cursor/agents/sf-architect.md +426 -0
- package/.cursor/agents/sf-aura-reviewer.md +369 -0
- package/.cursor/agents/sf-bugfix-agent.md +101 -0
- package/.cursor/agents/sf-flow-agent.md +155 -0
- package/.cursor/agents/sf-integration-agent.md +141 -0
- package/.cursor/agents/sf-lwc-agent.md +123 -0
- package/.cursor/agents/sf-review-agent.md +357 -0
- package/.cursor/agents/sf-visualforce-reviewer.md +465 -0
- package/.cursor/hooks/adapter.js +81 -0
- package/.cursor/hooks/after-file-edit.js +26 -0
- package/.cursor/hooks/after-mcp-execution.js +12 -0
- package/.cursor/hooks/after-shell-execution.js +30 -0
- package/.cursor/hooks/after-tab-file-edit.js +12 -0
- package/.cursor/hooks/before-mcp-execution.js +11 -0
- package/.cursor/hooks/before-read-file.js +13 -0
- package/.cursor/hooks/before-shell-execution.js +29 -0
- package/.cursor/hooks/before-submit-prompt.js +23 -0
- package/.cursor/hooks/pre-compact.js +7 -0
- package/.cursor/hooks/session-end.js +10 -0
- package/.cursor/hooks/session-start.js +10 -0
- package/.cursor/hooks/stop.js +18 -0
- package/.cursor/hooks/subagent-start.js +10 -0
- package/.cursor/hooks/subagent-stop.js +10 -0
- package/.cursor/hooks.json +107 -0
- package/.cursor/skills/aside/SKILL.md +115 -0
- package/.cursor/skills/checkpoint/SKILL.md +50 -0
- package/.cursor/skills/configure-scc/SKILL.md +160 -0
- package/.cursor/skills/continuous-agent-loop/SKILL.md +260 -0
- package/.cursor/skills/mcp-server-patterns/SKILL.md +142 -0
- package/.cursor/skills/model-route/SKILL.md +81 -0
- package/.cursor/skills/prompt-optimizer/SKILL.md +366 -0
- package/.cursor/skills/refactor-clean/SKILL.md +133 -0
- package/.cursor/skills/resume-session/SKILL.md +111 -0
- package/.cursor/skills/save-session/SKILL.md +183 -0
- package/.cursor/skills/search-first/SKILL.md +140 -0
- package/.cursor/skills/security-scan/SKILL.md +142 -0
- package/.cursor/skills/sessions/SKILL.md +124 -0
- package/.cursor/skills/sf-agentforce-development/SKILL.md +449 -0
- package/.cursor/skills/sf-apex-async-patterns/SKILL.md +324 -0
- package/.cursor/skills/sf-apex-best-practices/SKILL.md +421 -0
- package/.cursor/skills/sf-apex-constraints/SKILL.md +79 -0
- package/.cursor/skills/sf-apex-cursor/SKILL.md +336 -0
- package/.cursor/skills/sf-apex-enterprise-patterns/SKILL.md +344 -0
- package/.cursor/skills/sf-apex-testing/SKILL.md +407 -0
- package/.cursor/skills/sf-api-design/SKILL.md +237 -0
- package/.cursor/skills/sf-approval-processes/SKILL.md +312 -0
- package/.cursor/skills/sf-aura-development/SKILL.md +260 -0
- package/.cursor/skills/sf-build-fix/SKILL.md +120 -0
- package/.cursor/skills/sf-data-modeling/SKILL.md +274 -0
- package/.cursor/skills/sf-debugging/SKILL.md +362 -0
- package/.cursor/skills/sf-deployment/SKILL.md +291 -0
- package/.cursor/skills/sf-deployment-constraints/SKILL.md +153 -0
- package/.cursor/skills/sf-devops-ci-cd/SKILL.md +322 -0
- package/.cursor/skills/sf-docs-lookup/SKILL.md +100 -0
- package/.cursor/skills/sf-e2e-testing/SKILL.md +321 -0
- package/.cursor/skills/sf-experience-cloud/SKILL.md +248 -0
- package/.cursor/skills/sf-flow-development/SKILL.md +376 -0
- package/.cursor/skills/sf-governor-limits/SKILL.md +319 -0
- package/.cursor/skills/sf-harness-audit/SKILL.md +139 -0
- package/.cursor/skills/sf-help/SKILL.md +156 -0
- package/.cursor/skills/sf-integration/SKILL.md +479 -0
- package/.cursor/skills/sf-lwc-constraints/SKILL.md +128 -0
- package/.cursor/skills/sf-lwc-development/SKILL.md +302 -0
- package/.cursor/skills/sf-lwc-testing/SKILL.md +387 -0
- package/.cursor/skills/sf-metadata-management/SKILL.md +285 -0
- package/.cursor/skills/sf-platform-events-cdc/SKILL.md +372 -0
- package/.cursor/skills/sf-quickstart/SKILL.md +170 -0
- package/.cursor/skills/sf-security/SKILL.md +330 -0
- package/.cursor/skills/sf-security-constraints/SKILL.md +125 -0
- package/.cursor/skills/sf-soql-constraints/SKILL.md +129 -0
- package/.cursor/skills/sf-soql-optimization/SKILL.md +353 -0
- package/.cursor/skills/sf-tdd-workflow/SKILL.md +332 -0
- package/.cursor/skills/sf-testing-constraints/SKILL.md +198 -0
- package/.cursor/skills/sf-trigger-constraints/SKILL.md +88 -0
- package/.cursor/skills/sf-trigger-frameworks/SKILL.md +343 -0
- package/.cursor/skills/sf-visualforce-development/SKILL.md +259 -0
- package/.cursor/skills/strategic-compact/SKILL.md +205 -0
- package/.cursor/skills/update-docs/SKILL.md +162 -0
- package/.cursor/skills/update-platform-docs/SKILL.md +86 -0
- package/.cursor-plugin/plugin.json +26 -0
- package/LICENSE +21 -0
- package/README.md +522 -0
- package/agents/deep-researcher.md +145 -0
- package/agents/doc-updater.md +222 -0
- package/agents/eval-runner.md +340 -0
- package/agents/learning-engine.md +211 -0
- package/agents/loop-operator.md +247 -0
- package/agents/refactor-cleaner.md +122 -0
- package/agents/sf-admin-agent.md +131 -0
- package/agents/sf-agentforce-agent.md +132 -0
- package/agents/sf-apex-agent.md +124 -0
- package/agents/sf-architect.md +435 -0
- package/agents/sf-aura-reviewer.md +372 -0
- package/agents/sf-bugfix-agent.md +105 -0
- package/agents/sf-flow-agent.md +159 -0
- package/agents/sf-integration-agent.md +146 -0
- package/agents/sf-lwc-agent.md +127 -0
- package/agents/sf-review-agent.md +366 -0
- package/agents/sf-visualforce-reviewer.md +468 -0
- package/assets/logo.svg +18 -0
- package/docs/ARCHITECTURE.md +133 -0
- package/docs/authoring-guide.md +373 -0
- package/docs/hook-development.md +578 -0
- package/docs/token-optimization.md +139 -0
- package/docs/workflow-examples.md +645 -0
- package/examples/agentforce-action/README.md +227 -0
- package/examples/apex-trigger-handler/README.md +114 -0
- package/examples/devops-pipeline/README.md +325 -0
- package/examples/flow-automation/README.md +188 -0
- package/examples/integration-pattern/README.md +416 -0
- package/examples/lwc-component/README.md +180 -0
- package/examples/platform-events/README.md +492 -0
- package/examples/scratch-org-setup/README.md +138 -0
- package/examples/security-audit/README.md +244 -0
- package/examples/visualforce-migration/README.md +314 -0
- package/hooks/hooks.json +338 -0
- package/hooks/memory-persistence/README.md +73 -0
- package/manifests/install-modules.json +217 -0
- package/manifests/install-profiles.json +17 -0
- package/mcp-configs/mcp-servers.json +19 -0
- package/package.json +89 -0
- package/schemas/hooks.schema.json +123 -0
- package/schemas/install-modules.schema.json +76 -0
- package/schemas/install-profiles.schema.json +28 -0
- package/schemas/install-state.schema.json +73 -0
- package/schemas/package-manager.schema.json +18 -0
- package/schemas/plugin.schema.json +112 -0
- package/schemas/scc-install-config.schema.json +29 -0
- package/schemas/state-store.schema.json +111 -0
- package/scripts/cli/install-apply.js +170 -0
- package/scripts/cli/uninstall.js +193 -0
- package/scripts/hooks/check-console-log.js +101 -0
- package/scripts/hooks/check-hook-enabled.js +17 -0
- package/scripts/hooks/check-platform-docs-age.js +48 -0
- package/scripts/hooks/cost-tracker.js +78 -0
- package/scripts/hooks/doc-file-warning.js +63 -0
- package/scripts/hooks/evaluate-session.js +98 -0
- package/scripts/hooks/governor-check.js +220 -0
- package/scripts/hooks/learning-observe.sh +206 -0
- package/scripts/hooks/mcp-health-check.js +588 -0
- package/scripts/hooks/post-bash-build-complete.js +34 -0
- package/scripts/hooks/post-bash-pr-created.js +43 -0
- package/scripts/hooks/post-edit-console-warn.js +61 -0
- package/scripts/hooks/post-edit-format.js +79 -0
- package/scripts/hooks/post-edit-typecheck.js +98 -0
- package/scripts/hooks/post-write.js +168 -0
- package/scripts/hooks/pre-bash-git-push-reminder.js +35 -0
- package/scripts/hooks/pre-bash-tmux-reminder.js +47 -0
- package/scripts/hooks/pre-compact.js +51 -0
- package/scripts/hooks/pre-tool-use.js +163 -0
- package/scripts/hooks/pre-write-doc-warn.js +9 -0
- package/scripts/hooks/quality-gate.js +251 -0
- package/scripts/hooks/run-with-flags-shell.sh +32 -0
- package/scripts/hooks/run-with-flags.js +135 -0
- package/scripts/hooks/session-end-marker.js +29 -0
- package/scripts/hooks/session-end.js +311 -0
- package/scripts/hooks/session-start.js +202 -0
- package/scripts/hooks/sfdx-scanner-check.js +142 -0
- package/scripts/hooks/sfdx-validate.js +119 -0
- package/scripts/hooks/stop-hook.js +170 -0
- package/scripts/hooks/suggest-compact.js +67 -0
- package/scripts/lib/agent-adapter.js +82 -0
- package/scripts/lib/apex-analysis.js +194 -0
- package/scripts/lib/hook-flags.js +74 -0
- package/scripts/lib/install-config.js +73 -0
- package/scripts/lib/install-executor.js +363 -0
- package/scripts/lib/install-state.js +121 -0
- package/scripts/lib/orchestration-session.js +299 -0
- package/scripts/lib/package-manager.js +124 -0
- package/scripts/lib/project-detect.js +228 -0
- package/scripts/lib/schema-validator.js +190 -0
- package/scripts/lib/skill-adapter.js +100 -0
- package/scripts/lib/state-store.js +376 -0
- package/scripts/lib/tmux-worktree-orchestrator.js +598 -0
- package/scripts/lib/utils.js +313 -0
- package/scripts/scc.js +164 -0
- package/skills/_reference/AGENTFORCE_PATTERNS.md +112 -0
- package/skills/_reference/APEX_CURSOR.md +159 -0
- package/skills/_reference/API_VERSIONS.md +78 -0
- package/skills/_reference/APPROVAL_PROCESSES.md +105 -0
- package/skills/_reference/ASYNC_PATTERNS.md +163 -0
- package/skills/_reference/AURA_COMPONENTS.md +146 -0
- package/skills/_reference/DATA_MIGRATION_PATTERNS.md +151 -0
- package/skills/_reference/DATA_MODELING.md +124 -0
- package/skills/_reference/DEBUGGING_TOOLS.md +140 -0
- package/skills/_reference/DEPLOYMENT_CHECKLIST.md +87 -0
- package/skills/_reference/DEPRECATIONS.md +79 -0
- package/skills/_reference/DOCKER_CI_PATTERNS.md +138 -0
- package/skills/_reference/ENTERPRISE_PATTERNS.md +122 -0
- package/skills/_reference/EXPERIENCE_CLOUD.md +143 -0
- package/skills/_reference/FLOW_PATTERNS.md +113 -0
- package/skills/_reference/GOVERNOR_LIMITS.md +77 -0
- package/skills/_reference/INTEGRATION_PATTERNS.md +105 -0
- package/skills/_reference/LWC_PATTERNS.md +79 -0
- package/skills/_reference/METADATA_TYPES.md +115 -0
- package/skills/_reference/NAMING_CONVENTIONS.md +84 -0
- package/skills/_reference/PACKAGE_DEVELOPMENT.md +150 -0
- package/skills/_reference/PLATFORM_EVENTS.md +121 -0
- package/skills/_reference/REPORTING_API.md +143 -0
- package/skills/_reference/SCRATCH_ORG_PATTERNS.md +126 -0
- package/skills/_reference/SECURITY_PATTERNS.md +127 -0
- package/skills/_reference/SHARING_MODEL.md +120 -0
- package/skills/_reference/SOQL_PATTERNS.md +119 -0
- package/skills/_reference/TESTING_STANDARDS.md +96 -0
- package/skills/_reference/TRIGGER_PATTERNS.md +114 -0
- package/skills/_reference/VISUALFORCE_PATTERNS.md +121 -0
- package/skills/aside/SKILL.md +118 -0
- package/skills/checkpoint/SKILL.md +53 -0
- package/skills/configure-scc/SKILL.md +163 -0
- package/skills/continuous-agent-loop/SKILL.md +264 -0
- package/skills/mcp-server-patterns/SKILL.md +146 -0
- package/skills/model-route/SKILL.md +84 -0
- package/skills/prompt-optimizer/SKILL.md +369 -0
- package/skills/refactor-clean/SKILL.md +136 -0
- package/skills/resume-session/SKILL.md +114 -0
- package/skills/save-session/SKILL.md +186 -0
- package/skills/search-first/SKILL.md +144 -0
- package/skills/security-scan/SKILL.md +146 -0
- package/skills/sessions/SKILL.md +127 -0
- package/skills/sf-agentforce-development/SKILL.md +450 -0
- package/skills/sf-apex-async-patterns/SKILL.md +326 -0
- package/skills/sf-apex-best-practices/SKILL.md +425 -0
- package/skills/sf-apex-constraints/SKILL.md +81 -0
- package/skills/sf-apex-cursor/SKILL.md +338 -0
- package/skills/sf-apex-enterprise-patterns/SKILL.md +348 -0
- package/skills/sf-apex-testing/SKILL.md +409 -0
- package/skills/sf-api-design/SKILL.md +238 -0
- package/skills/sf-approval-processes/SKILL.md +315 -0
- package/skills/sf-aura-development/SKILL.md +263 -0
- package/skills/sf-build-fix/SKILL.md +121 -0
- package/skills/sf-data-modeling/SKILL.md +278 -0
- package/skills/sf-debugging/SKILL.md +363 -0
- package/skills/sf-deployment/SKILL.md +295 -0
- package/skills/sf-deployment-constraints/SKILL.md +155 -0
- package/skills/sf-devops-ci-cd/SKILL.md +325 -0
- package/skills/sf-docs-lookup/SKILL.md +103 -0
- package/skills/sf-e2e-testing/SKILL.md +324 -0
- package/skills/sf-experience-cloud/SKILL.md +249 -0
- package/skills/sf-flow-development/SKILL.md +377 -0
- package/skills/sf-governor-limits/SKILL.md +323 -0
- package/skills/sf-harness-audit/SKILL.md +142 -0
- package/skills/sf-help/SKILL.md +159 -0
- package/skills/sf-integration/SKILL.md +483 -0
- package/skills/sf-lwc-constraints/SKILL.md +130 -0
- package/skills/sf-lwc-development/SKILL.md +303 -0
- package/skills/sf-lwc-testing/SKILL.md +388 -0
- package/skills/sf-metadata-management/SKILL.md +288 -0
- package/skills/sf-platform-events-cdc/SKILL.md +375 -0
- package/skills/sf-quickstart/SKILL.md +173 -0
- package/skills/sf-security/SKILL.md +334 -0
- package/skills/sf-security-constraints/SKILL.md +127 -0
- package/skills/sf-soql-constraints/SKILL.md +131 -0
- package/skills/sf-soql-optimization/SKILL.md +354 -0
- package/skills/sf-tdd-workflow/SKILL.md +336 -0
- package/skills/sf-testing-constraints/SKILL.md +200 -0
- package/skills/sf-trigger-constraints/SKILL.md +90 -0
- package/skills/sf-trigger-frameworks/SKILL.md +347 -0
- package/skills/sf-visualforce-development/SKILL.md +260 -0
- package/skills/strategic-compact/SKILL.md +208 -0
- package/skills/update-docs/SKILL.md +165 -0
- package/skills/update-platform-docs/SKILL.md +90 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* schema-validator.js — Shared AJV schema validation for SCC.
|
|
5
|
+
*
|
|
6
|
+
* - Graceful AJV import with fallback when not installed
|
|
7
|
+
* - Cached schema, AJV instance, and compiled validators
|
|
8
|
+
* - Consistent error formatting: `${instancePath || '/'} ${message}`
|
|
9
|
+
* - Entity validation against $defs-based schemas
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
// Graceful AJV import — fallback when not installed (bare environments)
|
|
16
|
+
let Ajv = null;
|
|
17
|
+
try {
|
|
18
|
+
const ajvModule = require('ajv');
|
|
19
|
+
Ajv = ajvModule.default || ajvModule;
|
|
20
|
+
} catch (_error) {
|
|
21
|
+
Ajv = null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Caches
|
|
25
|
+
let cachedAjv = null;
|
|
26
|
+
const cachedSchemas = new Map();
|
|
27
|
+
const cachedValidators = new Map();
|
|
28
|
+
|
|
29
|
+
function getAjv() {
|
|
30
|
+
if (cachedAjv) return cachedAjv;
|
|
31
|
+
if (!Ajv) return null;
|
|
32
|
+
cachedAjv = new Ajv({ allErrors: true, strict: false });
|
|
33
|
+
return cachedAjv;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function readSchema(schemaPath) {
|
|
37
|
+
const resolved = path.resolve(schemaPath);
|
|
38
|
+
if (cachedSchemas.has(resolved)) return cachedSchemas.get(resolved);
|
|
39
|
+
const schema = JSON.parse(fs.readFileSync(resolved, 'utf8'));
|
|
40
|
+
cachedSchemas.set(resolved, schema);
|
|
41
|
+
return schema;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function getValidator(schemaPath) {
|
|
45
|
+
const resolved = path.resolve(schemaPath);
|
|
46
|
+
if (cachedValidators.has(resolved)) return cachedValidators.get(resolved);
|
|
47
|
+
|
|
48
|
+
const ajv = getAjv();
|
|
49
|
+
if (!ajv) return createFallbackValidator();
|
|
50
|
+
|
|
51
|
+
const schema = readSchema(resolved);
|
|
52
|
+
const validator = ajv.compile(schema);
|
|
53
|
+
cachedValidators.set(resolved, validator);
|
|
54
|
+
return validator;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getEntityValidator(schemaPath, entityName) {
|
|
58
|
+
const cacheKey = `${path.resolve(schemaPath)}#${entityName}`;
|
|
59
|
+
if (cachedValidators.has(cacheKey)) return cachedValidators.get(cacheKey);
|
|
60
|
+
|
|
61
|
+
const ajv = getAjv();
|
|
62
|
+
if (!ajv) return createFallbackValidator();
|
|
63
|
+
|
|
64
|
+
const schema = readSchema(schemaPath);
|
|
65
|
+
if (!schema.$defs || !schema.$defs[entityName]) {
|
|
66
|
+
throw new Error(`Unknown schema entity: ${entityName} in ${schemaPath}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const entitySchema = {
|
|
70
|
+
$schema: schema.$schema,
|
|
71
|
+
...schema.$defs[entityName],
|
|
72
|
+
$defs: schema.$defs,
|
|
73
|
+
};
|
|
74
|
+
const validator = ajv.compile(entitySchema);
|
|
75
|
+
cachedValidators.set(cacheKey, validator);
|
|
76
|
+
return validator;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Fallback validator when AJV is not installed.
|
|
81
|
+
* Returns a validator function with same interface: validate(data) → boolean, validate.errors → array.
|
|
82
|
+
*/
|
|
83
|
+
function createFallbackValidator() {
|
|
84
|
+
const validate = (data) => {
|
|
85
|
+
const errors = [];
|
|
86
|
+
validate.errors = errors;
|
|
87
|
+
|
|
88
|
+
if (data === null || data === undefined) {
|
|
89
|
+
errors.push({ instancePath: '/', message: 'must not be null or undefined' });
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
if (typeof data !== 'object' || Array.isArray(data)) {
|
|
93
|
+
errors.push({ instancePath: '/', message: 'must be object' });
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
return true;
|
|
97
|
+
};
|
|
98
|
+
validate.errors = [];
|
|
99
|
+
return validate;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Format AJV errors into a readable string.
|
|
104
|
+
*/
|
|
105
|
+
function formatErrors(errors) {
|
|
106
|
+
if (!errors || errors.length === 0) return '';
|
|
107
|
+
return errors
|
|
108
|
+
.map(e => `${e.instancePath || '/'} ${e.message}`)
|
|
109
|
+
.join('; ');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ── Public API ───────────────────────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Validate data against a JSON schema file.
|
|
116
|
+
* @param {string} schemaPath - path to JSON schema file
|
|
117
|
+
* @param {*} data - data to validate
|
|
118
|
+
* @returns {{ valid: boolean, errors: Array }}
|
|
119
|
+
*/
|
|
120
|
+
function validateAgainstSchema(schemaPath, data) {
|
|
121
|
+
const validator = getValidator(schemaPath);
|
|
122
|
+
const valid = validator(data);
|
|
123
|
+
return { valid, errors: validator.errors || [] };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Validate data against a schema, throw on invalid.
|
|
128
|
+
* @param {string} schemaPath - path to JSON schema file
|
|
129
|
+
* @param {*} data - data to validate
|
|
130
|
+
* @param {string} [label] - label for error message
|
|
131
|
+
*/
|
|
132
|
+
function assertAgainstSchema(schemaPath, data, label) {
|
|
133
|
+
const result = validateAgainstSchema(schemaPath, data);
|
|
134
|
+
if (!result.valid) {
|
|
135
|
+
throw new Error(`Invalid${label ? ` ${label}` : ''}: ${formatErrors(result.errors)}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Validate data against a named entity definition ($defs) in a schema.
|
|
141
|
+
* @param {string} schemaPath - path to JSON schema file
|
|
142
|
+
* @param {string} entityName - name of the entity in $defs
|
|
143
|
+
* @param {*} data - data to validate
|
|
144
|
+
* @returns {{ valid: boolean, errors: Array }}
|
|
145
|
+
*/
|
|
146
|
+
function validateEntity(schemaPath, entityName, data) {
|
|
147
|
+
const validator = getEntityValidator(schemaPath, entityName);
|
|
148
|
+
const valid = validator(data);
|
|
149
|
+
return { valid, errors: validator.errors || [] };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Validate entity against a schema, throw on invalid.
|
|
154
|
+
* @param {string} schemaPath - path to JSON schema file
|
|
155
|
+
* @param {string} entityName - name of the entity in $defs
|
|
156
|
+
* @param {*} data - data to validate
|
|
157
|
+
* @param {string} [label] - label for error message
|
|
158
|
+
*/
|
|
159
|
+
function assertValidEntity(schemaPath, entityName, data, label) {
|
|
160
|
+
const result = validateEntity(schemaPath, entityName, data);
|
|
161
|
+
if (!result.valid) {
|
|
162
|
+
throw new Error(`Invalid ${entityName}${label ? ` (${label})` : ''}: ${formatErrors(result.errors)}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Check if AJV is available.
|
|
168
|
+
*/
|
|
169
|
+
function hasAjv() {
|
|
170
|
+
return Ajv !== null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Clear all caches (useful for testing).
|
|
175
|
+
*/
|
|
176
|
+
function clearCaches() {
|
|
177
|
+
cachedAjv = null;
|
|
178
|
+
cachedSchemas.clear();
|
|
179
|
+
cachedValidators.clear();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
module.exports = {
|
|
183
|
+
validateAgainstSchema,
|
|
184
|
+
assertAgainstSchema,
|
|
185
|
+
validateEntity,
|
|
186
|
+
assertValidEntity,
|
|
187
|
+
formatErrors,
|
|
188
|
+
hasAjv,
|
|
189
|
+
clearCaches,
|
|
190
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* skill-adapter.js — Transforms SCC skills from Claude Code format to Cursor format.
|
|
5
|
+
*
|
|
6
|
+
* Claude Code skills use: name, description, origin, user-invocable, allowed-tools, context, etc.
|
|
7
|
+
* Cursor skills use: name, description, disable-model-invocation, license, compatibility, metadata.
|
|
8
|
+
*
|
|
9
|
+
* This adapter strips Claude-only fields, maps user-invocable to disable-model-invocation,
|
|
10
|
+
* and outputs clean Cursor-compatible SKILL.md files.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const { parseFrontmatter, serializeFrontmatter, ensureDir, copyFile } = require('./utils');
|
|
16
|
+
|
|
17
|
+
// Fields that Cursor recognizes in SKILL.md frontmatter
|
|
18
|
+
const CURSOR_ALLOWED_FIELDS = new Set([
|
|
19
|
+
'name',
|
|
20
|
+
'description',
|
|
21
|
+
'disable-model-invocation',
|
|
22
|
+
'license',
|
|
23
|
+
'compatibility',
|
|
24
|
+
'metadata',
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Transform a single SKILL.md content string from Claude Code to Cursor format.
|
|
29
|
+
* @param {string} content - raw SKILL.md content
|
|
30
|
+
* @returns {string} - transformed SKILL.md content for Cursor
|
|
31
|
+
*/
|
|
32
|
+
function transformSkill(content) {
|
|
33
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
34
|
+
|
|
35
|
+
const cursorFm = {};
|
|
36
|
+
|
|
37
|
+
// Copy allowed fields
|
|
38
|
+
for (const key of Object.keys(frontmatter)) {
|
|
39
|
+
if (CURSOR_ALLOWED_FIELDS.has(key)) {
|
|
40
|
+
cursorFm[key] = frontmatter[key];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// EXample for future Map user-invocable → disable-model-invocation
|
|
45
|
+
/*if (frontmatter['user-invocable'] !== undefined) {
|
|
46
|
+
const isUserInvocable = String(frontmatter['user-invocable']).toLowerCase() === 'true';
|
|
47
|
+
if (isUserInvocable) {
|
|
48
|
+
cursorFm['disable-model-invocation'] = false;
|
|
49
|
+
}
|
|
50
|
+
// When user-invocable is false (auto-only), omit disable-model-invocation
|
|
51
|
+
// so Cursor defaults to model-can-invoke behavior
|
|
52
|
+
}*/
|
|
53
|
+
|
|
54
|
+
return serializeFrontmatter(cursorFm, body);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Transform and copy an entire skill directory.
|
|
59
|
+
* Transforms SKILL.md frontmatter; copies all other files as-is.
|
|
60
|
+
* @param {string} srcDir - source skill directory (e.g., skills/sf-help/)
|
|
61
|
+
* @param {string} destDir - destination directory (e.g., .cursor/skills/sf-help/)
|
|
62
|
+
*/
|
|
63
|
+
function transformSkillDir(srcDir, destDir) {
|
|
64
|
+
if (!fs.existsSync(srcDir)) {
|
|
65
|
+
throw new Error(`Source skill directory not found: ${srcDir}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
ensureDir(destDir);
|
|
69
|
+
copyDirRecursive(srcDir, destDir);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Recursively copy a directory, transforming SKILL.md files.
|
|
74
|
+
* @param {string} src - source directory
|
|
75
|
+
* @param {string} dest - destination directory
|
|
76
|
+
*/
|
|
77
|
+
function copyDirRecursive(src, dest) {
|
|
78
|
+
ensureDir(dest);
|
|
79
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
80
|
+
|
|
81
|
+
for (const entry of entries) {
|
|
82
|
+
const srcPath = path.join(src, entry.name);
|
|
83
|
+
const destPath = path.join(dest, entry.name);
|
|
84
|
+
|
|
85
|
+
if (entry.isDirectory()) {
|
|
86
|
+
copyDirRecursive(srcPath, destPath);
|
|
87
|
+
} else if (entry.name === 'SKILL.md') {
|
|
88
|
+
// Transform SKILL.md frontmatter
|
|
89
|
+
const content = fs.readFileSync(srcPath, 'utf8');
|
|
90
|
+
const transformed = transformSkill(content);
|
|
91
|
+
ensureDir(path.dirname(destPath));
|
|
92
|
+
fs.writeFileSync(destPath, transformed, 'utf8');
|
|
93
|
+
} else {
|
|
94
|
+
// Copy other files as-is
|
|
95
|
+
copyFile(srcPath, destPath);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
module.exports = { transformSkill, transformSkillDir, CURSOR_ALLOWED_FIELDS };
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* state-store.js — Schema-validated JSON state store for SCC.
|
|
5
|
+
*
|
|
6
|
+
* Persists to ~/.scc/state.json with 6 entity types (validated against state-store.schema.json):
|
|
7
|
+
* - sessions: AI session tracking
|
|
8
|
+
* - skillRuns: Skill execution history
|
|
9
|
+
* - skillVersions: Skill version tracking
|
|
10
|
+
* - decisions: Decision records
|
|
11
|
+
* - installState: Installation tracking (replaces legacy installedFiles)
|
|
12
|
+
* - governanceEvents: Governor limit/security events
|
|
13
|
+
*
|
|
14
|
+
* Backward-compatible: saveState/loadState/clearState/removeFiles still work for existing callers.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const os = require('os');
|
|
20
|
+
const { assertValidEntity } = require('./schema-validator');
|
|
21
|
+
|
|
22
|
+
const STATE_DIR = path.join(os.homedir(), '.scc');
|
|
23
|
+
const STATE_PATH = path.join(STATE_DIR, 'state.json');
|
|
24
|
+
const SCHEMA_PATH = path.join(__dirname, '..', '..', 'schemas', 'state-store.schema.json');
|
|
25
|
+
|
|
26
|
+
// Entity collection names (must match schema properties)
|
|
27
|
+
const ENTITY_COLLECTIONS = ['sessions', 'skillRuns', 'skillVersions', 'decisions', 'installState', 'governanceEvents'];
|
|
28
|
+
|
|
29
|
+
function ensureStateDir() {
|
|
30
|
+
fs.mkdirSync(STATE_DIR, { recursive: true });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Create an empty state object with all entity collections.
|
|
35
|
+
*/
|
|
36
|
+
function emptyState() {
|
|
37
|
+
const state = {};
|
|
38
|
+
for (const col of ENTITY_COLLECTIONS) {
|
|
39
|
+
state[col] = [];
|
|
40
|
+
}
|
|
41
|
+
return state;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Load raw JSON state from disk.
|
|
46
|
+
*/
|
|
47
|
+
function loadJsonState() {
|
|
48
|
+
try {
|
|
49
|
+
const raw = fs.readFileSync(STATE_PATH, 'utf8');
|
|
50
|
+
const parsed = JSON.parse(raw);
|
|
51
|
+
|
|
52
|
+
// Detect and migrate legacy format
|
|
53
|
+
if (parsed.installedFiles || parsed.lastProfile) {
|
|
54
|
+
return migrateLegacyState(parsed);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Ensure all collections exist
|
|
58
|
+
const state = emptyState();
|
|
59
|
+
for (const col of ENTITY_COLLECTIONS) {
|
|
60
|
+
if (Array.isArray(parsed[col])) {
|
|
61
|
+
state[col] = parsed[col];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return state;
|
|
65
|
+
} catch {
|
|
66
|
+
return emptyState();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Migrate legacy state format (installedFiles/lastProfile) to entity model.
|
|
72
|
+
*/
|
|
73
|
+
function migrateLegacyState(legacy) {
|
|
74
|
+
const state = emptyState();
|
|
75
|
+
|
|
76
|
+
// Legacy state migration: v1.0.0 used fine-grained module IDs (rules-apex, agents-lwc, etc.)
|
|
77
|
+
// v2.0.0 uses 7 bundle IDs (core, apex, lwc, platform, devops, security, extended).
|
|
78
|
+
const LEGACY_PREFIXES = ['rules-', 'agents-', 'commands-', 'skills-', 'hooks-', 'platform-'];
|
|
79
|
+
const hasLegacyModules = (legacy.installedFiles || []).some(f =>
|
|
80
|
+
f.module && LEGACY_PREFIXES.some(prefix => f.module.startsWith(prefix))
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// If legacy module IDs detected, wipe and return empty
|
|
84
|
+
if (hasLegacyModules) {
|
|
85
|
+
saveJsonState(state);
|
|
86
|
+
return state;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Migrate installedFiles to installState entities (group by target)
|
|
90
|
+
if (Array.isArray(legacy.installedFiles) && legacy.installedFiles.length > 0) {
|
|
91
|
+
const byTarget = new Map();
|
|
92
|
+
for (const f of legacy.installedFiles) {
|
|
93
|
+
const target = f.target || legacy.lastTarget || 'claude';
|
|
94
|
+
if (!byTarget.has(target)) byTarget.set(target, []);
|
|
95
|
+
byTarget.get(target).push(f);
|
|
96
|
+
}
|
|
97
|
+
for (const [target, files] of byTarget) {
|
|
98
|
+
state.installState.push({
|
|
99
|
+
targetId: target,
|
|
100
|
+
targetRoot: process.cwd(),
|
|
101
|
+
profile: legacy.lastProfile || null,
|
|
102
|
+
modules: [],
|
|
103
|
+
operations: files.map(f => ({
|
|
104
|
+
kind: 'copy',
|
|
105
|
+
moduleId: f.module || '',
|
|
106
|
+
sourceRelativePath: f.srcPath || '',
|
|
107
|
+
destinationPath: f.destPath || '',
|
|
108
|
+
strategy: 'overwrite',
|
|
109
|
+
ownership: 'scc',
|
|
110
|
+
scaffoldOnly: false,
|
|
111
|
+
hash: f.hash || null,
|
|
112
|
+
})),
|
|
113
|
+
installedAt: legacy.lastInstalledAt || new Date().toISOString(),
|
|
114
|
+
sourceVersion: null,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Migrate legacy sessions
|
|
120
|
+
if (Array.isArray(legacy.sessions)) {
|
|
121
|
+
for (const s of legacy.sessions) {
|
|
122
|
+
state.sessions.push({
|
|
123
|
+
id: `legacy-${s.installedAt || Date.now()}`,
|
|
124
|
+
adapterId: 'scc-install',
|
|
125
|
+
harness: s.target || 'claude',
|
|
126
|
+
state: 'completed',
|
|
127
|
+
repoRoot: null,
|
|
128
|
+
startedAt: s.installedAt || null,
|
|
129
|
+
endedAt: s.installedAt || null,
|
|
130
|
+
snapshot: { profile: s.profile, fileCount: s.fileCount },
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Write migrated state
|
|
136
|
+
saveJsonState(state);
|
|
137
|
+
return state;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function saveJsonState(state) {
|
|
141
|
+
ensureStateDir();
|
|
142
|
+
fs.writeFileSync(STATE_PATH, JSON.stringify(state, null, 2), 'utf8');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ── Entity Operations (schema-validated) ────────────────────────────────────
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Upsert an entity into a collection. Validates against schema before saving.
|
|
149
|
+
* If entity with same `id` exists, it's replaced.
|
|
150
|
+
*/
|
|
151
|
+
function upsertEntity(collectionName, entityName, entity) {
|
|
152
|
+
assertValidEntity(SCHEMA_PATH, entityName, entity);
|
|
153
|
+
|
|
154
|
+
const state = loadJsonState();
|
|
155
|
+
if (!Array.isArray(state[collectionName])) {
|
|
156
|
+
state[collectionName] = [];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Replace by id if exists, otherwise append
|
|
160
|
+
const idField = entity.id ? 'id' : (entity.skillId ? 'skillId' : null);
|
|
161
|
+
if (idField && entity[idField]) {
|
|
162
|
+
const idx = state[collectionName].findIndex(e => e[idField] === entity[idField]);
|
|
163
|
+
if (idx >= 0) {
|
|
164
|
+
state[collectionName][idx] = entity;
|
|
165
|
+
} else {
|
|
166
|
+
state[collectionName].push(entity);
|
|
167
|
+
}
|
|
168
|
+
} else {
|
|
169
|
+
state[collectionName].push(entity);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
saveJsonState(state);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function upsertSession(entity) {
|
|
176
|
+
upsertEntity('sessions', 'session', entity);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function upsertSkillRun(entity) {
|
|
180
|
+
upsertEntity('skillRuns', 'skillRun', entity);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function upsertSkillVersion(entity) {
|
|
184
|
+
upsertEntity('skillVersions', 'skillVersion', entity);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function upsertDecision(entity) {
|
|
188
|
+
upsertEntity('decisions', 'decision', entity);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function upsertGovernanceEvent(entity) {
|
|
192
|
+
upsertEntity('governanceEvents', 'governanceEvent', entity);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ── Query Helpers ───────────────────────────────────────────────────────────
|
|
196
|
+
|
|
197
|
+
function listSessions() {
|
|
198
|
+
return loadJsonState().sessions;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function listSkillRuns(sessionId) {
|
|
202
|
+
const runs = loadJsonState().skillRuns;
|
|
203
|
+
return sessionId ? runs.filter(r => r.sessionId === sessionId) : runs;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function listDecisions(sessionId) {
|
|
207
|
+
const decisions = loadJsonState().decisions;
|
|
208
|
+
return sessionId ? decisions.filter(d => d.sessionId === sessionId) : decisions;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function listGovernanceEvents(sessionId) {
|
|
212
|
+
const events = loadJsonState().governanceEvents;
|
|
213
|
+
return sessionId ? events.filter(e => e.sessionId === sessionId) : events;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Collect all operations across all installState entities into flat installedFiles format.
|
|
218
|
+
*/
|
|
219
|
+
function allOperations(state) {
|
|
220
|
+
const files = [];
|
|
221
|
+
for (const install of state.installState) {
|
|
222
|
+
for (const op of install.operations) {
|
|
223
|
+
files.push({
|
|
224
|
+
destPath: op.destinationPath,
|
|
225
|
+
srcPath: op.sourceRelativePath,
|
|
226
|
+
module: op.moduleId,
|
|
227
|
+
hash: op.hash || null,
|
|
228
|
+
installedAt: install.installedAt,
|
|
229
|
+
profile: install.profile,
|
|
230
|
+
target: install.targetId,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return files;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ── Backward-Compatible API ─────────────────────────────────────────────────
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Save installation state (backward-compatible).
|
|
241
|
+
* Translates old format to installState entity.
|
|
242
|
+
*/
|
|
243
|
+
function saveState(state) {
|
|
244
|
+
ensureStateDir();
|
|
245
|
+
const now = state.installedAt || new Date().toISOString();
|
|
246
|
+
const current = loadJsonState();
|
|
247
|
+
|
|
248
|
+
// Create installState entity from legacy format
|
|
249
|
+
const installEntity = {
|
|
250
|
+
targetId: state.target || 'claude',
|
|
251
|
+
targetRoot: process.cwd(),
|
|
252
|
+
profile: state.profile || null,
|
|
253
|
+
modules: [],
|
|
254
|
+
operations: (state.installedFiles || []).map(f => ({
|
|
255
|
+
kind: 'copy',
|
|
256
|
+
moduleId: f.module || '',
|
|
257
|
+
sourceRelativePath: f.srcPath || '',
|
|
258
|
+
destinationPath: f.destPath || '',
|
|
259
|
+
strategy: 'overwrite',
|
|
260
|
+
ownership: 'scc',
|
|
261
|
+
scaffoldOnly: false,
|
|
262
|
+
hash: f.hash || null,
|
|
263
|
+
})),
|
|
264
|
+
installedAt: now,
|
|
265
|
+
sourceVersion: null,
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
// Merge operations: replace entries with same destinationPath
|
|
269
|
+
const existingInstall = current.installState[current.installState.length - 1];
|
|
270
|
+
if (existingInstall) {
|
|
271
|
+
const existingOps = new Map(existingInstall.operations.map(op => [op.destinationPath, op]));
|
|
272
|
+
for (const op of installEntity.operations) {
|
|
273
|
+
existingOps.set(op.destinationPath, op);
|
|
274
|
+
}
|
|
275
|
+
installEntity.operations = Array.from(existingOps.values());
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Replace or append installState
|
|
279
|
+
if (current.installState.length > 0) {
|
|
280
|
+
current.installState[current.installState.length - 1] = installEntity;
|
|
281
|
+
} else {
|
|
282
|
+
current.installState.push(installEntity);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Record session
|
|
286
|
+
current.sessions.push({
|
|
287
|
+
id: `install-${now}`,
|
|
288
|
+
adapterId: 'scc-install',
|
|
289
|
+
harness: state.target || 'claude',
|
|
290
|
+
state: 'completed',
|
|
291
|
+
repoRoot: null,
|
|
292
|
+
startedAt: now,
|
|
293
|
+
endedAt: now,
|
|
294
|
+
snapshot: { profile: state.profile, fileCount: (state.installedFiles || []).length },
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
saveJsonState(current);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Load current installation state (backward-compatible).
|
|
302
|
+
* Returns format expected by existing callers.
|
|
303
|
+
*/
|
|
304
|
+
function loadState() {
|
|
305
|
+
const state = loadJsonState();
|
|
306
|
+
const latest = state.installState[state.installState.length - 1];
|
|
307
|
+
|
|
308
|
+
// Legacy module ID detection in entity format
|
|
309
|
+
const LEGACY_PREFIXES = ['rules-', 'agents-', 'commands-', 'skills-', 'hooks-', 'platform-'];
|
|
310
|
+
if (latest) {
|
|
311
|
+
const hasLegacy = latest.operations.some(op =>
|
|
312
|
+
op.moduleId && LEGACY_PREFIXES.some(prefix => op.moduleId.startsWith(prefix))
|
|
313
|
+
);
|
|
314
|
+
if (hasLegacy) {
|
|
315
|
+
clearState();
|
|
316
|
+
return { profile: null, target: null, installedAt: null, installedFiles: [], sessions: [] };
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return {
|
|
321
|
+
profile: latest ? latest.profile : null,
|
|
322
|
+
target: latest ? latest.targetId : null,
|
|
323
|
+
installedAt: latest ? latest.installedAt : null,
|
|
324
|
+
installedFiles: allOperations(state),
|
|
325
|
+
sessions: state.sessions.map(s => ({
|
|
326
|
+
profile: s.snapshot?.profile || null,
|
|
327
|
+
target: s.harness,
|
|
328
|
+
fileCount: s.snapshot?.fileCount || 0,
|
|
329
|
+
installedAt: s.startedAt,
|
|
330
|
+
})),
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Clear all SCC state.
|
|
336
|
+
*/
|
|
337
|
+
function clearState() {
|
|
338
|
+
try { fs.unlinkSync(STATE_PATH); } catch { /* ignore */ }
|
|
339
|
+
// Also clean up legacy DB file if it exists
|
|
340
|
+
const legacyDbPath = path.join(STATE_DIR, 'state.db');
|
|
341
|
+
try { fs.unlinkSync(legacyDbPath); } catch { /* ignore */ }
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Remove specific files from the state store.
|
|
346
|
+
* @param {string[]} destPaths
|
|
347
|
+
*/
|
|
348
|
+
function removeFiles(destPaths) {
|
|
349
|
+
const state = loadJsonState();
|
|
350
|
+
const removeSet = new Set(destPaths);
|
|
351
|
+
|
|
352
|
+
for (const install of state.installState) {
|
|
353
|
+
install.operations = install.operations.filter(op => !removeSet.has(op.destinationPath));
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
saveJsonState(state);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
module.exports = {
|
|
360
|
+
// Backward-compatible API
|
|
361
|
+
saveState,
|
|
362
|
+
loadState,
|
|
363
|
+
clearState,
|
|
364
|
+
removeFiles,
|
|
365
|
+
// Entity operations (schema-validated)
|
|
366
|
+
upsertSession,
|
|
367
|
+
upsertSkillRun,
|
|
368
|
+
upsertSkillVersion,
|
|
369
|
+
upsertDecision,
|
|
370
|
+
upsertGovernanceEvent,
|
|
371
|
+
// Query helpers
|
|
372
|
+
listSessions,
|
|
373
|
+
listSkillRuns,
|
|
374
|
+
listDecisions,
|
|
375
|
+
listGovernanceEvents,
|
|
376
|
+
};
|