gaia-framework 1.87.0 → 1.105.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.
Files changed (26) hide show
  1. package/.claude/commands/gaia-ci-edit.md +17 -0
  2. package/CLAUDE.md +10 -0
  3. package/_gaia/_config/environment-presets.yaml +140 -0
  4. package/_gaia/_config/global.yaml +1 -1
  5. package/_gaia/_config/lifecycle-sequence.yaml +9 -0
  6. package/_gaia/_config/workflow-manifest.csv +1 -0
  7. package/_gaia/core/engine/workflow.xml +2 -0
  8. package/_gaia/core/validators/ci-edit-audit.js +181 -0
  9. package/_gaia/core/validators/ci-edit-test-env-scan.js +89 -0
  10. package/_gaia/core/validators/dev-story-security-controls.js +264 -0
  11. package/_gaia/core/validators/promotion-chain-env-resolver.js +140 -0
  12. package/_gaia/core/validators/test-environment-validator.js +292 -0
  13. package/_gaia/lifecycle/knowledge/brownfield/test-execution-scan.md +56 -9
  14. package/_gaia/lifecycle/workflows/4-implementation/create-story/instructions.xml +4 -0
  15. package/_gaia/lifecycle/workflows/4-implementation/dev-story/checklist.md +2 -0
  16. package/_gaia/lifecycle/workflows/4-implementation/dev-story/instructions.xml +94 -3
  17. package/_gaia/testing/workflows/ci-edit/checklist.md +41 -0
  18. package/_gaia/testing/workflows/ci-edit/instructions.xml +132 -0
  19. package/_gaia/testing/workflows/ci-edit/workflow.yaml +30 -0
  20. package/_gaia/testing/workflows/ci-setup/instructions.xml +75 -7
  21. package/_gaia/testing/workflows/test-gap-analysis/checklist.md +12 -0
  22. package/_gaia/testing/workflows/test-gap-analysis/instructions.xml +61 -2
  23. package/_gaia/testing/workflows/test-gap-analysis/workflow.yaml +2 -0
  24. package/gaia-install.sh +72 -76
  25. package/lib/copy-lib.sh +81 -0
  26. package/package.json +2 -1
@@ -0,0 +1,17 @@
1
+ ---
2
+ name: 'ci-edit'
3
+ description: 'Edit the ci_cd.promotion_chain in global.yaml — add, remove, edit, or reorder environments.'
4
+ model: sonnet
5
+ ---
6
+
7
+ IT IS CRITICAL THAT YOU FOLLOW THESE STEPS:
8
+
9
+ <steps CRITICAL="TRUE">
10
+ 1. LOAD the FULL {project-root}/_gaia/core/engine/workflow.xml
11
+ 2. READ its entire contents — this is the CORE OS
12
+ 3. Pass {project-root}/_gaia/testing/workflows/ci-edit/workflow.yaml as 'workflow-config'
13
+ 4. Follow workflow.xml instructions EXACTLY
14
+ 5. Save outputs after EACH section
15
+ </steps>
16
+
17
+ $ARGUMENTS
package/CLAUDE.md CHANGED
@@ -254,6 +254,16 @@ Publishing is fully automated — no manual steps required beyond creating the p
254
254
 
255
255
  After a release, verify: `npm view gaia-framework version`
256
256
 
257
+ ## Sprint Gate (Upgrade Protection)
258
+
259
+ The installer's `update` command includes a sprint gate that prevents framework upgrades while a sprint is active. Before any files are modified, the installer reads `docs/implementation-artifacts/sprint-status.yaml` and checks whether any story has status `in-progress`, `review`, or `ready-for-dev`.
260
+
261
+ - **Active sprint detected:** the upgrade halts with exit code 1 and a message identifying the sprint and the number of active stories.
262
+ - **No active sprint** (all stories `done` or `backlog`): the gate passes and the upgrade proceeds.
263
+ - **No sprint file:** the gate passes silently (fresh project or no sprint started).
264
+
265
+ To bypass the gate (not recommended): `gaia-install.sh update --skip-sprint-gate [target]`
266
+
257
267
  ## Do Not
258
268
 
259
269
  - Pre-load files — load at runtime when needed
