@hegemonart/get-design-done 1.28.8 → 1.30.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/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +81 -0
- package/README.de.md +23 -0
- package/README.fr.md +23 -0
- package/README.it.md +23 -0
- package/README.ja.md +23 -0
- package/README.ko.md +23 -0
- package/README.md +28 -0
- package/README.zh-CN.md +23 -0
- package/SKILL.md +2 -0
- package/agents/design-reflector.md +50 -0
- package/package.json +1 -1
- package/reference/capability-gap-stage-gate.md +261 -0
- package/reference/known-failure-modes.md +185 -0
- package/reference/pseudonymization-rules.md +189 -0
- package/reference/registry.json +22 -1
- package/reference/schemas/events.schema.json +97 -3
- package/reference/schemas/generated.d.ts +319 -4
- package/scripts/cli/gdd-events.mjs +35 -2
- package/scripts/gsd-cleanup-incubator.cjs +367 -0
- package/scripts/lib/apply-reflections/incubator-proposals.cjs +448 -0
- package/scripts/lib/bandit-router.cjs +92 -9
- package/scripts/lib/gsd-health-mirror/index.cjs +37 -1
- package/scripts/lib/incubator-author.cjs +845 -0
- package/scripts/lib/issue-reporter/cli-flag-report.cjs +153 -0
- package/scripts/lib/issue-reporter/consent-prompt.cjs +231 -0
- package/scripts/lib/issue-reporter/dedup.cjs +458 -0
- package/scripts/lib/issue-reporter/destination.cjs +37 -0
- package/scripts/lib/issue-reporter/draft-writer.cjs +157 -0
- package/scripts/lib/issue-reporter/gh-absent-fallback.cjs +220 -0
- package/scripts/lib/issue-reporter/gh-submit.cjs +114 -0
- package/scripts/lib/issue-reporter/kill-switch.cjs +122 -0
- package/scripts/lib/issue-reporter/payload-assembly.cjs +367 -0
- package/scripts/lib/issue-reporter/privacy-diff.cjs +385 -0
- package/scripts/lib/issue-reporter/report-flow.cjs +269 -0
- package/scripts/lib/issue-reporter/triage-matcher.cjs +270 -0
- package/scripts/lib/pseudonymize.cjs +444 -0
- package/scripts/lib/reflections-cycle-writer.cjs +172 -0
- package/scripts/lib/reflector/capability-gap-scan.cjs +751 -0
- package/scripts/lib/reflector-capability-gap-aggregator.cjs +320 -0
- package/scripts/release-smoke-test.cjs +33 -2
- package/scripts/validate-incubator-scope.cjs +133 -0
- package/skills/apply-reflections/SKILL.md +16 -1
- package/skills/apply-reflections/apply-reflections-procedure.md +71 -3
- package/skills/fast/SKILL.md +46 -0
- package/skills/reflect/SKILL.md +9 -0
- package/skills/reflect/procedures/capability-gap-scan.md +120 -0
- package/skills/report-issue/SKILL.md +53 -0
- package/skills/report-issue/report-issue-procedure.md +120 -0
- package/skills/router/SKILL.md +5 -0
- package/skills/router/capability-gap-emitter.md +65 -0
- package/skills/update/SKILL.md +3 -2
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* cli-flag-report.cjs — Plan 30-04 D-11 `--report` flag plumbing.
|
|
4
|
+
*
|
|
5
|
+
* The `--report` flag is intentionally NOT available on every command.
|
|
6
|
+
* Per D-11 it is whitelisted to specific failure modes catalogued in
|
|
7
|
+
* `reference/known-failure-modes.md` with `propose_report: true`. The
|
|
8
|
+
* matcher (30-03) does not act on this flag; this module is where it
|
|
9
|
+
* gates which commands get the flag at all.
|
|
10
|
+
*
|
|
11
|
+
* The schema for known-failure-modes.md does NOT include a per-mode
|
|
12
|
+
* `command` field — the catalogue is regex-based, not command-keyed.
|
|
13
|
+
* The whitelist therefore lives here as an explicit set of command
|
|
14
|
+
* names whose plausible failures map to one or more propose_report=true
|
|
15
|
+
* entries. Adding a command to the whitelist is a deliberate maintainer
|
|
16
|
+
* choice; we do not auto-derive it to avoid surprising users with new
|
|
17
|
+
* `--report` flags appearing as the catalogue grows.
|
|
18
|
+
*
|
|
19
|
+
* Today (matching KFM-008 MCP unreachable + KFM-009 plugin file missing):
|
|
20
|
+
* - `gdd:plan-phase` — typically the first MCP-touching command
|
|
21
|
+
* - `gdd:execute-phase` — typically the first plugin-file-touching command
|
|
22
|
+
* - `gdd:report-issue` — the flag is meaningful on itself (force flow)
|
|
23
|
+
*
|
|
24
|
+
* Commands NOT on the whitelist silently do not see the flag —
|
|
25
|
+
* non-whitelisted argv parsing returns `{ report: false }` regardless
|
|
26
|
+
* of whether the user typed --report.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const { listProposeReportModes } = require('./triage-matcher.cjs');
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Command whitelist — derived from CONTEXT D-11 + KFM-008/KFM-009 modes.
|
|
33
|
+
* Frozen to prevent accidental mutation at runtime.
|
|
34
|
+
*
|
|
35
|
+
* The intersection with listProposeReportModes() is checked at
|
|
36
|
+
* isReportFlagWhitelisted call time: if the catalogue is missing
|
|
37
|
+
* propose_report=true entries entirely, the flag is unavailable
|
|
38
|
+
* everywhere (defensive default).
|
|
39
|
+
*/
|
|
40
|
+
const COMMAND_WHITELIST = Object.freeze(new Set([
|
|
41
|
+
'gdd:plan-phase',
|
|
42
|
+
'gdd:execute-phase',
|
|
43
|
+
'gdd:report-issue',
|
|
44
|
+
]));
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @param {string} commandName
|
|
48
|
+
* @param {{ listFn?: typeof listProposeReportModes }} [opts]
|
|
49
|
+
* @returns {boolean}
|
|
50
|
+
*/
|
|
51
|
+
function isReportFlagWhitelisted(commandName, opts) {
|
|
52
|
+
if (typeof commandName !== 'string' || commandName.length === 0) return false;
|
|
53
|
+
if (!COMMAND_WHITELIST.has(commandName)) return false;
|
|
54
|
+
const listFn = (opts && opts.listFn) || listProposeReportModes;
|
|
55
|
+
let modes;
|
|
56
|
+
try {
|
|
57
|
+
modes = listFn();
|
|
58
|
+
} catch {
|
|
59
|
+
modes = [];
|
|
60
|
+
}
|
|
61
|
+
// Defensive: if the catalogue lost all propose_report=true entries,
|
|
62
|
+
// the flag is unavailable everywhere.
|
|
63
|
+
return Array.isArray(modes) && modes.length > 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Install --report on the given command-line parser ONLY if the command
|
|
68
|
+
* is whitelisted. The function adapts to two parser shapes:
|
|
69
|
+
*
|
|
70
|
+
* yargs-style: parser.option('report', { type: 'boolean', describe: ... })
|
|
71
|
+
* commander-style: parser.option('--report', '...', false)
|
|
72
|
+
*
|
|
73
|
+
* Non-whitelisted commands → no-op. Returns true if the flag was
|
|
74
|
+
* installed, false otherwise.
|
|
75
|
+
*
|
|
76
|
+
* @param {object} parser
|
|
77
|
+
* @param {string} commandName
|
|
78
|
+
* @param {{ listFn?: typeof listProposeReportModes }} [opts]
|
|
79
|
+
* @returns {boolean}
|
|
80
|
+
*/
|
|
81
|
+
function installReportFlagOn(parser, commandName, opts) {
|
|
82
|
+
if (!isReportFlagWhitelisted(commandName, opts)) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
if (parser == null) return false;
|
|
86
|
+
// Try yargs-style first (named option with a config object).
|
|
87
|
+
if (typeof parser.option === 'function') {
|
|
88
|
+
try {
|
|
89
|
+
parser.option('report', {
|
|
90
|
+
type: 'boolean',
|
|
91
|
+
default: false,
|
|
92
|
+
describe:
|
|
93
|
+
'Propose a GitHub issue draft after a failure (D-11 whitelisted; consent required — no auto-submit).',
|
|
94
|
+
});
|
|
95
|
+
return true;
|
|
96
|
+
} catch {
|
|
97
|
+
// fall through to commander-style
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
parser.option(
|
|
101
|
+
'--report',
|
|
102
|
+
'Propose a GitHub issue draft after a failure (D-11 whitelisted; consent required — no auto-submit).',
|
|
103
|
+
false
|
|
104
|
+
);
|
|
105
|
+
return true;
|
|
106
|
+
} catch {
|
|
107
|
+
// fall through
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Parse `--report` out of an argv array.
|
|
115
|
+
*
|
|
116
|
+
* Cheap parser used by tests and by any caller that just wants to know
|
|
117
|
+
* whether the flag was passed. Non-whitelisted commands ALWAYS return
|
|
118
|
+
* `false` regardless of what's in argv — the flag is unavailable to them.
|
|
119
|
+
*
|
|
120
|
+
* @param {string} commandName
|
|
121
|
+
* @param {string[]} argv
|
|
122
|
+
* @param {{ listFn?: typeof listProposeReportModes }} [opts]
|
|
123
|
+
* @returns {{ report: boolean, forceReport: boolean }}
|
|
124
|
+
*/
|
|
125
|
+
function parseReportFlag(commandName, argv, opts) {
|
|
126
|
+
const whitelisted = isReportFlagWhitelisted(commandName, opts);
|
|
127
|
+
let report = false;
|
|
128
|
+
let forceReport = false;
|
|
129
|
+
if (Array.isArray(argv)) {
|
|
130
|
+
for (const a of argv) {
|
|
131
|
+
if (a === '--report') report = true;
|
|
132
|
+
else if (a === '--force-report') forceReport = true;
|
|
133
|
+
else if (typeof a === 'string' && a.startsWith('--report=')) {
|
|
134
|
+
const v = a.slice('--report='.length).toLowerCase();
|
|
135
|
+
report = v === 'true' || v === '1' || v === 'yes';
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (!whitelisted) {
|
|
140
|
+
// Whitelist gate: even if the user typed --report, the command does
|
|
141
|
+
// not expose it. Force-report is independent: it's a global modifier
|
|
142
|
+
// for the explicit /gdd:report-issue command path.
|
|
143
|
+
return { report: false, forceReport: forceReport && commandName === 'gdd:report-issue' };
|
|
144
|
+
}
|
|
145
|
+
return { report, forceReport };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
module.exports = {
|
|
149
|
+
installReportFlagOn,
|
|
150
|
+
isReportFlagWhitelisted,
|
|
151
|
+
parseReportFlag,
|
|
152
|
+
COMMAND_WHITELIST,
|
|
153
|
+
};
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* consent-prompt.cjs — Plan 30-04 D-03 consent gate.
|
|
4
|
+
*
|
|
5
|
+
* The ONE place where the user explicitly says "yes" before /gdd:report-issue
|
|
6
|
+
* submits anything. There is no auto-mode and no env-var bypass:
|
|
7
|
+
*
|
|
8
|
+
* - Refuses (throws) if stdin is not a TTY (no piped 'y' from CI, no
|
|
9
|
+
* non-interactive shells).
|
|
10
|
+
* - Refuses (throws) if any process.env key matches /REPORT|ISSUE|AUTO_REPORT/i
|
|
11
|
+
* AND has a truthy value (runtime belt + suspenders for the static test
|
|
12
|
+
* in tests/report-issue-no-auto-submit-static.test.cjs).
|
|
13
|
+
* - Treats any answer other than literal `y`/`yes` (case-insensitive) as
|
|
14
|
+
* decline.
|
|
15
|
+
*
|
|
16
|
+
* After (optionally) opening the user's $EDITOR on the draft, RE-READS the
|
|
17
|
+
* draft from disk and returns the re-read title + body. This is what makes
|
|
18
|
+
* "edit before submit" work: the editor exit handler does not bind the
|
|
19
|
+
* content; the content is freshly loaded from the file path on every entry.
|
|
20
|
+
*
|
|
21
|
+
* EDITOR is a POSIX-standard user env (used by git, crontab, gh itself). It
|
|
22
|
+
* is intentionally NOT in the forbidden list — the static test only blocks
|
|
23
|
+
* names matching /REPORT|ISSUE|AUTO_REPORT/i.
|
|
24
|
+
*
|
|
25
|
+
* Pure dependencies: readline, child_process.spawnSync. No `fetch`, no
|
|
26
|
+
* `https`, no third-party packages.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const fs = require('node:fs');
|
|
30
|
+
const readline = require('node:readline');
|
|
31
|
+
const { spawnSync } = require('node:child_process');
|
|
32
|
+
|
|
33
|
+
const { DESTINATION_REPO } = require('./destination.cjs');
|
|
34
|
+
const { readDraft } = require('./draft-writer.cjs');
|
|
35
|
+
|
|
36
|
+
const FORBIDDEN_ENV_RE = /(REPORT|ISSUE|AUTO_REPORT)/i;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Scan process.env for any auto-submit bypass env var with a truthy value.
|
|
40
|
+
* If found, throws — D-03 runtime gate counterpart to the static test.
|
|
41
|
+
*
|
|
42
|
+
* Implementation note: we iterate Object.keys(process.env) so we can match
|
|
43
|
+
* substrings (REPORT, ISSUE, AUTO_REPORT). We never directly read a
|
|
44
|
+
* specific named env var here — that would itself fail the static-grep
|
|
45
|
+
* test in tests/report-issue-no-auto-submit-static.test.cjs (which only
|
|
46
|
+
* forbids `process.env.NAME` patterns).
|
|
47
|
+
*
|
|
48
|
+
* Truthy means: present, non-empty, not literal "0" / "false" / "no".
|
|
49
|
+
*
|
|
50
|
+
* @param {NodeJS.ProcessEnv} [env] — injection point for tests
|
|
51
|
+
*/
|
|
52
|
+
function rejectBypassEnv(env) {
|
|
53
|
+
const source = env || process.env;
|
|
54
|
+
const offenders = [];
|
|
55
|
+
for (const key of Object.keys(source)) {
|
|
56
|
+
if (!FORBIDDEN_ENV_RE.test(key)) continue;
|
|
57
|
+
const value = source[key];
|
|
58
|
+
if (value == null) continue;
|
|
59
|
+
const s = String(value).trim().toLowerCase();
|
|
60
|
+
if (s === '' || s === '0' || s === 'false' || s === 'no') continue;
|
|
61
|
+
offenders.push(key);
|
|
62
|
+
}
|
|
63
|
+
if (offenders.length > 0) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
`refused: env var ${offenders.join(', ')} detected; /gdd:report-issue has no auto-mode by design (D-03)`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Open the user's $EDITOR on the draft file, blocking until it exits.
|
|
72
|
+
*
|
|
73
|
+
* @param {string} draftPath
|
|
74
|
+
* @param {NodeJS.ProcessEnv} [env]
|
|
75
|
+
* @returns {boolean} — true if an editor was spawned, false if EDITOR unset
|
|
76
|
+
*/
|
|
77
|
+
function openInEditor(draftPath, env) {
|
|
78
|
+
const source = env || process.env;
|
|
79
|
+
const editor = source.EDITOR;
|
|
80
|
+
if (typeof editor !== 'string' || editor.trim().length === 0) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
// Whole-string command: split on first space for editor + args.
|
|
84
|
+
const parts = editor.trim().split(/\s+/);
|
|
85
|
+
const cmd = parts[0];
|
|
86
|
+
const args = parts.slice(1).concat([draftPath]);
|
|
87
|
+
spawnSync(cmd, args, { stdio: 'inherit' });
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Ask the user the single y/N question via readline. EOF / empty input
|
|
93
|
+
* counts as decline.
|
|
94
|
+
*
|
|
95
|
+
* @param {NodeJS.ReadableStream} input
|
|
96
|
+
* @param {NodeJS.WritableStream} output
|
|
97
|
+
* @returns {Promise<string>} — raw answer string
|
|
98
|
+
*/
|
|
99
|
+
function askYesNo(input, output) {
|
|
100
|
+
return new Promise((resolve) => {
|
|
101
|
+
const rl = readline.createInterface({ input, output, terminal: false });
|
|
102
|
+
let answered = false;
|
|
103
|
+
const finish = (value) => {
|
|
104
|
+
if (answered) return;
|
|
105
|
+
answered = true;
|
|
106
|
+
try { rl.close(); } catch { /* noop */ }
|
|
107
|
+
resolve(value);
|
|
108
|
+
};
|
|
109
|
+
rl.question(
|
|
110
|
+
`Submit this issue to ${DESTINATION_REPO}? [y/N] `,
|
|
111
|
+
(answer) => finish(typeof answer === 'string' ? answer.trim() : '')
|
|
112
|
+
);
|
|
113
|
+
rl.on('close', () => finish(''));
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function isAffirmative(answer) {
|
|
118
|
+
if (typeof answer !== 'string') return false;
|
|
119
|
+
const a = answer.trim().toLowerCase();
|
|
120
|
+
return a === 'y' || a === 'yes';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Build the human-facing summary printed before the y/N question.
|
|
125
|
+
*
|
|
126
|
+
* @param {{ title: string, body: string }} draft
|
|
127
|
+
* @param {string} draftPath
|
|
128
|
+
* @returns {string}
|
|
129
|
+
*/
|
|
130
|
+
function buildSummary(draft, draftPath) {
|
|
131
|
+
const head = String(draft.body || '').split(/\r?\n/).slice(0, 10).join('\n');
|
|
132
|
+
return [
|
|
133
|
+
'',
|
|
134
|
+
'--- /gdd:report-issue draft summary ---',
|
|
135
|
+
`Destination: ${DESTINATION_REPO}`,
|
|
136
|
+
`Draft path: ${draftPath}`,
|
|
137
|
+
`Title: ${draft.title}`,
|
|
138
|
+
'Body (first 10 lines):',
|
|
139
|
+
head,
|
|
140
|
+
'---',
|
|
141
|
+
'',
|
|
142
|
+
].join('\n');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* The full consent flow.
|
|
147
|
+
*
|
|
148
|
+
* 1. Reject bypass env vars (throw).
|
|
149
|
+
* 2. Reject non-TTY stdin (throw).
|
|
150
|
+
* 3. (Optional) open $EDITOR on the draft and wait.
|
|
151
|
+
* 4. Re-read the draft from disk → final {title, body}.
|
|
152
|
+
* 5. Print summary.
|
|
153
|
+
* 6. Ask y/N via readline.
|
|
154
|
+
* 7. Return { consented, finalTitle, finalBody }.
|
|
155
|
+
*
|
|
156
|
+
* @param {{
|
|
157
|
+
* draftPath: string,
|
|
158
|
+
* openEditor?: boolean,
|
|
159
|
+
* stdin?: NodeJS.ReadableStream,
|
|
160
|
+
* stdout?: NodeJS.WritableStream,
|
|
161
|
+
* env?: NodeJS.ProcessEnv,
|
|
162
|
+
* askYesNo?: typeof askYesNo,
|
|
163
|
+
* openInEditor?: typeof openInEditor
|
|
164
|
+
* }} opts
|
|
165
|
+
* @returns {Promise<{ consented: boolean, finalTitle: string, finalBody: string }>}
|
|
166
|
+
*/
|
|
167
|
+
async function promptConsent(opts) {
|
|
168
|
+
if (opts == null || typeof opts !== 'object') {
|
|
169
|
+
throw new Error('promptConsent: opts object required');
|
|
170
|
+
}
|
|
171
|
+
const draftPath = opts.draftPath;
|
|
172
|
+
if (typeof draftPath !== 'string' || draftPath.length === 0) {
|
|
173
|
+
throw new Error('promptConsent: draftPath required');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Step 1: runtime env-var bypass gate (D-03).
|
|
177
|
+
rejectBypassEnv(opts.env);
|
|
178
|
+
|
|
179
|
+
// Step 2: TTY gate (D-03).
|
|
180
|
+
const stdin = opts.stdin || process.stdin;
|
|
181
|
+
const stdout = opts.stdout || process.stdout;
|
|
182
|
+
// Allow tests to bypass the TTY check by injecting a stream that sets
|
|
183
|
+
// `isTTY = true` on itself (the public API contract is "interactive
|
|
184
|
+
// shell"; the underlying mechanism is the isTTY flag).
|
|
185
|
+
if (!stdin.isTTY) {
|
|
186
|
+
throw new Error(
|
|
187
|
+
'refused: /gdd:report-issue requires an interactive TTY (no auto-mode by design — D-03)'
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Step 3: optional editor (skipped if openEditor === false).
|
|
192
|
+
const editorFn = opts.openInEditor || openInEditor;
|
|
193
|
+
if (opts.openEditor !== false) {
|
|
194
|
+
try {
|
|
195
|
+
editorFn(draftPath, opts.env);
|
|
196
|
+
} catch {
|
|
197
|
+
// Editor failures are non-fatal — user can edit manually + still consent.
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Step 4: re-read the (possibly edited) draft. This is the key step
|
|
202
|
+
// that makes E1 + E2 pass — content is freshly loaded from disk.
|
|
203
|
+
const draft = readDraft(draftPath);
|
|
204
|
+
|
|
205
|
+
// Step 5: summary.
|
|
206
|
+
try {
|
|
207
|
+
stdout.write(buildSummary(draft, draftPath));
|
|
208
|
+
} catch {
|
|
209
|
+
// Best-effort UI; tests can inject stdout that throws.
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Step 6: ask y/N.
|
|
213
|
+
const ask = opts.askYesNo || askYesNo;
|
|
214
|
+
const answer = await ask(stdin, stdout);
|
|
215
|
+
const consented = isAffirmative(answer);
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
consented,
|
|
219
|
+
finalTitle: draft.title,
|
|
220
|
+
finalBody: draft.body,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
module.exports = {
|
|
225
|
+
promptConsent,
|
|
226
|
+
rejectBypassEnv,
|
|
227
|
+
isAffirmative,
|
|
228
|
+
buildSummary,
|
|
229
|
+
openInEditor,
|
|
230
|
+
askYesNo,
|
|
231
|
+
};
|