@opengsd/gsd-core 1.6.0-rc.2 → 1.6.0-rc.3
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 +2 -1
- package/agents/gsd-planner.md +8 -57
- package/agents/gsd-security-auditor.md +37 -18
- package/bin/install.js +151 -13
- package/gemini-extension.json +1 -1
- package/gsd-core/bin/gsd-tools.cjs +35 -3
- package/gsd-core/bin/lib/audit-command-router.cjs +52 -14
- package/gsd-core/bin/lib/capability-lifecycle.cjs +30 -7
- package/gsd-core/bin/lib/capability-registry.cjs +96 -83
- package/gsd-core/bin/lib/capability-validator.cjs +22 -0
- package/gsd-core/bin/lib/cjs-command-router-adapter.cjs +40 -2
- package/gsd-core/bin/lib/command-routing-hub.cjs +10 -3
- package/gsd-core/bin/lib/config-schema.cjs +1 -0
- package/gsd-core/bin/lib/config.cjs +67 -24
- package/gsd-core/bin/lib/coverage.cjs +464 -0
- package/gsd-core/bin/lib/graphify-command-router.cjs +53 -36
- package/gsd-core/bin/lib/init.cjs +8 -4
- package/gsd-core/bin/lib/install-profiles.cjs +6 -3
- package/gsd-core/bin/lib/intel-command-router.cjs +79 -60
- package/gsd-core/bin/lib/planning-workspace.cjs +157 -13
- package/gsd-core/bin/lib/profile-output.cjs +18 -6
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +53 -16
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +21 -3
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +15 -0
- package/gsd-core/bin/lib/runtime-name-policy.cjs +47 -2
- package/gsd-core/bin/lib/state.cjs +364 -52
- package/gsd-core/bin/lib/surface.cjs +42 -10
- package/gsd-core/bin/lib/update-context.cjs +2 -2
- package/gsd-core/references/planner-guidance.md +66 -0
- package/gsd-core/references/planning-config.md +2 -2
- package/gsd-core/references/security-asvs-levels.md +27 -0
- package/gsd-core/templates/SECURITY.md +6 -4
- package/gsd-core/templates/summary-complex.md +4 -0
- package/gsd-core/templates/summary-minimal.md +3 -0
- package/gsd-core/templates/summary-standard.md +4 -0
- package/gsd-core/templates/summary.md +41 -0
- package/gsd-core/workflows/execute-plan.md +5 -0
- package/gsd-core/workflows/new-project.md +4 -4
- package/gsd-core/workflows/plan-phase.md +15 -0
- package/gsd-core/workflows/secure-phase.md +13 -7
- package/gsd-core/workflows/verify-work.md +30 -1
- package/package.json +4 -2
- package/scripts/gen-plugin-skills.cjs +117 -0
- package/scripts/lint-test-file-count.allowlist.json +2 -1
- package/scripts/prompt-injection-scan.sh +4 -0
- package/scripts/release-notes/conventional-title.cjs +88 -0
- package/scripts/release-notes/format-github-release-notes.cjs +4 -3
- package/skills/gsd-add-tests/SKILL.md +38 -0
- package/skills/gsd-ai-integration-phase/SKILL.md +37 -0
- package/skills/gsd-audit-fix/SKILL.md +33 -0
- package/skills/gsd-audit-milestone/SKILL.md +37 -0
- package/skills/gsd-audit-uat/SKILL.md +25 -0
- package/skills/gsd-autonomous/SKILL.md +51 -0
- package/skills/gsd-capture/SKILL.md +67 -0
- package/skills/gsd-cleanup/SKILL.md +24 -0
- package/skills/gsd-code-review/SKILL.md +59 -0
- package/skills/gsd-complete-milestone/SKILL.md +142 -0
- package/skills/gsd-config/SKILL.md +56 -0
- package/skills/gsd-debug/SKILL.md +53 -0
- package/skills/gsd-discuss-phase/SKILL.md +77 -0
- package/skills/gsd-docs-update/SKILL.md +49 -0
- package/skills/gsd-eval-review/SKILL.md +33 -0
- package/skills/gsd-execute-phase/SKILL.md +65 -0
- package/skills/gsd-explore/SKILL.md +28 -0
- package/skills/gsd-extract-learnings/SKILL.md +22 -0
- package/skills/gsd-fast/SKILL.md +31 -0
- package/skills/gsd-forensics/SKILL.md +56 -0
- package/skills/gsd-graphify/SKILL.md +204 -0
- package/skills/gsd-health/SKILL.md +31 -0
- package/skills/gsd-help/SKILL.md +29 -0
- package/skills/gsd-import/SKILL.md +46 -0
- package/skills/gsd-inbox/SKILL.md +39 -0
- package/skills/gsd-ingest-docs/SKILL.md +43 -0
- package/skills/gsd-manager/SKILL.md +45 -0
- package/skills/gsd-map-codebase/SKILL.md +83 -0
- package/skills/gsd-mempalace-capture/SKILL.md +71 -0
- package/skills/gsd-mempalace-recall/SKILL.md +102 -0
- package/skills/gsd-milestone-summary/SKILL.md +51 -0
- package/skills/gsd-mvp-phase/SKILL.md +45 -0
- package/skills/gsd-new-milestone/SKILL.md +45 -0
- package/skills/gsd-new-project/SKILL.md +47 -0
- package/skills/gsd-ns-context/SKILL.md +24 -0
- package/skills/gsd-ns-ideate/SKILL.md +23 -0
- package/skills/gsd-ns-manage/SKILL.md +35 -0
- package/skills/gsd-ns-project/SKILL.md +26 -0
- package/skills/gsd-ns-review/SKILL.md +28 -0
- package/skills/gsd-ns-workflow/SKILL.md +33 -0
- package/skills/gsd-pause-work/SKILL.md +43 -0
- package/skills/gsd-phase/SKILL.md +57 -0
- package/skills/gsd-plan-phase/SKILL.md +63 -0
- package/skills/gsd-plan-review-convergence/SKILL.md +60 -0
- package/skills/gsd-pr-branch/SKILL.md +26 -0
- package/skills/gsd-profile-user/SKILL.md +47 -0
- package/skills/gsd-progress/SKILL.md +49 -0
- package/skills/gsd-quick/SKILL.md +174 -0
- package/skills/gsd-resume-work/SKILL.md +31 -0
- package/skills/gsd-review/SKILL.md +42 -0
- package/skills/gsd-review-backlog/SKILL.md +63 -0
- package/skills/gsd-secure-phase/SKILL.md +36 -0
- package/skills/gsd-settings/SKILL.md +29 -0
- package/skills/gsd-ship/SKILL.md +24 -0
- package/skills/gsd-sketch/SKILL.md +60 -0
- package/skills/gsd-spec-phase/SKILL.md +63 -0
- package/skills/gsd-spike/SKILL.md +57 -0
- package/skills/gsd-stats/SKILL.md +20 -0
- package/skills/gsd-surface/SKILL.md +162 -0
- package/skills/gsd-thread/SKILL.md +24 -0
- package/skills/gsd-ui-phase/SKILL.md +35 -0
- package/skills/gsd-ui-review/SKILL.md +33 -0
- package/skills/gsd-ultraplan-phase/SKILL.md +34 -0
- package/skills/gsd-undo/SKILL.md +35 -0
- package/skills/gsd-update/SKILL.md +50 -0
- package/skills/gsd-validate-phase/SKILL.md +36 -0
- package/skills/gsd-verify-work/SKILL.md +39 -0
- package/skills/gsd-workspace/SKILL.md +53 -0
- package/skills/gsd-workstreams/SKILL.md +70 -0
|
@@ -582,6 +582,27 @@ function validateFeatureBody(cap) {
|
|
|
582
582
|
'absolute path, or ".." segment) — it contains unsafe characters: ' + JSON.stringify(h.script),
|
|
583
583
|
);
|
|
584
584
|
}
|
|
585
|
+
// #1634: optional tool-scoping `matcher` (a settings.json concept — exact tool name,
|
|
586
|
+
// pipe-separated list, wildcard, or regex; e.g. "Write|Edit"). When present it must be a
|
|
587
|
+
// non-empty string without control characters; absent => match-all (omitted at projection
|
|
588
|
+
// so existing shipped capabilities are unchanged).
|
|
589
|
+
if (h.matcher !== undefined) {
|
|
590
|
+
if (typeof h.matcher !== 'string' || h.matcher.length === 0) {
|
|
591
|
+
errors.push('hooks[' + i + '].matcher must be a non-empty string when present');
|
|
592
|
+
} else {
|
|
593
|
+
// Reject ASCII control characters (0x00-0x1f and 0x7f DEL) via char codes — a literal
|
|
594
|
+
// control-char range regex trips ESLint's no-control-regex rule, and char codes are
|
|
595
|
+
// equally precise.
|
|
596
|
+
let hasControl = false;
|
|
597
|
+
for (let c = 0; c < h.matcher.length; c++) {
|
|
598
|
+
const code = h.matcher.charCodeAt(c);
|
|
599
|
+
if (code < 0x20 || code === 0x7f) { hasControl = true; break; }
|
|
600
|
+
}
|
|
601
|
+
if (hasControl) {
|
|
602
|
+
errors.push('hooks[' + i + '].matcher must not contain control characters');
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
585
606
|
}
|
|
586
607
|
}
|
|
587
608
|
}
|
|
@@ -665,6 +686,7 @@ const VALID_CONVERTER_NAMES = new Set([
|
|
|
665
686
|
'convertClaudeCommandToOpencodeSkill',
|
|
666
687
|
'convertClaudeCommandToTraeSkill',
|
|
667
688
|
'convertClaudeCommandToWindsurfSkill',
|
|
689
|
+
'convertClaudeCommandToWindsurfWorkflow',
|
|
668
690
|
// agent converters (#1173 — descriptor-driven agent conversion wiring)
|
|
669
691
|
'convertClaudeAgentToCopilotAgent',
|
|
670
692
|
'convertClaudeAgentToAntigravityAgent',
|
|
@@ -13,6 +13,12 @@
|
|
|
13
13
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
14
14
|
const commandRoutingHub = require("./command-routing-hub.cjs");
|
|
15
15
|
const { createHub, ERROR_KINDS } = commandRoutingHub;
|
|
16
|
+
// Phase 2 (#1646): import ERROR_REASON so the UnknownCommand translation can
|
|
17
|
+
// pass `sdk_unknown_command` as the second arg to error(), preserving the
|
|
18
|
+
// JSON-error envelope contract that capability routers' tests assert on.
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
20
|
+
const io = require("./io.cjs");
|
|
21
|
+
const { ERROR_REASON } = io;
|
|
16
22
|
// ─── Implementation ───────────────────────────────────────────────────────────
|
|
17
23
|
function routeCjsCommandFamily({ args, subcommands, handlers, defaultSubcommand, unsupported = {}, unknownMessage, error, cwd, raw, }) {
|
|
18
24
|
routeHubCommandFamily({
|
|
@@ -44,6 +50,18 @@ function routeHubCommandFamily({ family, args, subcommands, handlers, defaultSub
|
|
|
44
50
|
const available = subcommands.filter((s) => !unsupported[s]);
|
|
45
51
|
const registryHandlers = Object.fromEntries(Object.entries(handlers).map(([name, handler]) => [
|
|
46
52
|
name,
|
|
53
|
+
// Honestified via amendment #1642 (#1644 Phase 1): the runtime check
|
|
54
|
+
// `'ok' in result` already passes any `{ok:*}` object through, so the
|
|
55
|
+
// historical `{ok:true, data}` return type was a lie whenever the
|
|
56
|
+
// handler returned an err Result. The lying cast below is preserved
|
|
57
|
+
// because the Hub's `export =` syntax doesn't expose `HubResult` for
|
|
58
|
+
// import; the Hub's `_validateErrResult` runtime-validates the actual
|
|
59
|
+
// shape, so structural compatibility is sufficient. The wrapper's
|
|
60
|
+
// 0-arg signature is assignable to the Hub's `(ctx) => HubResult`
|
|
61
|
+
// Handler type via TypeScript parameter bivariance; the Hub's per-call
|
|
62
|
+
// ctx is intentionally ignored (host-router handlers don't use it;
|
|
63
|
+
// capability-router handlers in Phase 2 will return HubResults that
|
|
64
|
+
// already carry context).
|
|
47
65
|
() => {
|
|
48
66
|
const result = handler();
|
|
49
67
|
if (result && typeof result === 'object' && Object.prototype.hasOwnProperty.call(result, 'ok')) {
|
|
@@ -66,10 +84,30 @@ function routeHubCommandFamily({ family, args, subcommands, handlers, defaultSub
|
|
|
66
84
|
if (result.ok)
|
|
67
85
|
return;
|
|
68
86
|
if (result.kind === ERROR_KINDS.UnknownCommand) {
|
|
69
|
-
|
|
87
|
+
// Phase 2 (#1646): pass SDK_UNKNOWN_COMMAND as the second arg so the
|
|
88
|
+
// JSON-error envelope (GSD_JSON_ERRORS=1) preserves the typed reason
|
|
89
|
+
// for downstream consumers. Additive for host routers (their existing
|
|
90
|
+
// one-arg `error` callbacks ignore the second arg); required for
|
|
91
|
+
// capability routers whose tests assert on `reason === 'sdk_unknown_command'`.
|
|
92
|
+
error(unknownMessage(subcommand ?? '', available), ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (result.kind === ERROR_KINDS.InvalidArgs) {
|
|
96
|
+
// Amendment #1642 (#1644): when the handler provided exitReason, pass it
|
|
97
|
+
// as the second arg to error() so the JSON-error envelope
|
|
98
|
+
// (GSD_JSON_ERRORS=1) preserves the typed ERROR_REASON value for
|
|
99
|
+
// downstream consumers. When exitReason is absent, call error(msg) with
|
|
100
|
+
// exactly one arg — byte-identical with prior behavior.
|
|
101
|
+
const invalidArgs = result;
|
|
102
|
+
if (invalidArgs.exitReason) {
|
|
103
|
+
error(invalidArgs.reason, invalidArgs.exitReason);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
error(invalidArgs.reason);
|
|
107
|
+
}
|
|
70
108
|
return;
|
|
71
109
|
}
|
|
72
|
-
if (result.kind === ERROR_KINDS.
|
|
110
|
+
if (result.kind === ERROR_KINDS.HandlerRefusal) {
|
|
73
111
|
error(result.reason);
|
|
74
112
|
return;
|
|
75
113
|
}
|
|
@@ -73,8 +73,14 @@ function _safeJson(value) {
|
|
|
73
73
|
function makeUnknownCommand(command) {
|
|
74
74
|
return Object.freeze({ ok: false, kind: ERROR_KINDS.UnknownCommand, command });
|
|
75
75
|
}
|
|
76
|
-
function makeInvalidArgs(arg, reason) {
|
|
77
|
-
|
|
76
|
+
function makeInvalidArgs(arg, reason, exitReason) {
|
|
77
|
+
const obj = { ok: false, kind: ERROR_KINDS.InvalidArgs, arg, reason };
|
|
78
|
+
// Conditionally add exitReason only when truthy — preserves strict-keys
|
|
79
|
+
// invariant (2-arg callers must continue to produce a 4-key frozen result).
|
|
80
|
+
if (exitReason) {
|
|
81
|
+
obj.exitReason = exitReason;
|
|
82
|
+
}
|
|
83
|
+
return Object.freeze(obj);
|
|
78
84
|
}
|
|
79
85
|
function makeHandlerRefusal(reason) {
|
|
80
86
|
return Object.freeze({ ok: false, kind: ERROR_KINDS.HandlerRefusal, reason });
|
|
@@ -113,7 +119,8 @@ const _VARIANT_SCHEMA = {
|
|
|
113
119
|
},
|
|
114
120
|
InvalidArgs: {
|
|
115
121
|
required: ['arg', 'reason'],
|
|
116
|
-
|
|
122
|
+
// Amendment #1642: exitReason? is allowed but not required.
|
|
123
|
+
allowed: new Set(['ok', 'kind', 'arg', 'reason', 'exitReason']),
|
|
117
124
|
},
|
|
118
125
|
HandlerRefusal: {
|
|
119
126
|
required: ['reason'],
|
|
@@ -27,7 +27,7 @@ const modelProfiles = require("./model-profiles.cjs");
|
|
|
27
27
|
const { VALID_PROFILES, getAgentToModelMapForProfile, formatAgentToModelMapAsTable } = modelProfiles;
|
|
28
28
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
29
29
|
const configSchema = require("./config-schema.cjs");
|
|
30
|
-
const { VALID_CONFIG_KEYS, isValidConfigKey } = configSchema;
|
|
30
|
+
const { VALID_CONFIG_KEYS, isValidConfigKey, getCapabilityConfigSchema } = configSchema;
|
|
31
31
|
const secrets_cjs_1 = require("./secrets.cjs");
|
|
32
32
|
const review_reviewer_selection_cjs_1 = require("./review-reviewer-selection.cjs");
|
|
33
33
|
const configuration_cjs_1 = require("./configuration.cjs");
|
|
@@ -480,6 +480,22 @@ function setConfigValues(cwd, entries) {
|
|
|
480
480
|
}
|
|
481
481
|
});
|
|
482
482
|
}
|
|
483
|
+
/**
|
|
484
|
+
* Type-safe enum guard for config-set string-enum keys.
|
|
485
|
+
*
|
|
486
|
+
* Rejects any parsedValue that is not a plain string AND a member of `allowed`.
|
|
487
|
+
* This closes the JSON-array coercion bypass: String(["val"]) === "val" satisfies
|
|
488
|
+
* a bare .includes(String(parsedValue)) check, but typeof parsedValue !== 'string'
|
|
489
|
+
* catches the array before the includes test.
|
|
490
|
+
*
|
|
491
|
+
* The `label` parameter is used verbatim in the error message so callers can
|
|
492
|
+
* preserve existing message text byte-for-byte.
|
|
493
|
+
*/
|
|
494
|
+
function assertEnumValue(parsedValue, rawVal, allowed, label) {
|
|
495
|
+
if (typeof parsedValue !== 'string' || !allowed.includes(parsedValue)) {
|
|
496
|
+
error(`Invalid ${label} '${rawVal}'. Valid values: ${allowed.join(', ')}`);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
483
499
|
/**
|
|
484
500
|
* Command to set a value in the config file, allowing nested values via dot notation (e.g.,
|
|
485
501
|
* "workflow.research").
|
|
@@ -524,14 +540,12 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
|
|
|
524
540
|
catch { /* keep as string */ }
|
|
525
541
|
}
|
|
526
542
|
const VALID_CONTEXT_VALUES = ['dev', 'research', 'review'];
|
|
527
|
-
if (kp === 'context'
|
|
528
|
-
|
|
529
|
-
}
|
|
543
|
+
if (kp === 'context')
|
|
544
|
+
assertEnumValue(parsedValue, val, VALID_CONTEXT_VALUES, 'context value');
|
|
530
545
|
// Codebase drift detector (#2003)
|
|
531
546
|
const VALID_DRIFT_ACTIONS = ['warn', 'auto-remap'];
|
|
532
|
-
if (kp === 'workflow.drift_action'
|
|
533
|
-
|
|
534
|
-
}
|
|
547
|
+
if (kp === 'workflow.drift_action')
|
|
548
|
+
assertEnumValue(parsedValue, val, VALID_DRIFT_ACTIONS, 'workflow.drift_action');
|
|
535
549
|
if (kp === 'workflow.drift_threshold') {
|
|
536
550
|
if (typeof parsedValue !== 'number' || !Number.isInteger(parsedValue) || parsedValue < 1) {
|
|
537
551
|
error(`Invalid workflow.drift_threshold '${val}'. Must be a positive integer.`);
|
|
@@ -554,28 +568,23 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
|
|
|
554
568
|
}
|
|
555
569
|
// Human verification checkpoint mode (#3309)
|
|
556
570
|
const VALID_HUMAN_VERIFY_MODES = ['mid-flight', 'end-of-phase'];
|
|
557
|
-
if (kp === 'workflow.human_verify_mode'
|
|
558
|
-
|
|
559
|
-
}
|
|
571
|
+
if (kp === 'workflow.human_verify_mode')
|
|
572
|
+
assertEnumValue(parsedValue, val, VALID_HUMAN_VERIFY_MODES, 'workflow.human_verify_mode');
|
|
560
573
|
// Context exhaustion guard mode (#1452)
|
|
561
574
|
const VALID_CONTEXT_GUARD_MODES = ['auto', 'warn', 'off'];
|
|
562
|
-
if (kp === 'workflow.context_guard_mode'
|
|
563
|
-
|
|
564
|
-
}
|
|
575
|
+
if (kp === 'workflow.context_guard_mode')
|
|
576
|
+
assertEnumValue(parsedValue, val, VALID_CONTEXT_GUARD_MODES, 'workflow.context_guard_mode');
|
|
565
577
|
// Context position enum validation (#2937)
|
|
566
578
|
const VALID_CONTEXT_POSITIONS = ['front', 'end'];
|
|
567
|
-
if (kp === 'statusline.context_position'
|
|
568
|
-
|
|
569
|
-
}
|
|
579
|
+
if (kp === 'statusline.context_position')
|
|
580
|
+
assertEnumValue(parsedValue, val, VALID_CONTEXT_POSITIONS, 'statusline.context_position');
|
|
570
581
|
// Fallow scope + profile enum validation (#3424)
|
|
571
582
|
const VALID_FALLOW_SCOPES = ['phase', 'repo'];
|
|
572
|
-
if (kp === 'code_quality.fallow.scope'
|
|
573
|
-
|
|
574
|
-
}
|
|
583
|
+
if (kp === 'code_quality.fallow.scope')
|
|
584
|
+
assertEnumValue(parsedValue, val, VALID_FALLOW_SCOPES, 'code_quality.fallow.scope');
|
|
575
585
|
const VALID_FALLOW_PROFILES = ['minimal', 'standard', 'strict'];
|
|
576
|
-
if (kp === 'code_quality.fallow.profile'
|
|
577
|
-
|
|
578
|
-
}
|
|
586
|
+
if (kp === 'code_quality.fallow.profile')
|
|
587
|
+
assertEnumValue(parsedValue, val, VALID_FALLOW_PROFILES, 'code_quality.fallow.profile');
|
|
579
588
|
// plan_review.source_grounding (#22) — boolean only
|
|
580
589
|
if (kp === 'plan_review.source_grounding') {
|
|
581
590
|
if (typeof parsedValue !== 'boolean') {
|
|
@@ -584,8 +593,42 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
|
|
|
584
593
|
}
|
|
585
594
|
// plan_review.source_grounding_authority (#22) — enum
|
|
586
595
|
const VALID_SOURCE_GROUNDING_AUTHORITIES = ['grep', 'intel', 'treesitter', 'lsp', 'scip'];
|
|
587
|
-
if (kp === 'plan_review.source_grounding_authority'
|
|
588
|
-
|
|
596
|
+
if (kp === 'plan_review.source_grounding_authority')
|
|
597
|
+
assertEnumValue(parsedValue, val, VALID_SOURCE_GROUNDING_AUTHORITIES, 'plan_review.source_grounding_authority');
|
|
598
|
+
// Generic capability-registry validation (#1628). Capability-owned keys declare
|
|
599
|
+
// their type/values in the registry but most lack a hardcoded guard, so out-of-
|
|
600
|
+
// domain values (including JSON array/object coercion) were stored silently.
|
|
601
|
+
const capDef = getCapabilityConfigSchema(cwd)[kp];
|
|
602
|
+
if (capDef && typeof capDef.type === 'string') {
|
|
603
|
+
switch (capDef.type) {
|
|
604
|
+
case 'enum':
|
|
605
|
+
if (Array.isArray(capDef.values)) {
|
|
606
|
+
assertEnumValue(parsedValue, val, capDef.values.map((v) => String(v)), kp);
|
|
607
|
+
}
|
|
608
|
+
break;
|
|
609
|
+
case 'boolean':
|
|
610
|
+
if (typeof parsedValue !== 'boolean') {
|
|
611
|
+
error(`Invalid ${kp} '${val}'. Must be a boolean (true or false).`);
|
|
612
|
+
}
|
|
613
|
+
break;
|
|
614
|
+
case 'number':
|
|
615
|
+
if (typeof parsedValue !== 'number' || !Number.isFinite(parsedValue)) {
|
|
616
|
+
error(`Invalid ${kp} '${val}'. Must be a number.`);
|
|
617
|
+
}
|
|
618
|
+
break;
|
|
619
|
+
case 'string':
|
|
620
|
+
if (typeof parsedValue !== 'string') {
|
|
621
|
+
error(`Invalid ${kp} '${val}'. Must be a string.`);
|
|
622
|
+
}
|
|
623
|
+
break;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
// Security — ASVS level range (#1628)
|
|
627
|
+
// Must be an integer in {1, 2, 3} (OWASP ASVS levels).
|
|
628
|
+
if (kp === 'workflow.security_asvs_level') {
|
|
629
|
+
if (typeof parsedValue !== 'number' || !Number.isInteger(parsedValue) || parsedValue < 1 || parsedValue > 3) {
|
|
630
|
+
error(`Invalid workflow.security_asvs_level '${val}'. Must be an integer 1, 2, or 3.`);
|
|
631
|
+
}
|
|
589
632
|
}
|
|
590
633
|
if (kp === 'review.default_reviewers') {
|
|
591
634
|
const normalized = (0, review_reviewer_selection_cjs_1.normalizeConfiguredDefaultReviewers)(parsedValue);
|