@@ -0,0 +1,140 @@
1
+ # GAIA Environment Presets — E20-S2 (FR-245, ADR-033)
2
+ #
3
+ # User-facing entry point for promotion-chain configuration. Each preset is a
4
+ # complete, ready-to-use promotion_chain array that can be copied into
5
+ # global.yaml under `ci_cd.promotion_chain`. The presets are consumed by
6
+ # /gaia-ci-setup (E20-S3) which offers them during initial project setup.
7
+ #
8
+ # The schema of each promotion_chain entry is defined in architecture §10.24.1
9
+ # and enforced by the E20-S1 validator (test/validators/promotion-chain-validator.js).
10
+ # Required fields per entry: id, name, branch, ci_provider.
11
+ # Optional fields used here: merge_strategy, environment, test_tiers, auto_merge,
12
+ # approval_required, description, ci_checks.
13
+ #
14
+ # Test-tier convention (§10.24.3): additive — later environments include all
15
+ # earlier tiers plus additional ones (e.g., [1] -> [1, 2] -> [1, 2, 3]).
16
+ #
17
+ # Backward compatibility (NFR-045): this file is purely additive. Projects that
18
+ # do not set `ci_cd.promotion_chain` in global.yaml are unaffected.
19
+
20
+ # ─────────────────────────────────────────────────────────────────────────────
21
+ # solo — Solo developer shipping direct to main
22
+ # ─────────────────────────────────────────────────────────────────────────────
23
+ solo:
24
+ description: "Solo developer shipping direct to main. Single environment, auto-merge, full test coverage on the only branch."
25
+ promotion_chain:
26
+ - id: "prod"
27
+ name: "Production"
28
+ branch: "main"
29
+ environment: "production"
30
+ ci_provider: "github_actions"
31
+ merge_strategy: "squash"
32
+ test_tiers: [1, 2, 3]
33
+ auto_merge: true
34
+ description: "Solo preset — single environment on main. auto_merge is enabled because there is no upstream environment to promote from."
35
+
36
+ # ─────────────────────────────────────────────────────────────────────────────
37
+ # small-team — 2-5 developers with a single pre-prod gate
38
+ # ─────────────────────────────────────────────────────────────────────────────
39
+ small-team:
40
+ description: "Small team (2-5 developers) with a single pre-prod gate. Feature branches merge to develop for integration, then promote to main for release."
41
+ promotion_chain:
42
+ - id: "dev"
43
+ name: "Development"
44
+ branch: "develop"
45
+ environment: "development"
46
+ ci_provider: "github_actions"
47
+ merge_strategy: "squash"
48
+ test_tiers: [1]
49
+ auto_merge: false
50
+ description: "Integration branch for the small team. Runs tier-1 (unit + lint) on every feature merge."
51
+ - id: "prod"
52
+ name: "Production"
53
+ branch: "main"
54
+ environment: "production"
55
+ ci_provider: "github_actions"
56
+ merge_strategy: "squash"
57
+ test_tiers: [1, 2, 3]
58
+ auto_merge: false
59
+ description: "Release branch. Runs the full tier-1/2/3 suite before promotion from develop."
60
+
61
+ # ─────────────────────────────────────────────────────────────────────────────
62
+ # standard — Standard three-tier flow (dev -> staging -> prod)
63
+ # ─────────────────────────────────────────────────────────────────────────────
64
+ standard:
65
+ description: "Standard three-tier flow for most product teams. Feature branches land on develop, promote to staging for pre-prod validation, then promote to main for release."
66
+ promotion_chain:
67
+ - id: "dev"
68
+ name: "Development"
69
+ branch: "develop"
70
+ environment: "development"
71
+ ci_provider: "github_actions"
72
+ merge_strategy: "squash"
73
+ test_tiers: [1]
74
+ auto_merge: false
75
+ description: "Integration branch. Tier-1 tests run on every feature merge."
76
+ - id: "staging"
77
+ name: "Staging"
78
+ branch: "staging"
79
+ environment: "staging"
80
+ ci_provider: "github_actions"
81
+ merge_strategy: "squash"
82
+ test_tiers: [1, 2]
83
+ auto_merge: false
84
+ description: "Pre-production validation. Adds tier-2 (integration) tests on top of tier-1."
85
+ - id: "prod"
86
+ name: "Production"
87
+ branch: "main"
88
+ environment: "production"
89
+ ci_provider: "github_actions"
90
+ merge_strategy: "squash"
91
+ test_tiers: [1, 2, 3]
92
+ auto_merge: false
93
+ description: "Production release. Runs the full tier-1/2/3 suite before promotion from staging."
94
+
95
+ # ─────────────────────────────────────────────────────────────────────────────
96
+ # enterprise — Four-stage flow with dedicated UAT and approval gates
97
+ # ─────────────────────────────────────────────────────────────────────────────
98
+ enterprise:
99
+ description: "Enterprise four-stage flow with a dedicated UAT environment and mandatory approval gates on staging and production. Uses merge (not squash) to preserve individual commit history for audit trails."
100
+ promotion_chain:
101
+ - id: "dev"
102
+ name: "Development"
103
+ branch: "develop"
104
+ environment: "development"
105
+ ci_provider: "github_actions"
106
+ merge_strategy: "merge"
107
+ test_tiers: [1]
108
+ auto_merge: false
109
+ approval_required: false
110
+ description: "Integration branch. Tier-1 tests (unit + lint) on every feature merge."
111
+ - id: "uat"
112
+ name: "User Acceptance Testing"
113
+ branch: "uat"
114
+ environment: "uat"
115
+ ci_provider: "github_actions"
116
+ merge_strategy: "merge"
117
+ test_tiers: [1, 2]
118
+ auto_merge: false
119
+ approval_required: false
120
+ description: "User acceptance testing environment. Adds tier-2 integration tests. Business stakeholders validate features here before staging promotion."
121
+ - id: "staging"
122
+ name: "Staging"
123
+ branch: "staging"
124
+ environment: "staging"
125
+ ci_provider: "github_actions"
126
+ merge_strategy: "merge"
127
+ test_tiers: [1, 2, 3]
128
+ auto_merge: false
129
+ approval_required: true
130
+ description: "Pre-production staging. Runs the full tier-1/2/3 suite. Requires explicit approval before promotion — enforced by branch protection."
131
+ - id: "prod"
132
+ name: "Production"
133
+ branch: "main"
134
+ environment: "production"
135
+ ci_provider: "github_actions"
136
+ merge_strategy: "merge"
137
+ test_tiers: [1, 2, 3]
138
+ auto_merge: false
139
+ approval_required: true
140
+ description: "Production release. Requires explicit approval before promotion from staging — enforced by branch protection and a release manager sign-off."
@@ -3,7 +3,7 @@
3
3
  # After modifying this file, run /gaia-build-configs to regenerate resolved configs.
