@opengsd/gsd-core 1.20.0 → 1.21.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 +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.opencode/plugins/gsd-core.js +33 -2
- package/agents/gsd-executor.md +1 -1
- package/agents/gsd-plan-checker.md +1 -1
- package/agents/gsd-planner.md +3 -3
- package/agents/gsd-verifier.md +2 -2
- package/bin/install.js +265 -161
- package/commands/gsd/autonomous.md +1 -1
- package/commands/gsd/new-milestone.md +2 -2
- package/commands/gsd/new-project.md +2 -2
- package/gsd-core/bin/gsd-tools.cjs +554 -823
- package/gsd-core/bin/lib/capability-activation.cjs +8 -2
- package/gsd-core/bin/lib/capability-command-router.cjs +6 -2
- package/gsd-core/bin/lib/capability-ledger.cjs +1 -1
- package/gsd-core/bin/lib/capability-lock.cjs +19 -27
- package/gsd-core/bin/lib/capability-registry.cjs +74 -71
- package/gsd-core/bin/lib/capability-validator.cjs +1 -0
- package/gsd-core/bin/lib/capability-writer.cjs +7 -4
- package/gsd-core/bin/lib/claude-orchestration-command-router.cjs +142 -62
- package/gsd-core/bin/lib/claude-orchestration.cjs +214 -122
- package/gsd-core/bin/lib/cli-surface.cjs +545 -0
- package/gsd-core/bin/lib/command-arg-projection.cjs +356 -103
- package/gsd-core/bin/lib/commands.cjs +99 -2
- package/gsd-core/bin/lib/config-file-read.cjs +85 -0
- package/gsd-core/bin/lib/config-loader.cjs +23 -15
- package/gsd-core/bin/lib/config.cjs +139 -101
- package/gsd-core/bin/lib/configuration.cjs +41 -2
- package/gsd-core/bin/lib/criterion-baseline.cjs +346 -0
- package/gsd-core/bin/lib/criterion-replay.cjs +380 -0
- package/gsd-core/bin/lib/estimate-cli.cjs +5 -1
- package/gsd-core/bin/lib/gap-checker.cjs +12 -1
- package/gsd-core/bin/lib/gate-evaluation-scope.cjs +593 -27
- package/gsd-core/bin/lib/gate-phase-context.cjs +16 -4
- package/gsd-core/bin/lib/graphify.cjs +19 -2
- package/gsd-core/bin/lib/gsd-tool-dir.cjs +134 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +5 -1
- package/gsd-core/bin/lib/init.cjs +20 -18
- package/gsd-core/bin/lib/install-effort-resolver.cjs +24 -4
- package/gsd-core/bin/lib/install-engine.cjs +20 -28
- package/gsd-core/bin/lib/install-fs-adapter.cjs +5 -1
- package/gsd-core/bin/lib/install-manifest-record.cjs +86 -0
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +33 -3
- package/gsd-core/bin/lib/install-profiles.cjs +4 -2
- package/gsd-core/bin/lib/io.cjs +6 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +62 -31
- package/gsd-core/bin/lib/markdown-table.cjs +39 -1
- package/gsd-core/bin/lib/milestone.cjs +8 -6
- package/gsd-core/bin/lib/model-resolver.cjs +8 -4
- package/gsd-core/bin/lib/phase-entry-grammar.cjs +14 -0
- package/gsd-core/bin/lib/phase-id.cjs +26 -9
- package/gsd-core/bin/lib/phase-locator.cjs +8 -3
- package/gsd-core/bin/lib/phase.cjs +170 -45
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +46 -23
- package/gsd-core/bin/lib/planning-snapshot.cjs +10 -6
- package/gsd-core/bin/lib/planning-workspace.cjs +20 -10
- package/gsd-core/bin/lib/profile-output.cjs +9 -6
- package/gsd-core/bin/lib/project-context.cjs +439 -0
- package/gsd-core/bin/lib/project-root.cjs +9 -230
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +1 -1
- package/gsd-core/bin/lib/report-parser.cjs +16 -0
- package/gsd-core/bin/lib/roadmap-parser.cjs +357 -50
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +24 -5
- package/gsd-core/bin/lib/roadmap.cjs +2 -2
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +317 -134
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +81 -3
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +6 -0
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +279 -8
- package/gsd-core/bin/lib/runtime-slash.cjs +7 -2
- package/gsd-core/bin/lib/shell-command-projection.cjs +1010 -10
- package/gsd-core/bin/lib/stale-bake-guard.cjs +4 -1
- package/gsd-core/bin/lib/state-transition.cjs +103 -46
- package/gsd-core/bin/lib/state.cjs +448 -63
- package/gsd-core/bin/lib/surface.cjs +4 -7
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +10 -3
- package/gsd-core/bin/lib/verify-command-router.cjs +6 -1
- package/gsd-core/bin/lib/verify.cjs +49 -6
- package/gsd-core/bin/lib/workstream-inventory.cjs +4 -4
- package/gsd-core/bin/lib/worktree-base-ref.cjs +32 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +8 -111
- package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
- package/gsd-core/references/autonomous-smart-discuss.md +99 -18
- package/gsd-core/references/checkpoints.md +1 -1
- package/gsd-core/references/planner-guidance.md +11 -0
- package/gsd-core/references/planner-human-verify-mode.md +1 -1
- package/gsd-core/references/planner-reversibility.md +2 -0
- package/gsd-core/references/planner-verify-command-grounding.md +22 -6
- package/gsd-core/workflows/autonomous.md +4 -4
- package/gsd-core/workflows/code-review-fix.md +5 -7
- package/gsd-core/workflows/execute-phase.md +21 -7
- package/gsd-core/workflows/plan-phase/steps/plan-pre-dispatch.md +101 -0
- package/gsd-core/workflows/plan-phase.md +18 -19
- package/hooks/dist/gsd-agent-isolation-guard.js +4 -3
- package/hooks/dist/gsd-config-reload.js +2 -2
- package/hooks/dist/gsd-context-monitor.js +2 -1
- package/hooks/dist/gsd-cursor-subagent-start.js +9 -6
- package/hooks/dist/gsd-read-injection-scanner.js +2 -2
- package/hooks/dist/gsd-secret-read-guard.js +141 -6
- package/hooks/dist/gsd-statusline.js +3 -2
- package/hooks/dist/gsd-workflow-guard.js +2 -1
- package/hooks/dist/lib/config-read.js +49 -0
- package/hooks/dist/lib/git-cmd.js +3 -1
- package/hooks/dist/lib/git-head-tree.js +474 -0
- package/hooks/dist/lib/isolation-sentinel.js +168 -13
- package/hooks/dist/lib/secret-read-exemptions.js +161 -0
- package/hooks/gsd-agent-isolation-guard.js +4 -3
- package/hooks/gsd-config-reload.js +2 -2
- package/hooks/gsd-context-monitor.js +2 -1
- package/hooks/gsd-cursor-subagent-start.js +9 -6
- package/hooks/gsd-read-injection-scanner.js +2 -2
- package/hooks/gsd-secret-read-guard.js +141 -6
- package/hooks/gsd-statusline.js +3 -2
- package/hooks/gsd-workflow-guard.js +2 -1
- package/hooks/lib/config-read.js +49 -0
- package/hooks/lib/git-cmd.js +3 -1
- package/hooks/lib/git-head-tree.js +474 -0
- package/hooks/lib/isolation-sentinel.js +168 -13
- package/hooks/lib/secret-read-exemptions.js +161 -0
- package/package.json +7 -2
- package/pi/package-agents/gsd-executor.md +1 -1
- package/pi/package-agents/gsd-plan-checker.md +1 -1
- package/pi/package-agents/gsd-planner.md +3 -3
- package/pi/package-agents/gsd-verifier.md +2 -2
- package/pi/package-agents/manifest.json +4 -4
- package/pi/package-skills/gsd-autonomous/SKILL.md +1 -1
- package/pi/package-skills/gsd-new-milestone/SKILL.md +1 -1
- package/pi/package-skills/gsd-new-project/SKILL.md +1 -1
- package/scripts/changeset/cli.cjs +2 -2
- package/scripts/changeset/lint.cjs +28 -3
- package/scripts/changeset/serialize.cjs +12 -2
- package/scripts/ci-compact-v8-coverage.cjs +204 -0
- package/scripts/ci-fix-next-rerun.cjs +149 -0
- package/scripts/ci-next-health.cjs +52 -9
- package/scripts/ci-test-scope.cjs +77 -71
- package/scripts/command-contract-helpers.cjs +56 -7
- package/scripts/docs-guard-registry.cjs +3 -0
- package/scripts/gen-cli-surface-manifest.cjs +287 -0
- package/scripts/gen-platform-conformance-tier.cjs +258 -19
- package/scripts/lib/macos-conformance-tier.generated.cjs +59 -47
- package/scripts/lib/platform-conformance-tier.generated.cjs +23 -64
- package/scripts/lib/shellcheck-fetch.cjs +71 -4
- package/scripts/lint-removed-but-needed.cjs +562 -50
- package/scripts/merge-commit-pr.cjs +186 -0
- package/scripts/release-ancestry.cjs +86 -0
- package/scripts/release-notes/changelog-release-notes.cjs +345 -0
- package/scripts/release-notes/discord-release-summary.cjs +82 -1
- package/scripts/release-pipeline-pr.cjs +92 -0
- package/scripts/require-issue-link-policy.cjs +18 -9
- package/scripts/sync-next-version.cjs +11 -2
- package/scripts/timeout-evidence-from-ci.cjs +964 -0
- package/skills/gsd-autonomous/SKILL.md +1 -1
- package/skills/gsd-new-milestone/SKILL.md +1 -1
- package/skills/gsd-new-project/SKILL.md +1 -1
- package/vscode/package.json +1 -1
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Shared helpers for command-family adapters to project argv tokens into
|
|
13
13
|
* typed named values and multi-word segments.
|
|
14
|
+
* `requiredFlags` (ADR-4909 D1, #6087) turns a missing value-flag into InvalidArgs.
|
|
14
15
|
*/
|
|
15
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
17
|
exports.isFlagToken = isFlagToken;
|
|
@@ -45,121 +46,373 @@ function assertValidSpec(spec) {
|
|
|
45
46
|
throw new TypeError('parseNamedArgs: spec.positionals must be a non-negative integer or the literal "rest" ' +
|
|
46
47
|
`— received ${JSON.stringify(positionals)}.`);
|
|
47
48
|
}
|
|
49
|
+
if (spec.freePositionals !== undefined && (spec.freePositionals !== true || positionals === 'rest')) {
|
|
50
|
+
throw new TypeError('parseNamedArgs: spec.freePositionals must be true and cannot be combined with positionals "rest" ' +
|
|
51
|
+
`— received freePositionals ${JSON.stringify(spec.freePositionals)} with positionals ${JSON.stringify(positionals)}.`);
|
|
52
|
+
}
|
|
53
|
+
if (spec.rejectShortFlags !== undefined && spec.rejectShortFlags !== true) {
|
|
54
|
+
throw new TypeError(`parseNamedArgs: spec.rejectShortFlags must be true — received ${JSON.stringify(spec.rejectShortFlags)}.`);
|
|
55
|
+
}
|
|
56
|
+
if (spec.unknownFlagMessage !== undefined &&
|
|
57
|
+
(typeof spec.unknownFlagMessage !== 'string' || !spec.unknownFlagMessage.includes('{flag}'))) {
|
|
58
|
+
throw new TypeError(`parseNamedArgs: spec.unknownFlagMessage must be a string containing the {flag} placeholder — received ${JSON.stringify(spec.unknownFlagMessage)}.`);
|
|
59
|
+
}
|
|
60
|
+
for (const kind of VALUE_SUBSET_KINDS)
|
|
61
|
+
assertSubsetOf(spec, kind, 'valueFlags');
|
|
62
|
+
assertSubsetOf(spec, 'emptyValueFlags', 'nonEmptyFlags');
|
|
63
|
+
assertSubsetOf(spec, 'emptyEqFlags', 'nonEmptyFlags');
|
|
64
|
+
assertSubsetOf(spec, 'emptyEqFlags', 'eqFlags');
|
|
65
|
+
assertSubsetOf(spec, 'trimEqFlags', 'eqFlags');
|
|
66
|
+
assertSubsetOf(spec, 'blankIsMissingFlags', 'nonEmptyFlags');
|
|
67
|
+
assertEqRejectPrefix(spec);
|
|
68
|
+
if (spec.missingValueMessage !== undefined &&
|
|
69
|
+
(typeof spec.missingValueMessage !== 'string' || !spec.missingValueMessage.includes('{flag}'))) {
|
|
70
|
+
throw new TypeError(`parseNamedArgs: spec.missingValueMessage must be a string containing the {flag} placeholder — received ${JSON.stringify(spec.missingValueMessage)}.`);
|
|
71
|
+
}
|
|
72
|
+
for (const kind of INDEPENDENT_KINDS)
|
|
73
|
+
assertIndependentKind(spec, kind);
|
|
74
|
+
if (spec.passthroughAfter !== undefined && spec.passthroughAfter !== '--') {
|
|
75
|
+
throw new TypeError(`parseNamedArgs: spec.passthroughAfter must be the literal "--" — received ${JSON.stringify(spec.passthroughAfter)}.`);
|
|
76
|
+
}
|
|
48
77
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
78
|
+
const VALUE_SUBSET_KINDS = [
|
|
79
|
+
'requiredFlags',
|
|
80
|
+
'eqFlags',
|
|
81
|
+
'nonEmptyFlags',
|
|
82
|
+
'bareEmptyFlags',
|
|
83
|
+
'repeatableFlags',
|
|
84
|
+
];
|
|
85
|
+
// Kinds that own their tokens: a flag may not also be a plain value, boolean or
|
|
86
|
+
// optional flag, nor belong to the other independent kind.
|
|
87
|
+
const INDEPENDENT_KINDS = ['listFlags', 'multiwordFlags'];
|
|
88
|
+
function assertStringArray(spec, kind) {
|
|
89
|
+
const list = spec[kind];
|
|
90
|
+
if (list === undefined)
|
|
91
|
+
return null;
|
|
92
|
+
if (!Array.isArray(list) || !list.every((f) => typeof f === 'string')) {
|
|
93
|
+
throw new TypeError(`parseNamedArgs: spec.${kind} must be an array of strings.`);
|
|
94
|
+
}
|
|
95
|
+
return list;
|
|
96
|
+
}
|
|
97
|
+
function assertSubsetOf(spec, kind, parent) {
|
|
98
|
+
const list = assertStringArray(spec, kind);
|
|
99
|
+
if (list === null)
|
|
100
|
+
return;
|
|
101
|
+
const declared = Array.isArray(spec[parent]) ? spec[parent] : [];
|
|
102
|
+
const stray = list.filter((f) => !declared.includes(f));
|
|
103
|
+
if (stray.length > 0) {
|
|
104
|
+
throw new TypeError(`parseNamedArgs: spec.${kind} must be a subset of spec.${parent} — ` +
|
|
105
|
+
`not declared in ${parent}: ${stray.join(', ')}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function assertEqRejectPrefix(spec) {
|
|
109
|
+
const map = spec.eqRejectPrefix;
|
|
110
|
+
if (map === undefined)
|
|
111
|
+
return;
|
|
112
|
+
if (!isPlainObject(map) || !Object.values(map).every((p) => typeof p === 'string' && p !== '')) {
|
|
113
|
+
throw new TypeError('parseNamedArgs: spec.eqRejectPrefix must map flag names to non-empty prefix strings.');
|
|
114
|
+
}
|
|
115
|
+
const flags = Object.keys(map);
|
|
116
|
+
for (const parent of ['eqFlags', 'nonEmptyFlags']) {
|
|
117
|
+
const declared = Array.isArray(spec[parent]) ? spec[parent] : [];
|
|
118
|
+
const stray = flags.filter((f) => !declared.includes(f));
|
|
119
|
+
if (stray.length > 0) {
|
|
120
|
+
throw new TypeError(`parseNamedArgs: spec.eqRejectPrefix must be a subset of spec.${parent} — ` +
|
|
121
|
+
`not declared in ${parent}: ${stray.join(', ')}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function assertIndependentKind(spec, kind) {
|
|
126
|
+
const list = assertStringArray(spec, kind);
|
|
127
|
+
if (list === null)
|
|
128
|
+
return;
|
|
129
|
+
const others = ['valueFlags', 'booleanFlags', 'optionalValueFlags', ...INDEPENDENT_KINDS.filter((k) => k !== kind)];
|
|
130
|
+
for (const other of others) {
|
|
131
|
+
const clash = list.filter((f) => Array.isArray(spec[other]) && spec[other].includes(f));
|
|
132
|
+
if (clash.length > 0) {
|
|
133
|
+
throw new TypeError(`parseNamedArgs: spec.${kind} must be disjoint from spec.${other} — ` +
|
|
134
|
+
`declared in both: ${clash.join(', ')}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
56
137
|
}
|
|
57
|
-
// ─── parseNamedArgs ───────────────────────────────────────────────────────────
|
|
58
138
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* Extraction (unchanged semantics, #312): first occurrence wins; a value
|
|
63
|
-
* flag whose next token is absent or starts with `--` yields `null` (this is
|
|
64
|
-
* NOT a validation error — see the value-flag branch below); boolean flags
|
|
65
|
-
* and optional-value flags (`optionalValueFlags`, #2932's `--wave` shape) are
|
|
66
|
-
* presence tests. Kept as a single first-index Map so the flag loops don't
|
|
67
|
-
* each re-scan argv — O(argv + flags) instead of O(flags * argv).
|
|
68
|
-
*
|
|
69
|
-
* Validation (skipped entirely when `positionals === 'rest'`): a single
|
|
70
|
-
* left-to-right cursor walk from `spec.positionals`, per the design doc's
|
|
71
|
-
* Kernighan's Law note — debuggable over clever, never a set-difference.
|
|
139
|
+
* First required flag (declaration order) whose extracted value is `null`
|
|
140
|
+
* (absent or flag-shaped) or `''`, as an InvalidArgs result; else null.
|
|
72
141
|
*/
|
|
73
|
-
function
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
142
|
+
function missingRequired(spec, data) {
|
|
143
|
+
for (const flag of spec.requiredFlags ?? []) {
|
|
144
|
+
const value = data[flag];
|
|
145
|
+
if (value === null || value === '' || (Array.isArray(value) && value.length === 0)) {
|
|
146
|
+
return {
|
|
147
|
+
ok: false,
|
|
148
|
+
kind: 'InvalidArgs',
|
|
149
|
+
arg: `--${flag}`,
|
|
150
|
+
reason: `missing required flag --${flag} <value>`,
|
|
151
|
+
exitReason: ERROR_REASON.USAGE,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
82
154
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (
|
|
101
|
-
return
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Whether the occurrence of a `nonEmptyFlags` member carries no value, per the
|
|
159
|
+
* `emptyValueFlags` / `emptyEqFlags` relaxations: no next token or a flag-shaped
|
|
160
|
+
* one always counts as missing; an empty next token and an empty or
|
|
161
|
+
* whitespace-only `--f=` value count unless relaxed.
|
|
162
|
+
*/
|
|
163
|
+
function lacksValue(args, spec, flag, occ) {
|
|
164
|
+
const blankMissing = (spec.blankIsMissingFlags ?? []).includes(flag);
|
|
165
|
+
if (occ.inline !== null) {
|
|
166
|
+
const prefix = spec.eqRejectPrefix?.[flag];
|
|
167
|
+
if (prefix !== undefined && occ.inline.startsWith(prefix))
|
|
168
|
+
return true;
|
|
169
|
+
return occ.inline.trim() === '' && (blankMissing || !(spec.emptyEqFlags ?? []).includes(flag));
|
|
170
|
+
}
|
|
171
|
+
const next = args[occ.index + 1];
|
|
172
|
+
if (next === undefined || isFlagToken(next))
|
|
173
|
+
return true;
|
|
174
|
+
if (blankMissing && next.trim() === '')
|
|
175
|
+
return true;
|
|
176
|
+
return next === '' && !(spec.emptyValueFlags ?? []).includes(flag);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* First `nonEmptyFlags` member (declaration order) that is present (`--f` or
|
|
180
|
+
* `--f=` occurs) without a value, as an InvalidArgs result with today's wording
|
|
181
|
+
* (`Missing value for --f`); else null. The first occurrence is checked, except
|
|
182
|
+
* for a repeatable flag, whose last occurrence is.
|
|
183
|
+
*/
|
|
184
|
+
function missingValue(args, spec, occurrences) {
|
|
185
|
+
for (const flag of spec.nonEmptyFlags ?? []) {
|
|
186
|
+
const occs = occurrences.get(flag);
|
|
187
|
+
if (occs === undefined)
|
|
188
|
+
continue;
|
|
189
|
+
const occ = (spec.repeatableFlags ?? []).includes(flag) ? occs[occs.length - 1] : occs[0];
|
|
190
|
+
if (lacksValue(args, spec, flag, occ)) {
|
|
191
|
+
return {
|
|
192
|
+
ok: false,
|
|
193
|
+
kind: 'InvalidArgs',
|
|
194
|
+
arg: `--${flag}`,
|
|
195
|
+
reason: spec.missingValueMessage !== undefined
|
|
196
|
+
? spec.missingValueMessage.split('{flag}').join(`--${flag}`)
|
|
197
|
+
: `Missing value for --${flag}`,
|
|
198
|
+
exitReason: ERROR_REASON.USAGE,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
/** Single pass over argv: flag name -> its occurrences in argv order. */
|
|
205
|
+
function indexOccurrences(args, eq) {
|
|
206
|
+
const map = new Map();
|
|
207
|
+
args.forEach((tok, index) => {
|
|
208
|
+
if (!isFlagToken(tok))
|
|
209
|
+
return;
|
|
210
|
+
const eqAt = tok.indexOf('=');
|
|
211
|
+
const eqName = eqAt > 2 ? tok.slice(2, eqAt) : null;
|
|
212
|
+
const isEq = eqName !== null && eq.has(eqName);
|
|
213
|
+
const name = isEq ? eqName : tok.slice(2);
|
|
214
|
+
const list = map.get(name) ?? [];
|
|
215
|
+
list.push({ index, inline: isEq ? tok.slice(eqAt + 1) : null });
|
|
216
|
+
map.set(name, list);
|
|
217
|
+
});
|
|
218
|
+
return map;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* The value an occurrence carries: `null` when absent or flag-shaped, or when
|
|
222
|
+
* `--f=` is empty (unless `emptyEqFlags` makes it a value).
|
|
223
|
+
*/
|
|
224
|
+
function occurrenceValue(args, spec, flag, occ) {
|
|
225
|
+
if (occ.inline !== null) {
|
|
226
|
+
const inline = (spec.trimEqFlags ?? []).includes(flag) ? occ.inline.trim() : occ.inline;
|
|
227
|
+
return inline === '' && !(spec.emptyEqFlags ?? []).includes(flag) ? null : inline;
|
|
228
|
+
}
|
|
229
|
+
const next = args[occ.index + 1];
|
|
230
|
+
return next !== undefined && !isFlagToken(next) ? next : null;
|
|
231
|
+
}
|
|
232
|
+
function extractValueFlag(args, spec, flag, occs) {
|
|
233
|
+
if ((spec.repeatableFlags ?? []).includes(flag)) {
|
|
234
|
+
// An empty or flag-shaped value contributes nothing to a repeatable flag.
|
|
235
|
+
return occs
|
|
236
|
+
.map((o) => occurrenceValue(args, spec, flag, o))
|
|
237
|
+
.filter((v) => v !== null && v !== '');
|
|
238
|
+
}
|
|
239
|
+
if (occs.length === 0)
|
|
240
|
+
return null;
|
|
241
|
+
const value = occurrenceValue(args, spec, flag, occs[0]);
|
|
242
|
+
const bareEmpty = (spec.bareEmptyFlags ?? []).includes(flag) && occs[0].inline === null;
|
|
243
|
+
return value === null && bareEmpty ? '' : value;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Every occurrence of `--flag` starts a run of non-`--` tokens that ends at
|
|
247
|
+
* the next list flag of the spec (gsd-tools.cjs `collectListFlagValues`).
|
|
248
|
+
*/
|
|
249
|
+
function collectListValues(args, flag, stops, sets) {
|
|
250
|
+
const values = [];
|
|
251
|
+
args.forEach((a, i) => {
|
|
252
|
+
if (a !== `--${flag}`)
|
|
253
|
+
return;
|
|
254
|
+
let j = i + 1;
|
|
255
|
+
while (j < args.length) {
|
|
256
|
+
const b = args[j];
|
|
257
|
+
if (stops.has(b))
|
|
258
|
+
break;
|
|
259
|
+
if (isFlagToken(b)) {
|
|
260
|
+
// Step over exactly what the walk steps over, so a word the walk reads
|
|
261
|
+
// as a flag's value is never also collected; a bare `--` advances by 1.
|
|
262
|
+
j += advanceOverFlag(args, j, sets) ?? 1;
|
|
134
263
|
continue;
|
|
135
264
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
: `unknown flag ${formatDiagnosticToken(tok)}; this command accepts no flags`;
|
|
139
|
-
return { ok: false, kind: 'InvalidArgs', arg: tok, reason, exitReason: ERROR_REASON.USAGE };
|
|
265
|
+
values.push(b);
|
|
266
|
+
j += 1;
|
|
140
267
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
268
|
+
});
|
|
269
|
+
return values;
|
|
270
|
+
}
|
|
271
|
+
function extractData(args, spec, occurrences) {
|
|
272
|
+
const data = {};
|
|
273
|
+
for (const flag of spec.valueFlags ?? []) {
|
|
274
|
+
data[flag] = extractValueFlag(args, spec, flag, occurrences.get(flag) ?? []);
|
|
148
275
|
}
|
|
149
|
-
|
|
276
|
+
// Boolean and optional-value flags (#2932: `--wave`-shaped) are presence
|
|
277
|
+
// tests; the optional value is deliberately not surfaced here.
|
|
278
|
+
for (const flag of [...(spec.booleanFlags ?? []), ...(spec.optionalValueFlags ?? [])]) {
|
|
279
|
+
data[flag] = occurrences.has(flag);
|
|
280
|
+
}
|
|
281
|
+
const listFlags = spec.listFlags ?? [];
|
|
282
|
+
const stops = new Set(listFlags.map((f) => `--${f}`));
|
|
283
|
+
const sets = buildFlagSets(spec);
|
|
284
|
+
for (const flag of listFlags)
|
|
285
|
+
data[flag] = collectListValues(args, flag, stops, sets);
|
|
286
|
+
for (const flag of spec.multiwordFlags ?? [])
|
|
287
|
+
data[flag] = parseMultiwordArg(args, flag);
|
|
288
|
+
return data;
|
|
289
|
+
}
|
|
290
|
+
function buildFlagSets(spec) {
|
|
291
|
+
const value = spec.valueFlags ?? [];
|
|
292
|
+
const bool = spec.booleanFlags ?? [];
|
|
293
|
+
const optional = spec.optionalValueFlags ?? [];
|
|
294
|
+
const list = spec.listFlags ?? [];
|
|
295
|
+
const multiword = spec.multiwordFlags ?? [];
|
|
296
|
+
return {
|
|
297
|
+
value: new Set(value),
|
|
298
|
+
boolean: new Set(bool),
|
|
299
|
+
optional: new Set(optional),
|
|
300
|
+
eq: new Set(spec.eqFlags ?? []),
|
|
301
|
+
list: new Set(list),
|
|
302
|
+
multiword: new Set(multiword),
|
|
303
|
+
accepted: [
|
|
304
|
+
...value.map((f) => `--${f} <value>`),
|
|
305
|
+
...bool.map((f) => `--${f}`),
|
|
306
|
+
...optional.map((f) => `--${f} [value]`),
|
|
307
|
+
...list.map((f) => `--${f} <values...>`),
|
|
308
|
+
...multiword.map((f) => `--${f} <words...>`),
|
|
309
|
+
],
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
// A short flag is a dash followed by a letter; `-1` (negative number) and `-` are not flags.
|
|
313
|
+
const SHORT_FLAG_RE = /^-[A-Za-z]/;
|
|
314
|
+
/** Tokens consumed by the flag at `args[i]`, or null when the flag is not declared. */
|
|
315
|
+
function advanceOverFlag(args, i, sets) {
|
|
316
|
+
const tok = args[i];
|
|
317
|
+
const eqAt = tok.indexOf('=');
|
|
318
|
+
if (eqAt > 2 && sets.eq.has(tok.slice(2, eqAt)))
|
|
319
|
+
return 1;
|
|
320
|
+
const name = tok.slice(2);
|
|
321
|
+
const next = args[i + 1];
|
|
322
|
+
const hasValue = next !== undefined && !isFlagToken(next);
|
|
323
|
+
// A value flag whose next token is absent or flag-shaped resolves to `null`
|
|
324
|
+
// in `data` — NOT an error (#3180/behavior-lock). Advance by 1 so the
|
|
325
|
+
// following flag token is validated on its own merits.
|
|
326
|
+
if (sets.value.has(name) || sets.optional.has(name))
|
|
327
|
+
return hasValue ? 2 : 1;
|
|
328
|
+
if (sets.boolean.has(name) || sets.list.has(name))
|
|
329
|
+
return 1;
|
|
330
|
+
if (sets.multiword.has(name)) {
|
|
331
|
+
let n = 1;
|
|
332
|
+
while (args[i + n] !== undefined && !isFlagToken(args[i + n]))
|
|
333
|
+
n++;
|
|
334
|
+
return n;
|
|
335
|
+
}
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
function unknownFlag(tok, sets, spec) {
|
|
339
|
+
const template = spec.unknownFlagMessage;
|
|
340
|
+
const reason = template !== undefined
|
|
341
|
+
? template.split('{flag}').join(tok)
|
|
342
|
+
: sets.accepted.length > 0
|
|
343
|
+
? `unknown flag ${formatDiagnosticToken(tok)}; accepted: ${sets.accepted.join(', ')}`
|
|
344
|
+
: `unknown flag ${formatDiagnosticToken(tok)}; this command accepts no flags`;
|
|
345
|
+
return { ok: false, kind: 'InvalidArgs', arg: tok, reason, exitReason: ERROR_REASON.USAGE };
|
|
150
346
|
}
|
|
151
347
|
/**
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
* analysis needs a `never`-returning path so callers can destructure the
|
|
157
|
-
* return value without a null check; (2) it is a fail-closed backstop — the
|
|
158
|
-
* `fail` callbacks in this repo are `never`-returning at runtime (`io.error`
|
|
159
|
-
* calls `process.exit(1)`) but are typed `void`, so if a caller ever passes a
|
|
160
|
-
* `fail` that actually returns, this still halts instead of falling through
|
|
161
|
-
* with a half-built `ParsedNamedArgs`.
|
|
348
|
+
* Left-to-right cursor walk from `spec.positionals`. Once a list flag has
|
|
349
|
+
* been seen, non-flag tokens belong to its run (collectListFlagValues
|
|
350
|
+
* semantics: a run is only ended by another list flag, which restarts it) and
|
|
351
|
+
* are not stray positionals; `--` tokens inside a run are still validated.
|
|
162
352
|
*/
|
|
353
|
+
function walkTokens(args, spec) {
|
|
354
|
+
const sets = buildFlagSets(spec);
|
|
355
|
+
let inListRun = false;
|
|
356
|
+
let i = spec.positionals === 'rest' ? args.length : spec.positionals;
|
|
357
|
+
while (i < args.length) {
|
|
358
|
+
const tok = args[i];
|
|
359
|
+
if (spec.rejectShortFlags === true && SHORT_FLAG_RE.test(tok))
|
|
360
|
+
return unknownFlag(tok, sets, spec);
|
|
361
|
+
if (inListRun && tok === '--') {
|
|
362
|
+
// collectListFlagValues skipped every `--`-prefixed token inside a run, so a bare `--` is accepted there.
|
|
363
|
+
i += 1;
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
if (isFlagToken(tok)) {
|
|
367
|
+
const advance = advanceOverFlag(args, i, sets);
|
|
368
|
+
if (advance === null)
|
|
369
|
+
return unknownFlag(tok, sets, spec);
|
|
370
|
+
if (sets.list.has(tok.slice(2)))
|
|
371
|
+
inListRun = true;
|
|
372
|
+
i += advance;
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
if (!inListRun && spec.freePositionals !== true) {
|
|
376
|
+
return {
|
|
377
|
+
ok: false,
|
|
378
|
+
kind: 'InvalidArgs',
|
|
379
|
+
arg: tok,
|
|
380
|
+
reason: `unexpected positional argument ${formatDiagnosticToken(tok)}`,
|
|
381
|
+
exitReason: ERROR_REASON.USAGE,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
i += 1;
|
|
385
|
+
}
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
/** Split argv at the first literal `--` (passthroughAfter) at/after the positional boundary. */
|
|
389
|
+
function splitPassthrough(args, spec) {
|
|
390
|
+
if (spec.passthroughAfter === undefined)
|
|
391
|
+
return { head: args, tail: [] };
|
|
392
|
+
const from = typeof spec.positionals === 'number' ? spec.positionals : 0;
|
|
393
|
+
const at = args.indexOf(spec.passthroughAfter, from);
|
|
394
|
+
return at === -1 ? { head: args, tail: [] } : { head: args.slice(0, at), tail: args.slice(at + 1) };
|
|
395
|
+
}
|
|
396
|
+
// Single predicate reused for both extraction (a value beginning with a
|
|
397
|
+
// single `-` is a value, not a flag) and validation (negative space N5).
|
|
398
|
+
// Exported for init-command-router's #3865 --phase alias normalization,
|
|
399
|
+
// which needs the same flag-shape test before deciding whether args[2] is a
|
|
400
|
+
// phase positional or a flag token.
|
|
401
|
+
function isFlagToken(tok) {
|
|
402
|
+
return tok.startsWith('--');
|
|
403
|
+
}
|
|
404
|
+
function parseNamedArgs(args, spec) {
|
|
405
|
+
assertValidSpec(spec);
|
|
406
|
+
const { head, tail } = splitPassthrough(args, spec);
|
|
407
|
+
const occurrences = indexOccurrences(head, new Set(spec.eqFlags ?? []));
|
|
408
|
+
const data = extractData(head, spec, occurrences);
|
|
409
|
+
if (spec.passthroughAfter !== undefined)
|
|
410
|
+
data['--'] = tail;
|
|
411
|
+
const invalid = (spec.positionals === 'rest' ? null : walkTokens(head, spec)) ??
|
|
412
|
+
missingValue(head, spec, occurrences) ??
|
|
413
|
+
missingRequired(spec, data);
|
|
414
|
+
return invalid ?? { ok: true, data };
|
|
415
|
+
}
|
|
163
416
|
function parseNamedArgsOrExit(args, spec, fail) {
|
|
164
417
|
const result = parseNamedArgs(args, spec);
|
|
165
418
|
if (!result.ok) {
|