@awak-app/simy-cli 0.2.3 → 0.4.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 +145 -1
- package/package.json +14 -5
- package/src/agent.js +740 -37
- package/src/bounded-local-task-subtask-pool.js +708 -0
- package/src/bounded-local-task-subtasks-contract.js +1189 -0
- package/src/cli-contract.js +42 -3
- package/src/console/app.js +212 -90
- package/src/desktop-executor.js +21 -2
- package/src/durable-local-task-steps-contract.js +1256 -0
- package/src/durable-local-task-worker.js +2607 -0
- package/src/execution-capability-contract.js +116 -0
- package/src/execution-guardrail.js +69 -12
- package/src/index.js +22 -7
- package/src/local-attachments.js +94 -113
- package/src/local-task-artifact-contract.js +266 -0
- package/src/local-task-attachment-store.js +447 -0
- package/src/local-task-file-capabilities.js +738 -0
- package/src/local-task-scenario-packs.js +681 -0
- package/src/local-task.js +1137 -0
- package/src/orchestrator/audit.js +42 -1
- package/src/orchestrator/loop.js +29 -3
- package/src/repository-inventory.js +37 -1
- package/src/runner.js +44 -0
- package/src/shutdown.js +63 -0
- package/src/sqm/bundle-store.js +249 -0
- package/src/sqm/canonical.js +45 -0
- package/src/sqm/checkers.js +299 -0
- package/src/sqm/command.js +149 -0
- package/src/sqm/evidence-client.js +12 -0
- package/src/sqm/index.js +141 -0
- package/src/sqm/proof.js +154 -0
- package/src/sqm/repository.js +163 -0
- package/src/sqm/session.js +58 -0
- package/src/sqm/validation.js +305 -0
- package/src/workspace-context.js +40 -7
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
const SEMVER = /^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/;
|
|
2
|
+
const DIGEST = /^sha256:[a-f0-9]{64}$/;
|
|
3
|
+
const MODULE_ID = /^KM-[A-Z0-9][A-Z0-9._-]{2,127}$/;
|
|
4
|
+
const INCIDENT_ID = /^INC-[A-Z0-9][A-Z0-9._-]{2,127}$/;
|
|
5
|
+
const RULE_ID = /^[a-z][a-z0-9._-]{2,127}$/;
|
|
6
|
+
const REPOSITORY = /^[^/\s]+\/[^/\s]+$/;
|
|
7
|
+
const CHECKERS = new Set(["diff_regex", "path_presence", "content_regex", "reference_definition"]);
|
|
8
|
+
const MATCHERS = new Set(["changed_path", "diff_regex", "content_regex", "reference"]);
|
|
9
|
+
const LIFECYCLES = new Set(["candidate", "shadow", "warn", "blocking", "retired"]);
|
|
10
|
+
const SEVERITIES = new Set(["info", "low", "medium", "high", "critical"]);
|
|
11
|
+
const BOUNDARIES = new Set(["api", "auth", "database", "deployment", "external_tool", "human", "llm", "pipeline", "runtime", "ui"]);
|
|
12
|
+
const ASSURANCE_ID = /^[A-Z][A-Z0-9._-]{2,127}$/;
|
|
13
|
+
const STRENGTH_KINDS = new Set(["prevention", "detection", "recovery"]);
|
|
14
|
+
const STRENGTH_STATUSES = new Set(["existing", "expected"]);
|
|
15
|
+
const SCENARIO_REQUIREMENTS = new Set(["local", "requires_human", "requires_post_deploy"]);
|
|
16
|
+
|
|
17
|
+
export function validateKnowledgeBundle(bundle, { cliVersion }) {
|
|
18
|
+
const errors = [];
|
|
19
|
+
object(bundle, "$", errors);
|
|
20
|
+
exactKeys(bundle, ["schema_version", "bundle_id", "version", "organization_id", "generated_at", "expires_at", "modules", "integrity"], "$", errors);
|
|
21
|
+
equal(bundle?.schema_version, "1.0.0", "$.schema_version", errors);
|
|
22
|
+
stringPattern(bundle?.bundle_id, /^[a-z][a-z0-9._-]{2,127}$/, "$.bundle_id", errors);
|
|
23
|
+
stringPattern(bundle?.version, /^[0-9]{4}\.[0-9]{2}\.[0-9]{2}\.[0-9]+$/, "$.version", errors);
|
|
24
|
+
nullableString(bundle?.organization_id, 200, "$.organization_id", errors);
|
|
25
|
+
dateTime(bundle?.generated_at, "$.generated_at", errors);
|
|
26
|
+
dateTime(bundle?.expires_at, "$.expires_at", errors);
|
|
27
|
+
if (!Array.isArray(bundle?.modules)) errors.push("$.modules must be an array");
|
|
28
|
+
for (const [index, module] of (bundle?.modules || []).entries()) {
|
|
29
|
+
validateModule(module, `$.modules[${index}]`, errors, cliVersion);
|
|
30
|
+
}
|
|
31
|
+
object(bundle?.integrity, "$.integrity", errors);
|
|
32
|
+
exactKeys(bundle?.integrity, ["algorithm", "digest", "key_id", "signature"], "$.integrity", errors);
|
|
33
|
+
equal(bundle?.integrity?.algorithm, "ed25519", "$.integrity.algorithm", errors);
|
|
34
|
+
stringPattern(bundle?.integrity?.digest, DIGEST, "$.integrity.digest", errors);
|
|
35
|
+
boundedString(bundle?.integrity?.key_id, 1, 200, "$.integrity.key_id", errors);
|
|
36
|
+
boundedString(bundle?.integrity?.signature, 1, 1000, "$.integrity.signature", errors);
|
|
37
|
+
if (errors.length) throw new Error(`Invalid SQM knowledge bundle:\n- ${errors.join("\n- ")}`);
|
|
38
|
+
return bundle;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function validateModule(module, at, errors, cliVersion) {
|
|
42
|
+
object(module, at, errors);
|
|
43
|
+
exactKeys(module, ["schema_version", "id", "version", "title", "summary", "lifecycle", "scope", "invariant", "detection", "assurance", "incident_refs", "provenance", "evaluation", "compatibility"], at, errors);
|
|
44
|
+
oneOf(module?.schema_version, new Set(["1.0.0", "1.1.0"]), `${at}.schema_version`, errors);
|
|
45
|
+
stringPattern(module?.id, MODULE_ID, `${at}.id`, errors);
|
|
46
|
+
stringPattern(module?.version, SEMVER, `${at}.version`, errors);
|
|
47
|
+
boundedString(module?.title, 1, 200, `${at}.title`, errors);
|
|
48
|
+
boundedString(module?.summary, 1, 2000, `${at}.summary`, errors);
|
|
49
|
+
|
|
50
|
+
object(module?.lifecycle, `${at}.lifecycle`, errors);
|
|
51
|
+
exactKeys(module?.lifecycle, ["status", "severity", "owner"], `${at}.lifecycle`, errors);
|
|
52
|
+
oneOf(module?.lifecycle?.status, LIFECYCLES, `${at}.lifecycle.status`, errors);
|
|
53
|
+
oneOf(module?.lifecycle?.severity, SEVERITIES, `${at}.lifecycle.severity`, errors);
|
|
54
|
+
nullableString(module?.lifecycle?.owner, 200, `${at}.lifecycle.owner`, errors);
|
|
55
|
+
|
|
56
|
+
object(module?.scope, `${at}.scope`, errors);
|
|
57
|
+
exactKeys(module?.scope, ["visibility", "organization_id", "repositories", "languages", "frameworks", "boundaries"], `${at}.scope`, errors);
|
|
58
|
+
oneOf(module?.scope?.visibility, new Set(["core", "framework", "organization"]), `${at}.scope.visibility`, errors);
|
|
59
|
+
nullableString(module?.scope?.organization_id, 200, `${at}.scope.organization_id`, errors);
|
|
60
|
+
stringArray(module?.scope?.repositories, `${at}.scope.repositories`, errors, REPOSITORY, 500, false);
|
|
61
|
+
stringArray(module?.scope?.languages, `${at}.scope.languages`, errors, null, 100, false);
|
|
62
|
+
stringArray(module?.scope?.frameworks, `${at}.scope.frameworks`, errors, null, 100, false);
|
|
63
|
+
enumArray(module?.scope?.boundaries, `${at}.scope.boundaries`, errors, BOUNDARIES, true);
|
|
64
|
+
|
|
65
|
+
object(module?.invariant, `${at}.invariant`, errors);
|
|
66
|
+
exactKeys(module?.invariant, ["statement", "failure_condition", "remediation"], `${at}.invariant`, errors);
|
|
67
|
+
boundedString(module?.invariant?.statement, 1, 1000, `${at}.invariant.statement`, errors);
|
|
68
|
+
boundedString(module?.invariant?.failure_condition, 1, 1000, `${at}.invariant.failure_condition`, errors);
|
|
69
|
+
nullableString(module?.invariant?.remediation, 2000, `${at}.invariant.remediation`, errors);
|
|
70
|
+
|
|
71
|
+
object(module?.detection, `${at}.detection`, errors);
|
|
72
|
+
exactKeys(module?.detection, ["matchers", "checks", "exceptions"], `${at}.detection`, errors);
|
|
73
|
+
nonEmptyArray(module?.detection?.matchers, `${at}.detection.matchers`, errors);
|
|
74
|
+
for (const [index, matcher] of (module?.detection?.matchers || []).entries()) validateMatcher(matcher, `${at}.detection.matchers[${index}]`, errors);
|
|
75
|
+
nonEmptyArray(module?.detection?.checks, `${at}.detection.checks`, errors);
|
|
76
|
+
for (const [index, check] of (module?.detection?.checks || []).entries()) validateCheck(check, `${at}.detection.checks[${index}]`, errors);
|
|
77
|
+
array(module?.detection?.exceptions, `${at}.detection.exceptions`, errors);
|
|
78
|
+
for (const [index, exception] of (module?.detection?.exceptions || []).entries()) validateException(exception, `${at}.detection.exceptions[${index}]`, errors);
|
|
79
|
+
|
|
80
|
+
if (module?.schema_version === "1.1.0") validateAssurance(module?.assurance, module, `${at}.assurance`, errors);
|
|
81
|
+
else if (module?.assurance !== undefined) validateAssurance(module.assurance, module, `${at}.assurance`, errors);
|
|
82
|
+
|
|
83
|
+
stringArray(module?.incident_refs, `${at}.incident_refs`, errors, INCIDENT_ID, 128, true);
|
|
84
|
+
object(module?.provenance, `${at}.provenance`, errors);
|
|
85
|
+
exactKeys(module?.provenance, ["created_at", "created_by", "generation_method", "model", "prompt_digest", "source_digests"], `${at}.provenance`, errors);
|
|
86
|
+
dateTime(module?.provenance?.created_at, `${at}.provenance.created_at`, errors);
|
|
87
|
+
boundedString(module?.provenance?.created_by, 1, 200, `${at}.provenance.created_by`, errors);
|
|
88
|
+
oneOf(module?.provenance?.generation_method, new Set(["human", "ai", "hybrid", "imported"]), `${at}.provenance.generation_method`, errors);
|
|
89
|
+
nullableString(module?.provenance?.model, 200, `${at}.provenance.model`, errors);
|
|
90
|
+
nullablePattern(module?.provenance?.prompt_digest, /^(sha256:[a-f0-9]{64})?$/, `${at}.provenance.prompt_digest`, errors);
|
|
91
|
+
stringArray(module?.provenance?.source_digests, `${at}.provenance.source_digests`, errors, DIGEST, 71, true);
|
|
92
|
+
|
|
93
|
+
object(module?.evaluation, `${at}.evaluation`, errors);
|
|
94
|
+
exactKeys(module?.evaluation, ["status", "true_positives", "false_positives", "false_negatives", "evaluated_at", "benchmark_digest"], `${at}.evaluation`, errors);
|
|
95
|
+
oneOf(module?.evaluation?.status, new Set(["not_evaluated", "synthetic_only", "historical_replay", "prospective"]), `${at}.evaluation.status`, errors);
|
|
96
|
+
nonNegativeInteger(module?.evaluation?.true_positives, `${at}.evaluation.true_positives`, errors);
|
|
97
|
+
nonNegativeInteger(module?.evaluation?.false_positives, `${at}.evaluation.false_positives`, errors);
|
|
98
|
+
nonNegativeInteger(module?.evaluation?.false_negatives, `${at}.evaluation.false_negatives`, errors);
|
|
99
|
+
nullableDateTime(module?.evaluation?.evaluated_at, `${at}.evaluation.evaluated_at`, errors);
|
|
100
|
+
nullablePattern(module?.evaluation?.benchmark_digest, /^(sha256:[a-f0-9]{64})?$/, `${at}.evaluation.benchmark_digest`, errors);
|
|
101
|
+
|
|
102
|
+
object(module?.compatibility, `${at}.compatibility`, errors);
|
|
103
|
+
exactKeys(module?.compatibility, ["min_cli_version", "checker_api_version"], `${at}.compatibility`, errors);
|
|
104
|
+
stringPattern(module?.compatibility?.min_cli_version, SEMVER, `${at}.compatibility.min_cli_version`, errors);
|
|
105
|
+
oneOf(module?.compatibility?.checker_api_version, new Set(["1.0.0", "1.1.0"]), `${at}.compatibility.checker_api_version`, errors);
|
|
106
|
+
if (SEMVER.test(cliVersion || "") && SEMVER.test(module?.compatibility?.min_cli_version || "") && compareSemver(cliVersion, module.compatibility.min_cli_version) < 0) {
|
|
107
|
+
errors.push(`${at} requires CLI ${module.compatibility.min_cli_version}, current ${cliVersion}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function validateAssurance(value, module, at, errors) {
|
|
112
|
+
object(value, at, errors);
|
|
113
|
+
exactKeys(value, ["state_models", "stressors", "strengths", "scenarios"], at, errors);
|
|
114
|
+
for (const key of ["state_models", "stressors", "strengths", "scenarios"]) nonEmptyArray(value?.[key], `${at}.${key}`, errors);
|
|
115
|
+
const checkIds = new Set((module?.detection?.checks || []).map((check) => check?.id));
|
|
116
|
+
const stressorIds = new Set();
|
|
117
|
+
const strengthIds = new Set();
|
|
118
|
+
const scenarioIds = new Set();
|
|
119
|
+
const modelIds = new Set();
|
|
120
|
+
|
|
121
|
+
for (const [index, stressor] of (value?.stressors || []).entries()) {
|
|
122
|
+
const path = `${at}.stressors[${index}]`;
|
|
123
|
+
object(stressor, path, errors);
|
|
124
|
+
exactKeys(stressor, ["id", "title", "description", "source", "activation_check_refs"], path, errors);
|
|
125
|
+
assuranceId(stressor?.id, `${path}.id`, stressorIds, errors);
|
|
126
|
+
boundedString(stressor?.title, 1, 200, `${path}.title`, errors);
|
|
127
|
+
boundedString(stressor?.description, 1, 1000, `${path}.description`, errors);
|
|
128
|
+
oneOf(stressor?.source, new Set(["historical_incident", "derived", "human"]), `${path}.source`, errors);
|
|
129
|
+
checkReferenceArray(stressor?.activation_check_refs, `${path}.activation_check_refs`, checkIds, errors, true);
|
|
130
|
+
}
|
|
131
|
+
for (const [index, strength] of (value?.strengths || []).entries()) {
|
|
132
|
+
const path = `${at}.strengths[${index}]`;
|
|
133
|
+
object(strength, path, errors);
|
|
134
|
+
exactKeys(strength, ["id", "title", "description", "kind", "status", "check_refs"], path, errors);
|
|
135
|
+
assuranceId(strength?.id, `${path}.id`, strengthIds, errors);
|
|
136
|
+
boundedString(strength?.title, 1, 200, `${path}.title`, errors);
|
|
137
|
+
boundedString(strength?.description, 1, 1000, `${path}.description`, errors);
|
|
138
|
+
oneOf(strength?.kind, STRENGTH_KINDS, `${path}.kind`, errors);
|
|
139
|
+
oneOf(strength?.status, STRENGTH_STATUSES, `${path}.status`, errors);
|
|
140
|
+
checkReferenceArray(strength?.check_refs, `${path}.check_refs`, checkIds, errors, true);
|
|
141
|
+
}
|
|
142
|
+
for (const [index, scenario] of (value?.scenarios || []).entries()) {
|
|
143
|
+
const path = `${at}.scenarios[${index}]`;
|
|
144
|
+
object(scenario, path, errors);
|
|
145
|
+
exactKeys(scenario, ["id", "title", "state_model_id", "transition_id", "stressor_refs", "expected_strength_refs", "check_refs", "expected_outcome", "execution_requirement"], path, errors);
|
|
146
|
+
assuranceId(scenario?.id, `${path}.id`, scenarioIds, errors);
|
|
147
|
+
boundedString(scenario?.title, 1, 200, `${path}.title`, errors);
|
|
148
|
+
stringPattern(scenario?.state_model_id, ASSURANCE_ID, `${path}.state_model_id`, errors);
|
|
149
|
+
stringPattern(scenario?.transition_id, ASSURANCE_ID, `${path}.transition_id`, errors);
|
|
150
|
+
referenceArray(scenario?.stressor_refs, `${path}.stressor_refs`, stressorIds, errors, true);
|
|
151
|
+
referenceArray(scenario?.expected_strength_refs, `${path}.expected_strength_refs`, strengthIds, errors, true);
|
|
152
|
+
checkReferenceArray(scenario?.check_refs, `${path}.check_refs`, checkIds, errors, true);
|
|
153
|
+
boundedString(scenario?.expected_outcome, 1, 1000, `${path}.expected_outcome`, errors);
|
|
154
|
+
oneOf(scenario?.execution_requirement, SCENARIO_REQUIREMENTS, `${path}.execution_requirement`, errors);
|
|
155
|
+
}
|
|
156
|
+
for (const [index, model] of (value?.state_models || []).entries()) {
|
|
157
|
+
const path = `${at}.state_models[${index}]`;
|
|
158
|
+
object(model, path, errors);
|
|
159
|
+
exactKeys(model, ["id", "version", "subject", "initial_state", "states", "transitions", "forbidden_states", "forbidden_transitions"], path, errors);
|
|
160
|
+
assuranceId(model?.id, `${path}.id`, modelIds, errors);
|
|
161
|
+
stringPattern(model?.version, SEMVER, `${path}.version`, errors);
|
|
162
|
+
boundedString(model?.subject, 1, 500, `${path}.subject`, errors);
|
|
163
|
+
stringPattern(model?.initial_state, ASSURANCE_ID, `${path}.initial_state`, errors);
|
|
164
|
+
nonEmptyArray(model?.states, `${path}.states`, errors);
|
|
165
|
+
nonEmptyArray(model?.transitions, `${path}.transitions`, errors);
|
|
166
|
+
array(model?.forbidden_states, `${path}.forbidden_states`, errors);
|
|
167
|
+
array(model?.forbidden_transitions, `${path}.forbidden_transitions`, errors);
|
|
168
|
+
const stateIds = new Set();
|
|
169
|
+
const invariantIds = new Set();
|
|
170
|
+
const transitionIds = new Set();
|
|
171
|
+
for (const [stateIndex, state] of (model?.states || []).entries()) {
|
|
172
|
+
const statePath = `${path}.states[${stateIndex}]`;
|
|
173
|
+
object(state, statePath, errors);
|
|
174
|
+
exactKeys(state, ["id", "label", "terminal", "invariants"], statePath, errors);
|
|
175
|
+
assuranceId(state?.id, `${statePath}.id`, stateIds, errors);
|
|
176
|
+
boundedString(state?.label, 1, 200, `${statePath}.label`, errors);
|
|
177
|
+
if (typeof state?.terminal !== "boolean") errors.push(`${statePath}.terminal must be a boolean`);
|
|
178
|
+
array(state?.invariants, `${statePath}.invariants`, errors);
|
|
179
|
+
for (const [invIndex, invariant] of (state?.invariants || []).entries()) {
|
|
180
|
+
const invPath = `${statePath}.invariants[${invIndex}]`;
|
|
181
|
+
object(invariant, invPath, errors);
|
|
182
|
+
exactKeys(invariant, ["id", "statement", "check_refs"], invPath, errors);
|
|
183
|
+
assuranceId(invariant?.id, `${invPath}.id`, invariantIds, errors);
|
|
184
|
+
boundedString(invariant?.statement, 1, 1000, `${invPath}.statement`, errors);
|
|
185
|
+
checkReferenceArray(invariant?.check_refs, `${invPath}.check_refs`, checkIds, errors, true);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (!stateIds.has(model?.initial_state)) errors.push(`${path}.initial_state must reference a declared state`);
|
|
189
|
+
for (const [transitionIndex, transition] of (model?.transitions || []).entries()) {
|
|
190
|
+
const transitionPath = `${path}.transitions[${transitionIndex}]`;
|
|
191
|
+
object(transition, transitionPath, errors);
|
|
192
|
+
exactKeys(transition, ["id", "from", "event", "to", "preconditions", "invariant_refs", "stressor_refs", "strength_refs", "scenario_refs"], transitionPath, errors);
|
|
193
|
+
assuranceId(transition?.id, `${transitionPath}.id`, transitionIds, errors);
|
|
194
|
+
if (!stateIds.has(transition?.from)) errors.push(`${transitionPath}.from must reference a declared state`);
|
|
195
|
+
if (!stateIds.has(transition?.to)) errors.push(`${transitionPath}.to must reference a declared state`);
|
|
196
|
+
boundedString(transition?.event, 1, 200, `${transitionPath}.event`, errors);
|
|
197
|
+
array(transition?.preconditions, `${transitionPath}.preconditions`, errors);
|
|
198
|
+
for (const [preIndex, precondition] of (transition?.preconditions || []).entries()) {
|
|
199
|
+
const prePath = `${transitionPath}.preconditions[${preIndex}]`;
|
|
200
|
+
object(precondition, prePath, errors);
|
|
201
|
+
exactKeys(precondition, ["id", "statement", "check_refs"], prePath, errors);
|
|
202
|
+
stringPattern(precondition?.id, ASSURANCE_ID, `${prePath}.id`, errors);
|
|
203
|
+
boundedString(precondition?.statement, 1, 1000, `${prePath}.statement`, errors);
|
|
204
|
+
checkReferenceArray(precondition?.check_refs, `${prePath}.check_refs`, checkIds, errors, true);
|
|
205
|
+
}
|
|
206
|
+
referenceArray(transition?.invariant_refs, `${transitionPath}.invariant_refs`, invariantIds, errors, false);
|
|
207
|
+
referenceArray(transition?.stressor_refs, `${transitionPath}.stressor_refs`, stressorIds, errors, true);
|
|
208
|
+
referenceArray(transition?.strength_refs, `${transitionPath}.strength_refs`, strengthIds, errors, true);
|
|
209
|
+
referenceArray(transition?.scenario_refs, `${transitionPath}.scenario_refs`, scenarioIds, errors, true);
|
|
210
|
+
}
|
|
211
|
+
for (const [forbiddenIndex, forbidden] of (model?.forbidden_states || []).entries()) {
|
|
212
|
+
const forbiddenPath = `${path}.forbidden_states[${forbiddenIndex}]`;
|
|
213
|
+
object(forbidden, forbiddenPath, errors);
|
|
214
|
+
exactKeys(forbidden, ["id", "statement", "check_refs"], forbiddenPath, errors);
|
|
215
|
+
stringPattern(forbidden?.id, ASSURANCE_ID, `${forbiddenPath}.id`, errors);
|
|
216
|
+
boundedString(forbidden?.statement, 1, 1000, `${forbiddenPath}.statement`, errors);
|
|
217
|
+
checkReferenceArray(forbidden?.check_refs, `${forbiddenPath}.check_refs`, checkIds, errors, true);
|
|
218
|
+
}
|
|
219
|
+
for (const [forbiddenIndex, forbidden] of (model?.forbidden_transitions || []).entries()) {
|
|
220
|
+
const forbiddenPath = `${path}.forbidden_transitions[${forbiddenIndex}]`;
|
|
221
|
+
object(forbidden, forbiddenPath, errors);
|
|
222
|
+
exactKeys(forbidden, ["from", "to", "reason"], forbiddenPath, errors);
|
|
223
|
+
if (!stateIds.has(forbidden?.from)) errors.push(`${forbiddenPath}.from must reference a declared state`);
|
|
224
|
+
if (!stateIds.has(forbidden?.to)) errors.push(`${forbiddenPath}.to must reference a declared state`);
|
|
225
|
+
boundedString(forbidden?.reason, 1, 1000, `${forbiddenPath}.reason`, errors);
|
|
226
|
+
}
|
|
227
|
+
for (const scenario of value?.scenarios || []) {
|
|
228
|
+
if (scenario?.state_model_id === model?.id && !transitionIds.has(scenario?.transition_id)) errors.push(`${at}.scenarios transition must exist in ${model.id}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
for (const scenario of value?.scenarios || []) if (!modelIds.has(scenario?.state_model_id)) errors.push(`${at}.scenarios state_model_id must reference a declared model`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function assuranceId(value, at, seen, errors) {
|
|
235
|
+
stringPattern(value, ASSURANCE_ID, at, errors);
|
|
236
|
+
if (seen.has(value)) errors.push(`${at} must be unique`);
|
|
237
|
+
seen.add(value);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function referenceArray(value, at, declared, errors, required) {
|
|
241
|
+
stringArray(value, at, errors, ASSURANCE_ID, 128, required);
|
|
242
|
+
for (const item of value || []) if (!declared.has(item)) errors.push(`${at} references unknown ${item}`);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function checkReferenceArray(value, at, declared, errors, required) {
|
|
246
|
+
stringArray(value, at, errors, RULE_ID, 128, required);
|
|
247
|
+
for (const item of value || []) if (!declared.has(item)) errors.push(`${at} references unknown ${item}`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function validateMatcher(value, at, errors) {
|
|
251
|
+
object(value, at, errors);
|
|
252
|
+
exactKeys(value, ["type", "paths", "pattern", "side"], at, errors);
|
|
253
|
+
oneOf(value?.type, MATCHERS, `${at}.type`, errors);
|
|
254
|
+
stringArray(value?.paths, `${at}.paths`, errors, null, 500, true);
|
|
255
|
+
nullableString(value?.pattern, 2000, `${at}.pattern`, errors);
|
|
256
|
+
if (["diff_regex", "content_regex", "reference"].includes(value?.type) && !value?.pattern) errors.push(`${at}.pattern is required for ${value.type}`);
|
|
257
|
+
if (value?.side !== undefined) oneOf(value.side, new Set(["added", "removed", "either"]), `${at}.side`, errors);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function validateCheck(value, at, errors) {
|
|
261
|
+
object(value, at, errors);
|
|
262
|
+
exactKeys(value, ["id", "type", "message", "paths", "pattern", "expect", "definition_paths"], at, errors);
|
|
263
|
+
stringPattern(value?.id, RULE_ID, `${at}.id`, errors);
|
|
264
|
+
oneOf(value?.type, CHECKERS, `${at}.type`, errors);
|
|
265
|
+
boundedString(value?.message, 1, 1000, `${at}.message`, errors);
|
|
266
|
+
stringArray(value?.paths, `${at}.paths`, errors, null, 500, false);
|
|
267
|
+
nullableString(value?.pattern, 2000, `${at}.pattern`, errors);
|
|
268
|
+
if (["diff_regex", "content_regex", "reference_definition"].includes(value?.type) && !value?.pattern) errors.push(`${at}.pattern is required for ${value.type}`);
|
|
269
|
+
if (value?.expect !== undefined) oneOf(value.expect, new Set(["present", "absent", "defined"]), `${at}.expect`, errors);
|
|
270
|
+
stringArray(value?.definition_paths, `${at}.definition_paths`, errors, null, 500, false);
|
|
271
|
+
if (value?.type === "reference_definition" && (!value?.definition_paths?.length || value?.expect !== "defined")) errors.push(`${at} reference_definition requires definition_paths and expect=defined`);
|
|
272
|
+
if (value?.type === "path_presence" && !["present", "absent"].includes(value?.expect)) errors.push(`${at} path_presence requires expect=present|absent`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function validateException(value, at, errors) {
|
|
276
|
+
object(value, at, errors);
|
|
277
|
+
exactKeys(value, ["id", "reason", "paths", "expires_at"], at, errors);
|
|
278
|
+
stringPattern(value?.id, RULE_ID, `${at}.id`, errors);
|
|
279
|
+
boundedString(value?.reason, 1, 1000, `${at}.reason`, errors);
|
|
280
|
+
stringArray(value?.paths, `${at}.paths`, errors, null, 500, true);
|
|
281
|
+
nullableDateTime(value?.expires_at, `${at}.expires_at`, errors);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export function compareSemver(left, right) {
|
|
285
|
+
const a = left.split(".").map(Number);
|
|
286
|
+
const b = right.split(".").map(Number);
|
|
287
|
+
for (let index = 0; index < 3; index += 1) if (a[index] !== b[index]) return a[index] - b[index];
|
|
288
|
+
return 0;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function exactKeys(value, allowed, at, errors) { if (!value || typeof value !== "object" || Array.isArray(value)) return; const set = new Set(allowed); for (const key of Object.keys(value)) if (!set.has(key)) errors.push(`${at}.${key} is not allowed`); }
|
|
292
|
+
function object(value, at, errors) { if (!value || typeof value !== "object" || Array.isArray(value)) errors.push(`${at} must be an object`); }
|
|
293
|
+
function array(value, at, errors) { if (value !== undefined && !Array.isArray(value)) errors.push(`${at} must be an array`); }
|
|
294
|
+
function nonEmptyArray(value, at, errors) { if (!Array.isArray(value) || value.length === 0) errors.push(`${at} must be a non-empty array`); }
|
|
295
|
+
function equal(value, expected, at, errors) { if (value !== expected) errors.push(`${at} must equal ${expected}`); }
|
|
296
|
+
function oneOf(value, allowed, at, errors) { if (!allowed.has(value)) errors.push(`${at} has an unsupported value`); }
|
|
297
|
+
function boundedString(value, min, max, at, errors) { if (typeof value !== "string" || value.length < min || value.length > max) errors.push(`${at} must be a string of length ${min}-${max}`); }
|
|
298
|
+
function nullableString(value, max, at, errors) { if (value !== undefined && value !== null && (typeof value !== "string" || value.length > max)) errors.push(`${at} must be null or a string up to ${max} characters`); }
|
|
299
|
+
function stringPattern(value, pattern, at, errors) { if (typeof value !== "string" || !pattern.test(value)) errors.push(`${at} has an invalid format`); }
|
|
300
|
+
function nullablePattern(value, pattern, at, errors) { if (value !== undefined && value !== null && (typeof value !== "string" || !pattern.test(value))) errors.push(`${at} has an invalid format`); }
|
|
301
|
+
function dateTime(value, at, errors) { if (typeof value !== "string" || !Number.isFinite(Date.parse(value))) errors.push(`${at} must be a date-time`); }
|
|
302
|
+
function nullableDateTime(value, at, errors) { if (value !== undefined && value !== null) dateTime(value, at, errors); }
|
|
303
|
+
function nonNegativeInteger(value, at, errors) { if (!Number.isInteger(value) || value < 0) errors.push(`${at} must be a non-negative integer`); }
|
|
304
|
+
function stringArray(value, at, errors, pattern, max, required) { if (value === undefined && !required) return; if (!Array.isArray(value) || (required && value.length === 0)) { errors.push(`${at} must be ${required ? "a non-empty" : "an"} array`); return; } const seen = new Set(); for (const item of value) { if (typeof item !== "string" || item.length === 0 || item.length > max || (pattern && !pattern.test(item))) errors.push(`${at} contains an invalid string`); if (seen.has(item)) errors.push(`${at} must contain unique values`); seen.add(item); } }
|
|
305
|
+
function enumArray(value, at, errors, allowed, required) { if (!Array.isArray(value) || (required && value.length === 0)) { errors.push(`${at} must be a non-empty array`); return; } const seen = new Set(); for (const item of value) { if (!allowed.has(item)) errors.push(`${at} contains an unsupported value`); if (seen.has(item)) errors.push(`${at} must contain unique values`); seen.add(item); } }
|
package/src/workspace-context.js
CHANGED
|
@@ -23,13 +23,46 @@ export async function discoverWorkspace(cwd = process.cwd()) {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export function normalizeGitHubRemote(value) {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
.
|
|
32
|
-
|
|
26
|
+
const remote = String(value || "").trim();
|
|
27
|
+
let pathname = null;
|
|
28
|
+
if (/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+(?:\.git)?$/i.test(remote)) {
|
|
29
|
+
pathname = remote;
|
|
30
|
+
} else {
|
|
31
|
+
const scp = /^git@github\.com:([^?#]+)$/i.exec(remote);
|
|
32
|
+
if (scp) {
|
|
33
|
+
pathname = scp[1];
|
|
34
|
+
} else {
|
|
35
|
+
try {
|
|
36
|
+
const parsed = new URL(remote);
|
|
37
|
+
if (
|
|
38
|
+
!["https:", "http:", "ssh:"].includes(parsed.protocol) ||
|
|
39
|
+
parsed.hostname.toLowerCase() !== "github.com" ||
|
|
40
|
+
parsed.search ||
|
|
41
|
+
parsed.hash
|
|
42
|
+
) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
pathname = parsed.pathname.replace(/^\/+/, "");
|
|
46
|
+
} catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const repository = pathname.replace(/\.git$/i, "");
|
|
52
|
+
const parts = repository.split("/");
|
|
53
|
+
if (
|
|
54
|
+
parts.length !== 2 ||
|
|
55
|
+
parts.some(
|
|
56
|
+
(part) =>
|
|
57
|
+
!part ||
|
|
58
|
+
part === "." ||
|
|
59
|
+
part === ".." ||
|
|
60
|
+
!/^[A-Za-z0-9_.-]+$/.test(part),
|
|
61
|
+
)
|
|
62
|
+
) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
return parts.join("/");
|
|
33
66
|
}
|
|
34
67
|
|
|
35
68
|
function emptyWorkspace(cwd) {
|