4
4
 
5
5
  framework_name: "GAIA"
6
- framework_version: "1.87.0"
6
+ framework_version: "1.105.0"
7
7
 
8
8
  # User settings
9
9
  user_name: "jlouage"
@@ -531,6 +531,15 @@ sequence:
531
531
  next:
532
532
  primary: /gaia-readiness-check
533
533
 
534
+ ci-edit:
535
+ module: testing
536
+ command: /gaia-ci-edit
537
+ next:
538
+ standalone: true
539
+ suggestions:
540
+ - command: /gaia-build-configs
541
+ context: "Regenerate resolved configs after editing the promotion chain"
542
+
534
543
  atdd:
535
544
  module: testing
536
545
  command: /gaia-atdd
@@ -40,6 +40,7 @@ name,displayName,description,module,phase,path,command,agent
40
40
  "test-design","Test Design","Create risk-based test plans","testing","anytime","_gaia/testing/workflows/test-design/workflow.yaml","gaia-test-design","test-architect"
41
41
  "test-framework","Test Framework","Initialize test framework","testing","anytime","_gaia/testing/workflows/test-framework/workflow.yaml","gaia-test-framework","test-architect"
42
42
  "ci-setup","CI Setup","Scaffold CI/CD quality pipeline","testing","anytime","_gaia/testing/workflows/ci-setup/workflow.yaml","gaia-ci-setup","test-architect"
43
+ "ci-edit","CI Edit","Edit ci_cd.promotion_chain in global.yaml (add/remove/edit/reorder environments)","testing","anytime","_gaia/testing/workflows/ci-edit/workflow.yaml","gaia-ci-edit","test-architect"
43
44
  "atdd","ATDD","Generate failing acceptance tests","testing","anytime","_gaia/testing/workflows/atdd/workflow.yaml","gaia-atdd","test-architect"
44
45
  "test-automation","Test Automation","Expand automated test coverage","testing","anytime","_gaia/testing/workflows/test-automation/workflow.yaml","gaia-test-automate","test-architect"
45
46
  "test-review","Test Review","Review test quality","testing","anytime","_gaia/testing/workflows/test-review/workflow.yaml","gaia-test-review","test-architect"
@@ -51,6 +51,8 @@ execution modes (normal/yolo/planning), checkpoints, and quality gates.
51
51
  <action>Resolve {date} to current date</action>
52
52
  <action>Ask user for any remaining unresolved variables</action>
53
53
 
54
+ <!-- ci_cd backward-compat tolerance (E20-S11 / NFR-045 / ADR-033) — Config resolution treats a missing or empty ci_cd block as a no-op: no defaults are injected, no warning is emitted, and downstream workflows that depend on ci_cd.promotion_chain MUST consult the canonical chainPresent() predicate in scripts/lib/promotion-chain-guard.js rather than re-implementing the absent-variant check. -->
55
+
54
56
  <!-- Template Resolution (ADR-020, FR-101) — custom/templates/ overrides _gaia/lifecycle/templates/ -->
55
57
  <!-- Resolution order for template reads: custom/templates/{filename} > _gaia/lifecycle/templates/{filename} -->
56
58
  <!-- Resolution order for template writes: custom/templates/ ONLY — NEVER _gaia/lifecycle/templates/ -->
@@ -0,0 +1,181 @@
1
+ /**
2
+ * /gaia-ci-edit Audit Trail (E20-S13 AC5)
3
+ *
4
+ * Writes an audit checkpoint every time a user adds, removes, reorders, or
5
+ * modifies an entry in `ci_cd.promotion_chain`. The checkpoint format
6
+ * follows the existing `_memory/checkpoints/` YAML conventions so
7
+ * `/gaia-resume` and future audit tooling can discover and replay the
8
+ * history deterministically.
9
+ *
10
+ * Checkpoint schema:
11
+ * user: <string>
12
+ * timestamp: <ISO 8601 UTC>
13
+ * operation: add|remove|reorder|modify
14
+ * before_state: <full promotion_chain array before the edit>
15
+ * after_state: <full promotion_chain array after the edit>
16
+ * diff_summary: <human-readable change list: added/removed/modified/reordered>
17
+ *
18
+ * Design principles:
19
+ * - Deterministic filenames: `ci-edit-<iso-8601>.yaml` with colons
20
+ * replaced by hyphens so the name is filesystem-safe on every OS.
21
+ * - No external YAML dependency — the checkpoint writer emits a small,
22
+ * strict YAML subset. Round-tripping through a real YAML parser is
23
+ * covered in the ci-edit test suite; this module stays dependency-free.
24
+ * - Pure function at the edges: the filesystem write is the only side
25
+ * effect, and the caller provides `checkpointDir` and `now`.
26
+ *
27
+ * @module ci-edit-audit
28
+ */
29
+
30
+ import { writeFileSync, mkdirSync } from "node:fs";
31
+ import { join } from "node:path";
32
+
33
+ /**
34
+ * Build the filesystem-safe ISO-8601 timestamp component.
35
+ * @param {Date} date
36
+ * @returns {string} e.g., "2026-04-08T09-15-30Z"
37
+ */
38
+ function buildTimestampSlug(date) {
39
+ return date.toISOString().replace(/:/g, "-").replace(/\.\d{3}Z$/, "Z");
40
+ }
41
+
42
+ /**
43
+ * Compute a human-readable diff summary between two promotion_chain arrays.
44
+ *
45
+ * - Entries present only in `after` → added
46
+ * - Entries present only in `before` → removed
47
+ * - Entries present in both with mutated contents → modified
48
+ * - Entries present in both identical but in different positions → reordered
49
+ *
50
+ * @param {Array<object>} before
51
+ * @param {Array<object>} after
52
+ * @returns {{added:string[], removed:string[], modified:string[], reordered:boolean}}
53
+ */
54
+ export function computeDiffSummary(before, after) {
55
+ const beforeList = Array.isArray(before) ? before : [];
56
+ const afterList = Array.isArray(after) ? after : [];
57
+ const beforeById = new Map(beforeList.map((e) => [e?.id, e]));
58
+ const afterById = new Map(afterList.map((e) => [e?.id, e]));
59
+
60
+ const added = [];
61
+ const removed = [];
62
+ const modified = [];
63
+
64
+ for (const [id, afterEntry] of afterById.entries()) {
65
+ if (!beforeById.has(id)) {
66
+ added.push(id);
67
+ continue;
68
+ }
69
+ const beforeEntry = beforeById.get(id);
70
+ if (JSON.stringify(beforeEntry) !== JSON.stringify(afterEntry)) {
71
+ modified.push(id);
72
+ }
73
+ }
74
+ for (const id of beforeById.keys()) {
75
+ if (!afterById.has(id)) removed.push(id);
76
+ }
77
+
78
+ // Reorder detection: same set of ids, different ordering, no content changes.
79
+ let reordered = false;
80
+ if (added.length === 0 && removed.length === 0 && modified.length === 0) {
81
+ const beforeOrder = beforeList.map((e) => e?.id).join("|");
82
+ const afterOrder = afterList.map((e) => e?.id).join("|");
83
+ if (beforeOrder !== afterOrder) reordered = true;
84
+ }
85
+
86
+ return { added, removed, modified, reordered };
87
+ }
88
+
89
+ /**
90
+ * Minimal YAML emitter for the audit checkpoint. Handles strings, numbers,
91
+ * booleans, arrays, and nested objects — enough for promotion_chain entries.
92
+ *
93
+ * @param {*} value
94
+ * @param {number} indent
95
+ * @returns {string}
96
+ */
97
+ function emitYaml(value, indent = 0) {
98
+ const pad = " ".repeat(indent);
99
+ if (value === null || value === undefined) return "null";
100
+ if (typeof value === "string") {
101
+ // Quote if the string contains YAML-significant characters.
102
+ if (/[:#\-?\[\]{}&*!|>'"%@`,\n]/.test(value) || value === "") {
103
+ return JSON.stringify(value);
104
+ }
105
+ return value;
106
+ }
107
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
108
+ if (Array.isArray(value)) {
109
+ if (value.length === 0) return "[]";
110
+ return value
111
+ .map((item) => {
112
+ if (item && typeof item === "object" && !Array.isArray(item)) {
113
+ const inner = emitYaml(item, indent + 1)
114
+ .split("\n")
115
+ .map((line, idx) => (idx === 0 ? line : `${pad} ${line}`))
116
+ .join("\n");
117
+ return `${pad}- ${inner}`;
118
+ }
119
+ return `${pad}- ${emitYaml(item, indent + 1)}`;
120
+ })
121
+ .join("\n");
122
+ }
123
+ if (typeof value === "object") {
124
+ const entries = Object.entries(value);
125
+ if (entries.length === 0) return "{}";
126
+ return entries
127
+ .map(([k, v]) => {
128
+ if (v && typeof v === "object") {
129
+ if (Array.isArray(v) && v.length === 0) return `${pad}${k}: []`;
130
+ if (!Array.isArray(v) && Object.keys(v).length === 0) return `${pad}${k}: {}`;
131
+ const nested = emitYaml(v, indent + 1);
132
+ return `${pad}${k}:\n${nested}`;
133
+ }
134
+ return `${pad}${k}: ${emitYaml(v, indent + 1)}`;
135
+ })
136
+ .join("\n");
137
+ }
138
+ return JSON.stringify(value);
139
+ }
140
+
141
+ /**
142
+ * Write a /gaia-ci-edit audit checkpoint to disk.
143
+ *
144
+ * @param {{
145
+ * operation: "add"|"remove"|"reorder"|"modify",
146
+ * user: string,
147
+ * beforeState: Array<object>,
148
+ * afterState: Array<object>,
149
+ * checkpointDir: string,
150
+ * now?: Date,
151
+ * }} params
152
+ * @returns {string} Absolute path to the written checkpoint file.
153
+ */
154
+ export function writeCiEditAuditCheckpoint(params) {
155
+ const { operation, user, beforeState, afterState, checkpointDir, now } = params || {};
156
+ if (!checkpointDir || typeof checkpointDir !== "string") {
157
+ throw new Error("writeCiEditAuditCheckpoint: checkpointDir is required");
158
+ }
159
+
160
+ const stamp = now instanceof Date ? now : new Date();
161
+ const timestamp = stamp.toISOString();
162
+ const slug = buildTimestampSlug(stamp);
163
+ const filename = `ci-edit-${slug}.yaml`;
164
+
165
+ const diffSummary = computeDiffSummary(beforeState, afterState);
166
+
167
+ const payload = {
168
+ workflow: "ci-edit",
169
+ user: user || "unknown",
170
+ timestamp,
171
+ operation,
172
+ before_state: beforeState || [],
173
+ after_state: afterState || [],
174
+ diff_summary: diffSummary,
175
+ };
176
+
177
+ mkdirSync(checkpointDir, { recursive: true });
178
+ const filePath = join(checkpointDir, filename);
179
+ writeFileSync(filePath, emitYaml(payload) + "\n", "utf8");
180
+ return filePath;
181
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * ci-edit Remove Safety Scan — test-environment.yaml target (E20-S10, AC5)
3
+ *
4
+ * Scans a `test-environment.yaml` payload for tier entries that reference a
5
+ * given promotion chain environment id via `promotion_chain_env_id`, so the
6
+ * `/gaia-ci-edit` remove operation (E20-S4) can surface impacted tiers and
7
+ * warn the user before deleting an environment from `ci_cd.promotion_chain`.
8
+ *
9
+ * This module intentionally does NOT rely on a full YAML parser — it uses a
10
+ * minimal line-based scanner tuned to the manifest schema (flat runner list
11
+ * with scalar fields). This keeps the scan dependency-free and resilient to
12
+ * partial manifests.
13
+ *
14
+ * Architecture references:
15
+ * - ADR-033: Multi-Environment Promotion Chain
16
+ * - §10.24.4: /gaia-ci-edit cascade updates
17
+ *
18
+ * @module ci-edit-test-env-scan
19
+ */
20
+
21
+ /**
22
+ * Scan a test-environment.yaml content string for tier entries that reference
23
+ * the given environment id.
24
+ *
25
+ * @param {string|null|undefined} content — The raw YAML content.
26
+ * @param {string} targetEnvId — The id being removed from the promotion chain.
27
+ * @returns {string[]} List of tier/runner names that reference the id. Empty
28
+ * list if nothing matches, the content is empty, or the target id is falsy.
29
+ */
30
+ export function scanTestEnvironmentForEnvId(content, targetEnvId) {
31
+ if (!content || typeof content !== "string") return [];
32
+ if (!targetEnvId) return [];
33
+
34
+ const lines = content.split("\n");
35
+ const references = [];
36
+ let currentRunnerName = null;
37
+
38
+ for (const rawLine of lines) {
39
+ // Strip trailing comments and whitespace
40
+ const line = rawLine.replace(/#.*$/, "").trimEnd();
41
+ if (line.trim() === "") continue;
42
+
43
+ const trimmed = line.trimStart();
44
+
45
+ // Detect a new runner entry: "- name: <value>"
46
+ const runnerNameMatch = trimmed.match(/^-\s+name:\s*(.+)$/);
47
+ if (runnerNameMatch) {
48
+ currentRunnerName = stripQuotes(runnerNameMatch[1].trim());
49
+ continue;
50
+ }
51
+
52
+ // Detect an indented "name:" line for the current runner (alternative form)
53
+ const altNameMatch = trimmed.match(/^name:\s*(.+)$/);
54
+ if (altNameMatch && !trimmed.startsWith("- ")) {
55
+ // Only honor if we're already inside a runner block (indent > 0)
56
+ const indent = line.length - line.trimStart().length;
57
+ if (indent > 0) {
58
+ currentRunnerName = stripQuotes(altNameMatch[1].trim());
59
+ }
60
+ continue;
61
+ }
62
+
63
+ // Detect "promotion_chain_env_id: <value>" within the current runner
64
+ const envIdMatch = trimmed.match(/^promotion_chain_env_id:\s*(.+)$/);
65
+ if (envIdMatch && currentRunnerName) {
66
+ const value = stripQuotes(envIdMatch[1].trim());
67
+ if (value === targetEnvId) {
68
+ references.push(currentRunnerName);
69
+ }
70
+ }
71
+ }
72
+
73
+ return references;
74
+ }
75
+
76
+ /**
77
+ * Strip surrounding single or double quotes from a YAML scalar value.
78
+ * @param {string} value
79
+ * @returns {string}
80
+ */
81
+ function stripQuotes(value) {
82
+ if (
83
+ (value.startsWith('"') && value.endsWith('"')) ||
84
+ (value.startsWith("'") && value.endsWith("'"))
85
+ ) {
86
+ return value.slice(1, -1);
87
+ }
88
+ return value;
89
+ }