@opengsd/gsd-core 1.17.0 → 1.18.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/agents/gsd-advisor-researcher.compact.md +1 -1
- package/agents/gsd-advisor-researcher.md +1 -1
- package/agents/gsd-assumptions-analyzer.compact.md +1 -1
- package/agents/gsd-assumptions-analyzer.md +1 -1
- package/agents/gsd-code-fixer.compact.md +1 -1
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-code-reviewer.compact.md +1 -1
- package/agents/gsd-code-reviewer.md +1 -1
- package/agents/gsd-codebase-mapper.compact.md +1 -1
- package/agents/gsd-codebase-mapper.md +1 -1
- package/agents/gsd-debugger.md +1 -1
- package/agents/gsd-doc-verifier.compact.md +4 -4
- package/agents/gsd-doc-verifier.md +4 -3
- package/agents/gsd-doc-writer.compact.md +23 -5
- package/agents/gsd-doc-writer.md +19 -6
- package/agents/gsd-eval-auditor.compact.md +1 -1
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +7 -3
- package/agents/gsd-integration-checker.compact.md +1 -1
- package/agents/gsd-integration-checker.md +1 -1
- package/agents/gsd-nyquist-auditor.compact.md +1 -1
- package/agents/gsd-nyquist-auditor.md +1 -1
- package/agents/gsd-phase-researcher.md +1 -1
- package/agents/gsd-plan-checker.md +2 -1
- package/agents/gsd-planner.md +5 -4
- package/agents/gsd-project-researcher.compact.md +1 -1
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.compact.md +5 -5
- package/agents/gsd-research-synthesizer.md +5 -5
- package/agents/gsd-roadmapper.compact.md +1 -1
- package/agents/gsd-roadmapper.md +1 -1
- package/agents/gsd-security-auditor.compact.md +1 -1
- package/agents/gsd-security-auditor.md +1 -1
- package/agents/gsd-ui-auditor.compact.md +1 -1
- package/agents/gsd-ui-auditor.md +1 -1
- package/agents/gsd-ui-checker.compact.md +1 -1
- package/agents/gsd-ui-checker.md +1 -1
- package/agents/gsd-ui-researcher.compact.md +13 -1
- package/agents/gsd-ui-researcher.md +28 -1
- package/agents/gsd-verifier.md +1 -1
- package/bin/install.js +38 -2
- package/commands/gsd/code-review.md +8 -0
- package/commands/gsd/pause-work.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +18 -11
- package/gsd-core/bin/lib/capability-ledger.cjs +25 -0
- package/gsd-core/bin/lib/capability-lock.cjs +29 -7
- package/gsd-core/bin/lib/capability-registry.cjs +70 -64
- package/gsd-core/bin/lib/capability-source.cjs +3 -1
- package/gsd-core/bin/lib/capability-state.cjs +6 -3
- package/gsd-core/bin/lib/capability-validator.cjs +6 -4
- package/gsd-core/bin/lib/commands.cjs +5 -1
- package/gsd-core/bin/lib/config-loader.cjs +3 -3
- package/gsd-core/bin/lib/decision-coverage-support.cjs +19 -0
- package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +15 -0
- package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +13 -0
- package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +23 -3
- package/gsd-core/bin/lib/gate-predicate.cjs +8 -12
- package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +60 -5
- package/gsd-core/bin/lib/init.cjs +11 -4
- package/gsd-core/bin/lib/install-effort-resolver.cjs +2 -2
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +4 -3
- package/gsd-core/bin/lib/install-profiles.cjs +5 -3
- package/gsd-core/bin/lib/phase.cjs +120 -11
- package/gsd-core/bin/lib/report-parser.cjs +57 -1
- package/gsd-core/bin/lib/review-lane-invocation.cjs +40 -0
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +20 -1
- package/gsd-core/bin/lib/roadmap.cjs +56 -5
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +47 -2
- package/gsd-core/bin/lib/runtime-homes.cjs +18 -0
- package/gsd-core/bin/lib/shell-command-projection.cjs +2 -0
- package/gsd-core/bin/lib/smart-entry.cjs +24 -16
- package/gsd-core/bin/lib/state-document.cjs +37 -3
- package/gsd-core/bin/lib/state-transition.cjs +31 -10
- package/gsd-core/bin/lib/state.cjs +90 -60
- package/gsd-core/bin/lib/task-command-router.cjs +2 -3
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +5 -0
- package/gsd-core/bin/lib/tdd-test-files.cjs +42 -0
- package/gsd-core/bin/lib/verify.cjs +36 -1
- package/gsd-core/references/agent-contracts.md +1 -1
- package/gsd-core/references/agent-skills-bootstrap.md +26 -7
- package/gsd-core/references/git-integration.md +3 -3
- package/gsd-core/references/planner-reversibility.md +18 -4
- package/gsd-core/references/planner-verify-command-grounding.md +18 -0
- package/gsd-core/references/tdd.md +4 -3
- package/gsd-core/references/verifier-phase-gates.md +8 -1
- package/gsd-core/workflows/add-backlog.md +10 -2
- package/gsd-core/workflows/add-phase.md +5 -1
- package/gsd-core/workflows/add-tests.md +5 -1
- package/gsd-core/workflows/add-todo.md +5 -1
- package/gsd-core/workflows/ai-integration-phase.md +5 -1
- package/gsd-core/workflows/audit-milestone.md +21 -9
- package/gsd-core/workflows/autonomous/templates/minimal-context.md +41 -0
- package/gsd-core/workflows/autonomous.md +52 -64
- package/gsd-core/workflows/check-todos.md +5 -1
- package/gsd-core/workflows/code-review-fix.md +5 -1
- package/gsd-core/workflows/code-review.md +5 -1
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +5 -1
- package/gsd-core/workflows/complete-milestone.md +25 -5
- package/gsd-core/workflows/debug.md +5 -1
- package/gsd-core/workflows/diagnose-issues.md +5 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +11 -3
- package/gsd-core/workflows/discuss-phase.md +5 -1
- package/gsd-core/workflows/docs-update/detail/elaboration.md +7 -6
- package/gsd-core/workflows/docs-update.md +27 -5
- package/gsd-core/workflows/edit-phase.md +5 -1
- package/gsd-core/workflows/eval-review.md +11 -3
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +5 -1
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +12 -6
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +19 -2
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +18 -1
- package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +5 -1
- package/gsd-core/workflows/execute-phase.md +37 -6
- package/gsd-core/workflows/execute-plan.md +8 -4
- package/gsd-core/workflows/extract-learnings.md +6 -2
- package/gsd-core/workflows/forensics.md +5 -1
- package/gsd-core/workflows/import.md +5 -1
- package/gsd-core/workflows/insert-phase.md +5 -1
- package/gsd-core/workflows/manager.md +10 -2
- package/gsd-core/workflows/map-codebase.md +5 -1
- package/gsd-core/workflows/milestone-summary.md +10 -2
- package/gsd-core/workflows/new-milestone.md +13 -5
- package/gsd-core/workflows/new-project.md +9 -5
- package/gsd-core/workflows/next.md +8 -4
- package/gsd-core/workflows/pause-work.md +3 -3
- package/gsd-core/workflows/plan-phase/steps/auto-advance-check.md +70 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +5 -1
- package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +6 -2
- package/gsd-core/workflows/plan-phase.md +41 -73
- package/gsd-core/workflows/plan-review-convergence.md +17 -5
- package/gsd-core/workflows/plant-seed.md +8 -4
- package/gsd-core/workflows/progress.md +6 -2
- package/gsd-core/workflows/quick-batch.md +5 -1
- package/gsd-core/workflows/quick.md +22 -7
- package/gsd-core/workflows/remove-phase.md +5 -1
- package/gsd-core/workflows/resume-project.md +5 -1
- package/gsd-core/workflows/review.md +6 -2
- package/gsd-core/workflows/scan.md +5 -1
- package/gsd-core/workflows/secure-phase.md +6 -2
- package/gsd-core/workflows/settings.md +3 -3
- package/gsd-core/workflows/ship.md +13 -10
- package/gsd-core/workflows/thread.md +10 -2
- package/gsd-core/workflows/transition.md +5 -1
- package/gsd-core/workflows/ui-phase.md +5 -1
- package/gsd-core/workflows/ui-review.md +6 -2
- package/gsd-core/workflows/ultraplan-phase.md +5 -1
- package/gsd-core/workflows/update.md +5 -1
- package/gsd-core/workflows/validate-phase.md +6 -2
- package/gsd-core/workflows/verify-work/detail/elaboration.md +1 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +5 -1
- package/gsd-core/workflows/verify-work.md +11 -3
- package/hooks/dist/gsd-agent-isolation-guard.js +3 -1
- package/hooks/dist/gsd-cursor-subagent-start.js +2 -1
- package/hooks/dist/gsd-secret-read-guard.js +72 -14
- package/hooks/dist/gsd-validate-commit.sh +27 -5
- package/hooks/gsd-agent-isolation-guard.js +3 -1
- package/hooks/gsd-cursor-subagent-start.js +2 -1
- package/hooks/gsd-secret-read-guard.js +72 -14
- package/hooks/gsd-validate-commit.sh +27 -5
- package/package.json +1 -1
- package/scripts/audit-workflow-script-paths.cjs +1 -1
- package/scripts/diagnose-validate-commit-in-tier.cjs +270 -0
- package/scripts/diagnose-validate-commit-phases.cjs +348 -0
- package/scripts/diagnose-windows-spawn-watcher.cjs +317 -0
- package/scripts/docs-guard-registry.cjs +2 -0
- package/scripts/lib/macos-conformance-tier.generated.cjs +9 -0
- package/scripts/lib/platform-conformance-tier.generated.cjs +16 -0
- package/scripts/lint-phase-arg-assignment.cjs +2 -2
- package/scripts/lint-workflow-shellcheck-baseline.json +16 -16
- package/scripts/release-tarball-smoke.cjs +1 -0
- package/scripts/xtrace-phase-timing.cjs +205 -0
- package/skills/gsd-code-review/SKILL.md +8 -0
- package/skills/gsd-pause-work/SKILL.md +1 -1
- package/vscode/package.json +1 -1
|
@@ -148,7 +148,11 @@ If $ARGUMENTS is a new description (no matching thread file):
|
|
|
148
148
|
|
|
149
149
|
1. Generate slug from description:
|
|
150
150
|
```bash
|
|
151
|
-
|
|
151
|
+
IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
|
|
152
|
+
$ARGUMENTS
|
|
153
|
+
GSD_ARGS_END
|
|
154
|
+
GSD_ARGS=${GSD_ARGS%$'\n'}
|
|
155
|
+
SLUG=$(gsd_run query generate-slug "$GSD_ARGS" --raw)
|
|
152
156
|
```
|
|
153
157
|
|
|
154
158
|
2. Create the threads directory if needed:
|
|
@@ -192,7 +196,11 @@ updated: {today ISO date}
|
|
|
192
196
|
|
|
193
197
|
5. Commit:
|
|
194
198
|
```bash
|
|
195
|
-
|
|
199
|
+
IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
|
|
200
|
+
$ARGUMENTS
|
|
201
|
+
GSD_ARGS_END
|
|
202
|
+
GSD_ARGS=${GSD_ARGS%$'\n'}
|
|
203
|
+
gsd_run query commit "docs: create thread — ${GSD_ARGS}" --files ".planning/threads/${SLUG}.md"
|
|
196
204
|
```
|
|
197
205
|
|
|
198
206
|
6. Report:
|
|
@@ -446,7 +446,11 @@ This returns all phases with goals, disk status, and completion info.
|
|
|
446
446
|
**Section-manifest gate (#2994):** `gsd_run` is already established above (`verify_completion` step) — fetch the dedicated `init.transition` bundle for the workstream-collision-check gate below:
|
|
447
447
|
|
|
448
448
|
```bash
|
|
449
|
-
|
|
449
|
+
IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
|
|
450
|
+
$ARGUMENTS
|
|
451
|
+
GSD_ARGS_END
|
|
452
|
+
GSD_ARGS=${GSD_ARGS%$'\n'}
|
|
453
|
+
GSD_WS=$(echo " $GSD_ARGS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
|
|
450
454
|
INIT_TRANSITION=$(gsd_run query init.transition ${GSD_WS:+--ws=${GSD_WS##* }})
|
|
451
455
|
if [[ "$INIT_TRANSITION" == @file:* ]]; then INIT_TRANSITION=$(cat "${INIT_TRANSITION#@file:}"); fi
|
|
452
456
|
```
|
|
@@ -19,8 +19,12 @@ Valid GSD subagent types (use exact names — do not fall back to 'general-purpo
|
|
|
19
19
|
## 1. Initialize
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
+
IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
|
|
23
|
+
$ARGUMENTS
|
|
24
|
+
GSD_ARGS_END
|
|
25
|
+
GSD_ARGS=${GSD_ARGS%$'\n'}
|
|
22
26
|
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; _gsd_at() { for _p; do if [ -f "$_p" ]; then GSD_TOOLS="$_p"; return 0; fi; done; return 1; }; _gsd_id_ok() { case "$("$1" runtime-identity --raw 2>/dev/null || true)" in '{"packageName":"@opengsd/gsd-core"'*'}') return 0;; *) return 1;; esac; }; _gsd_homes() { set -- "${CLAUDE_CONFIG_DIR:-$HOME/.claude}" "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}" "$HOME/.gemini/antigravity-ide" "$HOME/.gemini/antigravity-cli" "${AUGMENT_CONFIG_DIR:-$HOME/.augment}" "${CLINE_CONFIG_DIR:-$HOME/.cline}" "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}" "${CODEX_HOME:-$HOME/.codex}" "${COPILOT_CONFIG_DIR:-${COPILOT_HOME:-$HOME/.copilot}}" "${CURSOR_CONFIG_DIR:-$HOME/.cursor}" "${HERMES_HOME:-$HOME/.hermes}" "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}" "${KIMI_CONFIG_DIR:-$HOME/.config/agents}" "$HOME/.agents" "${KIMI_CODE_HOME:-$HOME/.kimi-code}" "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}" "${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}" "${QWEN_CONFIG_DIR:-$HOME/.qwen}" "${TRAE_CONFIG_DIR:-$HOME/.trae}" "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}" "${ZCODE_CONFIG_DIR:-$HOME/.zcode}" "${GROK_AGENTS_HOME:-$HOME/.agents}"; for _h; do _gsd_at "$_h/gsd-core/bin/${_GSD_SHIM_NAME}" && return 0; done; return 1; }; if _gsd_at "${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif _gsd_homes; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif unset -f gsd_run; _G="$(command -v gsd_run)"; [ -n "$_G" ] && _gsd_id_ok "$_G"; then GSD_TOOLS="$_G"; gsd_run() { "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and no identity-proving gsd_run is on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; GSD_IDENTITY_STATUS=unverified; _gsd_id_ok gsd_run && GSD_IDENTITY_STATUS=ok; export GSD_IDENTITY_STATUS; [ "$GSD_IDENTITY_STATUS" = ok ] || echo "WARNING: \"$GSD_TOOLS\" did not prove it is @opengsd/gsd-core - it is either a different package or an @opengsd/gsd-core older than the runtime-identity verb. See docs/how-to/diagnose-a-foreign-gsd-tools.md" >&2; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
|
|
23
|
-
GSD_WS=$(echo " $
|
|
27
|
+
GSD_WS=$(echo " $GSD_ARGS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
|
|
24
28
|
INIT=$(gsd_run query init.plan-phase ${GSD_WS:+--ws=${GSD_WS##* }} "$PHASE")
|
|
25
29
|
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
|
26
30
|
AGENT_SKILLS_UI=$(gsd_run query agent-skills gsd-ui-researcher ${GSD_WS:+--ws=${GSD_WS##* }})
|
|
@@ -16,10 +16,14 @@ Valid GSD subagent types (use exact names — do not fall back to 'general-purpo
|
|
|
16
16
|
## 0. Initialize
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
+
IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
|
|
20
|
+
$ARGUMENTS
|
|
21
|
+
GSD_ARGS_END
|
|
22
|
+
GSD_ARGS=${GSD_ARGS%$'\n'}
|
|
19
23
|
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; _gsd_at() { for _p; do if [ -f "$_p" ]; then GSD_TOOLS="$_p"; return 0; fi; done; return 1; }; _gsd_id_ok() { case "$("$1" runtime-identity --raw 2>/dev/null || true)" in '{"packageName":"@opengsd/gsd-core"'*'}') return 0;; *) return 1;; esac; }; _gsd_homes() { set -- "${CLAUDE_CONFIG_DIR:-$HOME/.claude}" "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}" "$HOME/.gemini/antigravity-ide" "$HOME/.gemini/antigravity-cli" "${AUGMENT_CONFIG_DIR:-$HOME/.augment}" "${CLINE_CONFIG_DIR:-$HOME/.cline}" "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}" "${CODEX_HOME:-$HOME/.codex}" "${COPILOT_CONFIG_DIR:-${COPILOT_HOME:-$HOME/.copilot}}" "${CURSOR_CONFIG_DIR:-$HOME/.cursor}" "${HERMES_HOME:-$HOME/.hermes}" "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}" "${KIMI_CONFIG_DIR:-$HOME/.config/agents}" "$HOME/.agents" "${KIMI_CODE_HOME:-$HOME/.kimi-code}" "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}" "${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}" "${QWEN_CONFIG_DIR:-$HOME/.qwen}" "${TRAE_CONFIG_DIR:-$HOME/.trae}" "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}" "${ZCODE_CONFIG_DIR:-$HOME/.zcode}" "${GROK_AGENTS_HOME:-$HOME/.agents}"; for _h; do _gsd_at "$_h/gsd-core/bin/${_GSD_SHIM_NAME}" && return 0; done; return 1; }; if _gsd_at "${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif _gsd_homes; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif unset -f gsd_run; _G="$(command -v gsd_run)"; [ -n "$_G" ] && _gsd_id_ok "$_G"; then GSD_TOOLS="$_G"; gsd_run() { "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and no identity-proving gsd_run is on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; GSD_IDENTITY_STATUS=unverified; _gsd_id_ok gsd_run && GSD_IDENTITY_STATUS=ok; export GSD_IDENTITY_STATUS; [ "$GSD_IDENTITY_STATUS" = ok ] || echo "WARNING: \"$GSD_TOOLS\" did not prove it is @opengsd/gsd-core - it is either a different package or an @opengsd/gsd-core older than the runtime-identity verb. See docs/how-to/diagnose-a-foreign-gsd-tools.md" >&2; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
|
|
20
24
|
RESPONSE_LANGUAGE=$(gsd_run query config-get response_language --raw --default "" 2>/dev/null || echo "")
|
|
21
|
-
PHASE_ARG=$(echo "$
|
|
22
|
-
GSD_WS=$(echo " $
|
|
25
|
+
PHASE_ARG=$(echo "$GSD_ARGS" | sed -E 's/--ws[[:space:]]+[A-Za-z0-9._-]+//g' | xargs)
|
|
26
|
+
GSD_WS=$(echo " $GSD_ARGS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
|
|
23
27
|
INIT=$(gsd_run query init.phase-op ${GSD_WS:+--ws=${GSD_WS##* }} "${PHASE_ARG}")
|
|
24
28
|
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
|
25
29
|
AGENT_SKILLS_UI_REVIEWER=$(gsd_run query agent-skills gsd-ui-auditor ${GSD_WS:+--ws=${GSD_WS##* }})
|
|
@@ -64,8 +64,12 @@ unplanned phase from the roadmap (same logic as /gsd:plan-phase).
|
|
|
64
64
|
Load GSD phase context:
|
|
65
65
|
|
|
66
66
|
```bash
|
|
67
|
+
IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
|
|
68
|
+
$ARGUMENTS
|
|
69
|
+
GSD_ARGS_END
|
|
70
|
+
GSD_ARGS=${GSD_ARGS%$'\n'}
|
|
67
71
|
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; _gsd_at() { for _p; do if [ -f "$_p" ]; then GSD_TOOLS="$_p"; return 0; fi; done; return 1; }; _gsd_id_ok() { case "$("$1" runtime-identity --raw 2>/dev/null || true)" in '{"packageName":"@opengsd/gsd-core"'*'}') return 0;; *) return 1;; esac; }; _gsd_homes() { set -- "${CLAUDE_CONFIG_DIR:-$HOME/.claude}" "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}" "$HOME/.gemini/antigravity-ide" "$HOME/.gemini/antigravity-cli" "${AUGMENT_CONFIG_DIR:-$HOME/.augment}" "${CLINE_CONFIG_DIR:-$HOME/.cline}" "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}" "${CODEX_HOME:-$HOME/.codex}" "${COPILOT_CONFIG_DIR:-${COPILOT_HOME:-$HOME/.copilot}}" "${CURSOR_CONFIG_DIR:-$HOME/.cursor}" "${HERMES_HOME:-$HOME/.hermes}" "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}" "${KIMI_CONFIG_DIR:-$HOME/.config/agents}" "$HOME/.agents" "${KIMI_CODE_HOME:-$HOME/.kimi-code}" "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}" "${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}" "${QWEN_CONFIG_DIR:-$HOME/.qwen}" "${TRAE_CONFIG_DIR:-$HOME/.trae}" "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}" "${ZCODE_CONFIG_DIR:-$HOME/.zcode}" "${GROK_AGENTS_HOME:-$HOME/.agents}"; for _h; do _gsd_at "$_h/gsd-core/bin/${_GSD_SHIM_NAME}" && return 0; done; return 1; }; if _gsd_at "${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif _gsd_homes; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif unset -f gsd_run; _G="$(command -v gsd_run)"; [ -n "$_G" ] && _gsd_id_ok "$_G"; then GSD_TOOLS="$_G"; gsd_run() { "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and no identity-proving gsd_run is on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; GSD_IDENTITY_STATUS=unverified; _gsd_id_ok gsd_run && GSD_IDENTITY_STATUS=ok; export GSD_IDENTITY_STATUS; [ "$GSD_IDENTITY_STATUS" = ok ] || echo "WARNING: \"$GSD_TOOLS\" did not prove it is @opengsd/gsd-core - it is either a different package or an @opengsd/gsd-core older than the runtime-identity verb. See docs/how-to/diagnose-a-foreign-gsd-tools.md" >&2; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
|
|
68
|
-
GSD_WS=$(echo " $
|
|
72
|
+
GSD_WS=$(echo " $GSD_ARGS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
|
|
69
73
|
INIT=$(gsd_run query init.plan-phase ${GSD_WS:+--ws=${GSD_WS##* }} "$PHASE")
|
|
70
74
|
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
|
71
75
|
```
|
|
@@ -99,7 +99,11 @@ Exit.
|
|
|
99
99
|
Determine the release channel from `$ARGUMENTS`. This selects which npm dist-tag the entire update flow targets — `latest` (stable) by default, or `next` (the RC channel established by ADR #660) when the user opts in with `--next`/`--rc`:
|
|
100
100
|
|
|
101
101
|
```bash
|
|
102
|
-
|
|
102
|
+
IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
|
|
103
|
+
$ARGUMENTS
|
|
104
|
+
GSD_ARGS_END
|
|
105
|
+
GSD_ARGS=${GSD_ARGS%$'\n'}
|
|
106
|
+
case " $GSD_ARGS " in
|
|
103
107
|
*" --next "*|*" --rc "*)
|
|
104
108
|
TAG="next"
|
|
105
109
|
CHANNEL_LABEL="next (RC)"
|
|
@@ -16,10 +16,14 @@ Valid GSD subagent types (use exact names — do not fall back to 'general-purpo
|
|
|
16
16
|
## 0. Initialize
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
+
IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
|
|
20
|
+
$ARGUMENTS
|
|
21
|
+
GSD_ARGS_END
|
|
22
|
+
GSD_ARGS=${GSD_ARGS%$'\n'}
|
|
19
23
|
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; _gsd_at() { for _p; do if [ -f "$_p" ]; then GSD_TOOLS="$_p"; return 0; fi; done; return 1; }; _gsd_id_ok() { case "$("$1" runtime-identity --raw 2>/dev/null || true)" in '{"packageName":"@opengsd/gsd-core"'*'}') return 0;; *) return 1;; esac; }; _gsd_homes() { set -- "${CLAUDE_CONFIG_DIR:-$HOME/.claude}" "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}" "$HOME/.gemini/antigravity-ide" "$HOME/.gemini/antigravity-cli" "${AUGMENT_CONFIG_DIR:-$HOME/.augment}" "${CLINE_CONFIG_DIR:-$HOME/.cline}" "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}" "${CODEX_HOME:-$HOME/.codex}" "${COPILOT_CONFIG_DIR:-${COPILOT_HOME:-$HOME/.copilot}}" "${CURSOR_CONFIG_DIR:-$HOME/.cursor}" "${HERMES_HOME:-$HOME/.hermes}" "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}" "${KIMI_CONFIG_DIR:-$HOME/.config/agents}" "$HOME/.agents" "${KIMI_CODE_HOME:-$HOME/.kimi-code}" "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}" "${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}" "${QWEN_CONFIG_DIR:-$HOME/.qwen}" "${TRAE_CONFIG_DIR:-$HOME/.trae}" "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}" "${ZCODE_CONFIG_DIR:-$HOME/.zcode}" "${GROK_AGENTS_HOME:-$HOME/.agents}"; for _h; do _gsd_at "$_h/gsd-core/bin/${_GSD_SHIM_NAME}" && return 0; done; return 1; }; if _gsd_at "${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif _gsd_homes; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif unset -f gsd_run; _G="$(command -v gsd_run)"; [ -n "$_G" ] && _gsd_id_ok "$_G"; then GSD_TOOLS="$_G"; gsd_run() { "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and no identity-proving gsd_run is on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; GSD_IDENTITY_STATUS=unverified; _gsd_id_ok gsd_run && GSD_IDENTITY_STATUS=ok; export GSD_IDENTITY_STATUS; [ "$GSD_IDENTITY_STATUS" = ok ] || echo "WARNING: \"$GSD_TOOLS\" did not prove it is @opengsd/gsd-core - it is either a different package or an @opengsd/gsd-core older than the runtime-identity verb. See docs/how-to/diagnose-a-foreign-gsd-tools.md" >&2; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
|
|
20
24
|
RESPONSE_LANGUAGE=$(gsd_run query config-get response_language --raw --default "" 2>/dev/null || echo "")
|
|
21
|
-
PHASE_ARG=$(echo "$
|
|
22
|
-
GSD_WS=$(echo " $
|
|
25
|
+
PHASE_ARG=$(echo "$GSD_ARGS" | sed -E 's/--ws[[:space:]]+[A-Za-z0-9._-]+//g' | xargs)
|
|
26
|
+
GSD_WS=$(echo " $GSD_ARGS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
|
|
23
27
|
INIT=$(gsd_run query init.phase-op ${GSD_WS:+--ws=${GSD_WS##* }} "${PHASE_ARG}")
|
|
24
28
|
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
|
25
29
|
AGENT_SKILLS_AUDITOR=$(gsd_run query agent-skills gsd-nyquist-auditor ${GSD_WS:+--ws=${GSD_WS##* }})
|
|
@@ -267,4 +267,4 @@ alongside `pending_count`/`blocked_count`/`skipped_no_reason`, and its issues-ro
|
|
|
267
267
|
(`diagnose_issues` vs. proceeding toward promotion) reads `unresolved_issues`, not the raw
|
|
268
268
|
`issues` count from `## Summary`. The raw `issues` count is unchanged and still drives the
|
|
269
269
|
presented summary numbers.
|
|
270
|
-
```
|
|
270
|
+
```
|
|
@@ -8,9 +8,13 @@
|
|
|
8
8
|
**User-story format guard.** When `MVP_MODE=true`, also verify the phase's goal is in User Story format via the centralized validator:
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
|
+
IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
|
|
12
|
+
$ARGUMENTS
|
|
13
|
+
GSD_ARGS_END
|
|
14
|
+
GSD_ARGS=${GSD_ARGS%$'\n'}
|
|
11
15
|
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; _gsd_at() { for _p; do if [ -f "$_p" ]; then GSD_TOOLS="$_p"; return 0; fi; done; return 1; }; _gsd_id_ok() { case "$("$1" runtime-identity --raw 2>/dev/null || true)" in '{"packageName":"@opengsd/gsd-core"'*'}') return 0;; *) return 1;; esac; }; _gsd_homes() { set -- "${CLAUDE_CONFIG_DIR:-$HOME/.claude}" "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}" "$HOME/.gemini/antigravity-ide" "$HOME/.gemini/antigravity-cli" "${AUGMENT_CONFIG_DIR:-$HOME/.augment}" "${CLINE_CONFIG_DIR:-$HOME/.cline}" "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}" "${CODEX_HOME:-$HOME/.codex}" "${COPILOT_CONFIG_DIR:-${COPILOT_HOME:-$HOME/.copilot}}" "${CURSOR_CONFIG_DIR:-$HOME/.cursor}" "${HERMES_HOME:-$HOME/.hermes}" "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}" "${KIMI_CONFIG_DIR:-$HOME/.config/agents}" "$HOME/.agents" "${KIMI_CODE_HOME:-$HOME/.kimi-code}" "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}" "${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}" "${QWEN_CONFIG_DIR:-$HOME/.qwen}" "${TRAE_CONFIG_DIR:-$HOME/.trae}" "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}" "${ZCODE_CONFIG_DIR:-$HOME/.zcode}" "${GROK_AGENTS_HOME:-$HOME/.agents}"; for _h; do _gsd_at "$_h/gsd-core/bin/${_GSD_SHIM_NAME}" && return 0; done; return 1; }; if _gsd_at "${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif _gsd_homes; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif unset -f gsd_run; _G="$(command -v gsd_run)"; [ -n "$_G" ] && _gsd_id_ok "$_G"; then GSD_TOOLS="$_G"; gsd_run() { "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and no identity-proving gsd_run is on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; GSD_IDENTITY_STATUS=unverified; _gsd_id_ok gsd_run && GSD_IDENTITY_STATUS=ok; export GSD_IDENTITY_STATUS; [ "$GSD_IDENTITY_STATUS" = ok ] || echo "WARNING: \"$GSD_TOOLS\" did not prove it is @opengsd/gsd-core - it is either a different package or an @opengsd/gsd-core older than the runtime-identity verb. See docs/how-to/diagnose-a-foreign-gsd-tools.md" >&2; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
|
|
12
16
|
# #4772: this file is Read, not textually substituted - write the parent command's original argument string in place of $ARGUMENTS so --ws reaches the query.
|
|
13
|
-
GSD_WS=$(echo " $
|
|
17
|
+
GSD_WS=$(echo " $GSD_ARGS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
|
|
14
18
|
PHASE_GOAL=$(gsd_run query roadmap.get-phase "${phase_number}" ${GSD_WS:+--ws=${GSD_WS##* }} --pick goal)
|
|
15
19
|
USER_STORY_VALID=$(gsd_run query user-story.validate --story "$PHASE_GOAL" --pick valid)
|
|
16
20
|
if [ "$USER_STORY_VALID" != "true" ]; then
|
|
@@ -39,10 +39,14 @@ No Pass/Fail buttons. No severity questions. Just: "Here's what should happen. D
|
|
|
39
39
|
If $ARGUMENTS contains a phase number, load context:
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
|
+
IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
|
|
43
|
+
$ARGUMENTS
|
|
44
|
+
GSD_ARGS_END
|
|
45
|
+
GSD_ARGS=${GSD_ARGS%$'\n'}
|
|
42
46
|
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; _gsd_at() { for _p; do if [ -f "$_p" ]; then GSD_TOOLS="$_p"; return 0; fi; done; return 1; }; _gsd_id_ok() { case "$("$1" runtime-identity --raw 2>/dev/null || true)" in '{"packageName":"@opengsd/gsd-core"'*'}') return 0;; *) return 1;; esac; }; _gsd_homes() { set -- "${CLAUDE_CONFIG_DIR:-$HOME/.claude}" "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}" "$HOME/.gemini/antigravity-ide" "$HOME/.gemini/antigravity-cli" "${AUGMENT_CONFIG_DIR:-$HOME/.augment}" "${CLINE_CONFIG_DIR:-$HOME/.cline}" "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}" "${CODEX_HOME:-$HOME/.codex}" "${COPILOT_CONFIG_DIR:-${COPILOT_HOME:-$HOME/.copilot}}" "${CURSOR_CONFIG_DIR:-$HOME/.cursor}" "${HERMES_HOME:-$HOME/.hermes}" "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}" "${KIMI_CONFIG_DIR:-$HOME/.config/agents}" "$HOME/.agents" "${KIMI_CODE_HOME:-$HOME/.kimi-code}" "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}" "${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}" "${QWEN_CONFIG_DIR:-$HOME/.qwen}" "${TRAE_CONFIG_DIR:-$HOME/.trae}" "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}" "${ZCODE_CONFIG_DIR:-$HOME/.zcode}" "${GROK_AGENTS_HOME:-$HOME/.agents}"; for _h; do _gsd_at "$_h/gsd-core/bin/${_GSD_SHIM_NAME}" && return 0; done; return 1; }; if _gsd_at "${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif _gsd_homes; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif unset -f gsd_run; _G="$(command -v gsd_run)"; [ -n "$_G" ] && _gsd_id_ok "$_G"; then GSD_TOOLS="$_G"; gsd_run() { "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and no identity-proving gsd_run is on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; GSD_IDENTITY_STATUS=unverified; _gsd_id_ok gsd_run && GSD_IDENTITY_STATUS=ok; export GSD_IDENTITY_STATUS; [ "$GSD_IDENTITY_STATUS" = ok ] || echo "WARNING: \"$GSD_TOOLS\" did not prove it is @opengsd/gsd-core - it is either a different package or an @opengsd/gsd-core older than the runtime-identity verb. See docs/how-to/diagnose-a-foreign-gsd-tools.md" >&2; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
|
|
43
47
|
GSD_WS=""
|
|
44
|
-
echo "$
|
|
45
|
-
PHASE_ARG=$(echo "$
|
|
48
|
+
echo "$GSD_ARGS" | grep -qE -- '--ws[[:space:]]+[A-Za-z0-9._-]+' && GSD_WS=$(echo "$GSD_ARGS" | grep -oE -- '--ws[[:space:]]+[A-Za-z0-9._-]+')
|
|
49
|
+
PHASE_ARG=$(echo "$GSD_ARGS" | sed -E 's/--ws[[:space:]]+[A-Za-z0-9._-]+//g' | xargs)
|
|
46
50
|
|
|
47
51
|
INIT=$(gsd_run query init.verify-work "${PHASE_ARG}" ${GSD_WS:+--ws=${GSD_WS##* }})
|
|
48
52
|
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
|
@@ -678,7 +682,11 @@ itself (its only write is the canonicalization, #4663).
|
|
|
678
682
|
Load the step's inputs through the SAME bundle execute-phase loads, then include the step:
|
|
679
683
|
|
|
680
684
|
```bash
|
|
681
|
-
|
|
685
|
+
IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
|
|
686
|
+
$ARGUMENTS
|
|
687
|
+
GSD_ARGS_END
|
|
688
|
+
GSD_ARGS=${GSD_ARGS%$'\n'}
|
|
689
|
+
GSD_WS=$(echo " $GSD_ARGS" | sed -nE 's/.* --ws +([A-Za-z0-9][A-Za-z0-9._-]*).*/--ws \1/p' | head -n 1)
|
|
682
690
|
EXECUTE_INIT=$(gsd_run query init.execute-phase "{phase}" ${GSD_WS:+--ws=${GSD_WS##* }})
|
|
683
691
|
if [[ "$EXECUTE_INIT" == @file:* ]]; then EXECUTE_INIT=$(cat "${EXECUTE_INIT#@file:}"); fi
|
|
684
692
|
for _k in phase_dir phase_number verifier_model phase_req_ids requirements_path section_manifest response_language; do
|
|
@@ -227,7 +227,9 @@ function resolveRuntimeIdentity(cwd, configPath, resolveRuntimeNameFromCandidate
|
|
|
227
227
|
// never a resolution failure (this function only ever throws for the
|
|
228
228
|
// config.json read above, which the caller's catch already handles).
|
|
229
229
|
try {
|
|
230
|
-
|
|
230
|
+
// #5017: the store root is GSD_HOME when set and non-blank, else the home
|
|
231
|
+
// directory — the config loader's rule (src/runtime-homes.cts resolveGsdHome).
|
|
232
|
+
const defaultsPath = path.join(process.env.GSD_HOME || os.homedir(), '.gsd', 'defaults.json');
|
|
231
233
|
const defaultsRaw = fs.readFileSync(defaultsPath, 'utf-8');
|
|
232
234
|
const defaultsParsed = JSON.parse(defaultsRaw);
|
|
233
235
|
if (defaultsParsed && typeof defaultsParsed === 'object' && 'runtime' in defaultsParsed) {
|
|
@@ -435,7 +435,8 @@ function resolveFallbackIsolation(root, configPath) {
|
|
|
435
435
|
}
|
|
436
436
|
if (!runtimeId) {
|
|
437
437
|
try {
|
|
438
|
-
|
|
438
|
+
// #5017: GSD_HOME when set and non-blank, else the home directory — the loader's rule.
|
|
439
|
+
const defaultsPath = path.join(process.env.GSD_HOME || os.homedir(), '.gsd', 'defaults.json');
|
|
439
440
|
const defaultsParsed = JSON.parse(fs.readFileSync(defaultsPath, 'utf-8'));
|
|
440
441
|
if (defaultsParsed && typeof defaultsParsed === 'object' && 'runtime' in defaultsParsed) {
|
|
441
442
|
runtimeId = resolveRuntimeNameFromCandidates(defaultsParsed.runtime) || null;
|
|
@@ -67,6 +67,8 @@
|
|
|
67
67
|
// `git check-ignore` / `git ls-files` / `git rm --cached`, and the
|
|
68
68
|
// destination of a one-source `cp`/`mv` (#4856). The #4856 carve-outs
|
|
69
69
|
// fail closed: an option they do not list withdraws them from the segment.
|
|
70
|
+
// A git global option the walker does not know also keeps the #5045 `-c`
|
|
71
|
+
// scan running over every later word (see walkGitGlobals).
|
|
70
72
|
// A shell interpreter (bash/sh/zsh/dash/ksh/su) has its script scanned
|
|
71
73
|
// whether it arrives via `-c '…'`, a `<( )` file operand, a heredoc /
|
|
72
74
|
// here-string, or a pipe from a knowable `echo`/`printf` source
|
|
@@ -192,14 +194,29 @@ const GIT_PATHSPEC_SUBCOMMANDS = new Map([
|
|
|
192
194
|
}],
|
|
193
195
|
]);
|
|
194
196
|
|
|
195
|
-
// git's global options (
|
|
196
|
-
//
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
|
|
197
|
+
// git's global options (git.c handle_options, git 2.49) — the ONE vocabulary
|
|
198
|
+
// both the #4856 pathspec exemption and the #5045 -c scan walk, via
|
|
199
|
+
// walkGitGlobals. A value option consumes the next word; the long options in
|
|
200
|
+
// GIT_GLOBAL_EQUALS_FORM_OPTIONS also take it attached as `--opt=value`.
|
|
201
|
+
// `-C`, `-c` and the hidden `--shallow-file` accept only the separate word
|
|
202
|
+
// (git rejects `--shallow-file=x` and `-c<name>=<value>`), and
|
|
203
|
+
// `--exec-path` runs a subcommand only as `--exec-path=<path>` (bare, it
|
|
204
|
+
// prints the path and exits). Any other option fails closed for both
|
|
205
|
+
// consumers: its arity is unknown, so its value could be read as the
|
|
206
|
+
// subcommand (`git -C ls-files show HEAD:.env` runs `show`). The pathspec
|
|
207
|
+
// exemption is withdrawn, and the -c scan keeps collecting every later
|
|
208
|
+
// `-c <value>` pair instead of stopping.
|
|
209
|
+
const GIT_GLOBAL_VALUE_OPTIONS = new Set([
|
|
210
|
+
'-C', '-c', '--git-dir', '--work-tree', '--namespace', '--config-env', '--attr-source',
|
|
211
|
+
'--shallow-file',
|
|
212
|
+
]);
|
|
213
|
+
const GIT_GLOBAL_EQUALS_FORM_OPTIONS = new Set([
|
|
214
|
+
'--git-dir', '--work-tree', '--namespace', '--config-env', '--attr-source', '--exec-path',
|
|
215
|
+
]);
|
|
200
216
|
const GIT_GLOBAL_FLAGS = new Set([
|
|
201
217
|
'-p', '--paginate', '-P', '--no-pager', '--no-replace-objects', '--no-lazy-fetch',
|
|
202
218
|
'--no-optional-locks', '--no-advice', '--bare',
|
|
219
|
+
'--literal-pathspecs', '--glob-pathspecs', '--noglob-pathspecs', '--icase-pathspecs',
|
|
203
220
|
]);
|
|
204
221
|
|
|
205
222
|
// #4856: `cp`/`mv` write their destination and never print it, so a secret
|
|
@@ -789,6 +806,29 @@ function resolveCommand(words) {
|
|
|
789
806
|
return { base: lastSegment(words[idx].text).toLowerCase(), operands: words.slice(idx + 1) };
|
|
790
807
|
}
|
|
791
808
|
|
|
809
|
+
/**
|
|
810
|
+
* Rescan only the command-valued -c keys selected by #5045. This deliberately
|
|
811
|
+
* does not resolve config files, --config-env/GIT_CONFIG_* values, or other
|
|
812
|
+
* executing keys (diff drivers, filters, credential helpers, ssh, gpg).
|
|
813
|
+
* Those are documented gaps, not a promise of complete Git config analysis.
|
|
814
|
+
* Global option values and subcommand arguments are data, not more -c flags.
|
|
815
|
+
*/
|
|
816
|
+
function scanGitConfig(operands, depth) {
|
|
817
|
+
for (const config of walkGitGlobals(operands).configs) {
|
|
818
|
+
const eq = config.indexOf('=');
|
|
819
|
+
if (eq === -1) continue;
|
|
820
|
+
const key = config.slice(0, eq).toLowerCase();
|
|
821
|
+
const value = config.slice(eq + 1);
|
|
822
|
+
let script;
|
|
823
|
+
if (key.startsWith('alias.') && value.startsWith('!')) script = value.slice(1);
|
|
824
|
+
else if (key === 'diff.external') script = value;
|
|
825
|
+
else continue;
|
|
826
|
+
const hit = findSecretRead(script, depth + 1);
|
|
827
|
+
if (hit) return hit;
|
|
828
|
+
}
|
|
829
|
+
return null;
|
|
830
|
+
}
|
|
831
|
+
|
|
792
832
|
// Operand indices the segment's command consumes as a NAME, never as
|
|
793
833
|
// CONTENTS (#4639, #4856). Only these positions skip the operand check;
|
|
794
834
|
// every other operand is still checked, so a carve-out cannot launder a read
|
|
@@ -820,26 +860,38 @@ function isListedFlag(text, flags) {
|
|
|
820
860
|
return /^-[A-Za-z]+$/.test(text) && [...text.slice(1)].every((ch) => flags.has(`-${ch}`));
|
|
821
861
|
}
|
|
822
862
|
|
|
823
|
-
//
|
|
824
|
-
//
|
|
825
|
-
|
|
863
|
+
// Walks git's global options (see GIT_GLOBAL_VALUE_OPTIONS). `sub` is the
|
|
864
|
+
// subcommand's index, or -1 when an unlisted option (or the end of the words)
|
|
865
|
+
// comes first; `configs` holds every `-c` value seen on the way, in order.
|
|
866
|
+
// Past an unlisted option the subcommand cannot be located, so `configs`
|
|
867
|
+
// conservatively takes the word after every later `-c` as well: a read
|
|
868
|
+
// still needs a command-valued key, so a harmless value stays allowed.
|
|
869
|
+
function walkGitGlobals(operands) {
|
|
870
|
+
const configs = [];
|
|
826
871
|
for (let k = 0; k < operands.length; k++) {
|
|
827
872
|
const t = operands[k].text;
|
|
828
|
-
if (!t.startsWith('-')) return k;
|
|
829
|
-
if (GIT_GLOBAL_VALUE_OPTIONS.has(t)) {
|
|
873
|
+
if (!t.startsWith('-')) return { sub: k, configs };
|
|
874
|
+
if (GIT_GLOBAL_VALUE_OPTIONS.has(t)) {
|
|
875
|
+
if (t === '-c' && k + 1 < operands.length) configs.push(operands[k + 1].text);
|
|
876
|
+
k++;
|
|
877
|
+
continue;
|
|
878
|
+
}
|
|
830
879
|
if (GIT_GLOBAL_FLAGS.has(t)) continue;
|
|
831
880
|
const eq = t.indexOf('=');
|
|
832
|
-
|
|
833
|
-
|
|
881
|
+
if (t.startsWith('--') && eq !== -1 && GIT_GLOBAL_EQUALS_FORM_OPTIONS.has(t.slice(0, eq))) continue;
|
|
882
|
+
for (let j = k + 1; j + 1 < operands.length; j++) {
|
|
883
|
+
if (operands[j].text === '-c') configs.push(operands[++j].text);
|
|
884
|
+
}
|
|
885
|
+
return { sub: -1, configs };
|
|
834
886
|
}
|
|
835
|
-
return -1;
|
|
887
|
+
return { sub: -1, configs };
|
|
836
888
|
}
|
|
837
889
|
|
|
838
890
|
// #4856: the pathspec operands of a GIT_PATHSPEC_SUBCOMMANDS invocation. git
|
|
839
891
|
// permutes options, so one may follow a pathspec; after `--` every word is a
|
|
840
892
|
// pathspec. The subcommand is matched case-sensitively, as git looks it up.
|
|
841
893
|
function gitPathspecIndices(operands) {
|
|
842
|
-
const sub =
|
|
894
|
+
const { sub } = walkGitGlobals(operands);
|
|
843
895
|
const spec = sub === -1 ? undefined : GIT_PATHSPEC_SUBCOMMANDS.get(operands[sub].text);
|
|
844
896
|
if (!spec) return new Set();
|
|
845
897
|
const pathspecs = new Set();
|
|
@@ -1007,6 +1059,12 @@ function findSecretRead(command, depth) {
|
|
|
1007
1059
|
// `bash .env` (file mode) is caught by the operand check below.
|
|
1008
1060
|
}
|
|
1009
1061
|
|
|
1062
|
+
// Git -c can carry a shell script instead of a file-name operand (#5045).
|
|
1063
|
+
if ((base === 'git' || base === 'git.exe') && depth < MAX_NESTING_DEPTH) {
|
|
1064
|
+
const hit = scanGitConfig(operands, depth);
|
|
1065
|
+
if (hit) return hit;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1010
1068
|
if (NON_READING_COMMANDS.has(base)) continue;
|
|
1011
1069
|
|
|
1012
1070
|
const nameOnly = nameOnlyOperandIndices(base, operands);
|
|
@@ -39,6 +39,27 @@ cleanup_temp_files() {
|
|
|
39
39
|
}
|
|
40
40
|
trap cleanup_temp_files EXIT
|
|
41
41
|
|
|
42
|
+
# The first line of $1 (everything before the first newline), matched BYTE-wise.
|
|
43
|
+
#
|
|
44
|
+
# `${1%%$'\n'*}` is a longest-suffix glob match, and on bash 3.2 (macOS's
|
|
45
|
+
# /bin/bash) in a multibyte locale such as en_US.UTF-8 it costs ~3.5 s on a
|
|
46
|
+
# 112KB value (#5473, measured inside the macOS conformance job: the same hook
|
|
47
|
+
# on the same 114750-byte command took ~5.6 s under LC_ALL=en_US.UTF-8 and
|
|
48
|
+
# ~0.55 s under LC_ALL=C, and a trace put 3.5 s of it on this expansion). A
|
|
49
|
+
# newline is the same single byte in every locale, so matching it byte-wise
|
|
50
|
+
# changes no result.
|
|
51
|
+
#
|
|
52
|
+
# ONLY call this inside a command substitution, `SUBJECT=$(gsd_first_line "$MSG")`:
|
|
53
|
+
# the substitution's subshell is what keeps LC_ALL=C from leaking into the rest
|
|
54
|
+
# of the hook, whose regexes and length arithmetic keep the caller's locale. A
|
|
55
|
+
# substitution is a fork with no pipe between two processes, so it keeps the
|
|
56
|
+
# property the pure-expansion form was chosen for (no `head -1` SIGPIPE race
|
|
57
|
+
# under `set -euo pipefail`, #4429); printf is a builtin, so no exec either.
|
|
58
|
+
gsd_first_line() {
|
|
59
|
+
LC_ALL=C
|
|
60
|
+
printf '%s' "${1%%$'\n'*}"
|
|
61
|
+
}
|
|
62
|
+
|
|
42
63
|
# The 10 built-in Conventional Commits types — the SINGLE declaration (#3811
|
|
43
64
|
# review finding: this was previously hand-typed a second time inside the
|
|
44
65
|
# node -e script below, a generative-fix-divergence risk per CLAUDE.md's
|
|
@@ -575,18 +596,19 @@ if [ "$CLASSIFY_STATUS" = "0" ]; then
|
|
|
575
596
|
SUBJECT=$(GIT_CMD_LIB="$HOOK_DIR/lib/git-cmd.js" MSG="$MSG" node -e "
|
|
576
597
|
const {resolveCommitSubject}=require(process.env.GIT_CMD_LIB);
|
|
577
598
|
process.stdout.write(resolveCommitSubject(process.env.MSG));
|
|
578
|
-
" 2>/dev/null) || SUBJECT
|
|
599
|
+
" 2>/dev/null) || SUBJECT=$(gsd_first_line "$MSG")
|
|
579
600
|
else
|
|
580
|
-
# Pure parameter expansion, not
|
|
601
|
+
# Pure parameter expansion (in gsd_first_line), not
|
|
602
|
+
# `echo "$MSG" | head -1`: that pipeline
|
|
581
603
|
# raced a SIGPIPE under `set -euo pipefail` whenever $MSG had a body
|
|
582
604
|
# (the common case) — `head -1` can close its read end as soon as it
|
|
583
605
|
# has the first line, and if `echo`'s write lands after that close,
|
|
584
606
|
# `echo` dies with signal 13 (exit 141), which is NOT suppressed by
|
|
585
607
|
# `set -e` and aborted the whole hook intermittently (observed in
|
|
586
|
-
# tests/hooks-opt-in.test.cjs's --fixup=HEAD "round 7" case).
|
|
587
|
-
#
|
|
608
|
+
# tests/hooks-opt-in.test.cjs's --fixup=HEAD "round 7" case). No pipe
|
|
609
|
+
# means no race surface. Equivalent to
|
|
588
610
|
# `head -1` for single-line, multi-line, and trailing-newline input.
|
|
589
|
-
SUBJECT
|
|
611
|
+
SUBJECT=$(gsd_first_line "$MSG")
|
|
590
612
|
fi
|
|
591
613
|
# Single source of truth for the accepted commit-type list (#3811): the
|
|
592
614
|
# 10 built-ins plus whatever passed the safe-token filter above. Both the
|
|
@@ -227,7 +227,9 @@ function resolveRuntimeIdentity(cwd, configPath, resolveRuntimeNameFromCandidate
|
|
|
227
227
|
// never a resolution failure (this function only ever throws for the
|
|
228
228
|
// config.json read above, which the caller's catch already handles).
|
|
229
229
|
try {
|
|
230
|
-
|
|
230
|
+
// #5017: the store root is GSD_HOME when set and non-blank, else the home
|
|
231
|
+
// directory — the config loader's rule (src/runtime-homes.cts resolveGsdHome).
|
|
232
|
+
const defaultsPath = path.join(process.env.GSD_HOME || os.homedir(), '.gsd', 'defaults.json');
|
|
231
233
|
const defaultsRaw = fs.readFileSync(defaultsPath, 'utf-8');
|
|
232
234
|
const defaultsParsed = JSON.parse(defaultsRaw);
|
|
233
235
|
if (defaultsParsed && typeof defaultsParsed === 'object' && 'runtime' in defaultsParsed) {
|
|
@@ -435,7 +435,8 @@ function resolveFallbackIsolation(root, configPath) {
|
|
|
435
435
|
}
|
|
436
436
|
if (!runtimeId) {
|
|
437
437
|
try {
|
|
438
|
-
|
|
438
|
+
// #5017: GSD_HOME when set and non-blank, else the home directory — the loader's rule.
|
|
439
|
+
const defaultsPath = path.join(process.env.GSD_HOME || os.homedir(), '.gsd', 'defaults.json');
|
|
439
440
|
const defaultsParsed = JSON.parse(fs.readFileSync(defaultsPath, 'utf-8'));
|
|
440
441
|
if (defaultsParsed && typeof defaultsParsed === 'object' && 'runtime' in defaultsParsed) {
|
|
441
442
|
runtimeId = resolveRuntimeNameFromCandidates(defaultsParsed.runtime) || null;
|
|
@@ -67,6 +67,8 @@
|
|
|
67
67
|
// `git check-ignore` / `git ls-files` / `git rm --cached`, and the
|
|
68
68
|
// destination of a one-source `cp`/`mv` (#4856). The #4856 carve-outs
|
|
69
69
|
// fail closed: an option they do not list withdraws them from the segment.
|
|
70
|
+
// A git global option the walker does not know also keeps the #5045 `-c`
|
|
71
|
+
// scan running over every later word (see walkGitGlobals).
|
|
70
72
|
// A shell interpreter (bash/sh/zsh/dash/ksh/su) has its script scanned
|
|
71
73
|
// whether it arrives via `-c '…'`, a `<( )` file operand, a heredoc /
|
|
72
74
|
// here-string, or a pipe from a knowable `echo`/`printf` source
|
|
@@ -192,14 +194,29 @@ const GIT_PATHSPEC_SUBCOMMANDS = new Map([
|
|
|
192
194
|
}],
|
|
193
195
|
]);
|
|
194
196
|
|
|
195
|
-
// git's global options (
|
|
196
|
-
//
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
|
|
197
|
+
// git's global options (git.c handle_options, git 2.49) — the ONE vocabulary
|
|
198
|
+
// both the #4856 pathspec exemption and the #5045 -c scan walk, via
|
|
199
|
+
// walkGitGlobals. A value option consumes the next word; the long options in
|
|
200
|
+
// GIT_GLOBAL_EQUALS_FORM_OPTIONS also take it attached as `--opt=value`.
|
|
201
|
+
// `-C`, `-c` and the hidden `--shallow-file` accept only the separate word
|
|
202
|
+
// (git rejects `--shallow-file=x` and `-c<name>=<value>`), and
|
|
203
|
+
// `--exec-path` runs a subcommand only as `--exec-path=<path>` (bare, it
|
|
204
|
+
// prints the path and exits). Any other option fails closed for both
|
|
205
|
+
// consumers: its arity is unknown, so its value could be read as the
|
|
206
|
+
// subcommand (`git -C ls-files show HEAD:.env` runs `show`). The pathspec
|
|
207
|
+
// exemption is withdrawn, and the -c scan keeps collecting every later
|
|
208
|
+
// `-c <value>` pair instead of stopping.
|
|
209
|
+
const GIT_GLOBAL_VALUE_OPTIONS = new Set([
|
|
210
|
+
'-C', '-c', '--git-dir', '--work-tree', '--namespace', '--config-env', '--attr-source',
|
|
211
|
+
'--shallow-file',
|
|
212
|
+
]);
|
|
213
|
+
const GIT_GLOBAL_EQUALS_FORM_OPTIONS = new Set([
|
|
214
|
+
'--git-dir', '--work-tree', '--namespace', '--config-env', '--attr-source', '--exec-path',
|
|
215
|
+
]);
|
|
200
216
|
const GIT_GLOBAL_FLAGS = new Set([
|
|
201
217
|
'-p', '--paginate', '-P', '--no-pager', '--no-replace-objects', '--no-lazy-fetch',
|
|
202
218
|
'--no-optional-locks', '--no-advice', '--bare',
|
|
219
|
+
'--literal-pathspecs', '--glob-pathspecs', '--noglob-pathspecs', '--icase-pathspecs',
|
|
203
220
|
]);
|
|
204
221
|
|
|
205
222
|
// #4856: `cp`/`mv` write their destination and never print it, so a secret
|
|
@@ -789,6 +806,29 @@ function resolveCommand(words) {
|
|
|
789
806
|
return { base: lastSegment(words[idx].text).toLowerCase(), operands: words.slice(idx + 1) };
|
|
790
807
|
}
|
|
791
808
|
|
|
809
|
+
/**
|
|
810
|
+
* Rescan only the command-valued -c keys selected by #5045. This deliberately
|
|
811
|
+
* does not resolve config files, --config-env/GIT_CONFIG_* values, or other
|
|
812
|
+
* executing keys (diff drivers, filters, credential helpers, ssh, gpg).
|
|
813
|
+
* Those are documented gaps, not a promise of complete Git config analysis.
|
|
814
|
+
* Global option values and subcommand arguments are data, not more -c flags.
|
|
815
|
+
*/
|
|
816
|
+
function scanGitConfig(operands, depth) {
|
|
817
|
+
for (const config of walkGitGlobals(operands).configs) {
|
|
818
|
+
const eq = config.indexOf('=');
|
|
819
|
+
if (eq === -1) continue;
|
|
820
|
+
const key = config.slice(0, eq).toLowerCase();
|
|
821
|
+
const value = config.slice(eq + 1);
|
|
822
|
+
let script;
|
|
823
|
+
if (key.startsWith('alias.') && value.startsWith('!')) script = value.slice(1);
|
|
824
|
+
else if (key === 'diff.external') script = value;
|
|
825
|
+
else continue;
|
|
826
|
+
const hit = findSecretRead(script, depth + 1);
|
|
827
|
+
if (hit) return hit;
|
|
828
|
+
}
|
|
829
|
+
return null;
|
|
830
|
+
}
|
|
831
|
+
|
|
792
832
|
// Operand indices the segment's command consumes as a NAME, never as
|
|
793
833
|
// CONTENTS (#4639, #4856). Only these positions skip the operand check;
|
|
794
834
|
// every other operand is still checked, so a carve-out cannot launder a read
|
|
@@ -820,26 +860,38 @@ function isListedFlag(text, flags) {
|
|
|
820
860
|
return /^-[A-Za-z]+$/.test(text) && [...text.slice(1)].every((ch) => flags.has(`-${ch}`));
|
|
821
861
|
}
|
|
822
862
|
|
|
823
|
-
//
|
|
824
|
-
//
|
|
825
|
-
|
|
863
|
+
// Walks git's global options (see GIT_GLOBAL_VALUE_OPTIONS). `sub` is the
|
|
864
|
+
// subcommand's index, or -1 when an unlisted option (or the end of the words)
|
|
865
|
+
// comes first; `configs` holds every `-c` value seen on the way, in order.
|
|
866
|
+
// Past an unlisted option the subcommand cannot be located, so `configs`
|
|
867
|
+
// conservatively takes the word after every later `-c` as well: a read
|
|
868
|
+
// still needs a command-valued key, so a harmless value stays allowed.
|
|
869
|
+
function walkGitGlobals(operands) {
|
|
870
|
+
const configs = [];
|
|
826
871
|
for (let k = 0; k < operands.length; k++) {
|
|
827
872
|
const t = operands[k].text;
|
|
828
|
-
if (!t.startsWith('-')) return k;
|
|
829
|
-
if (GIT_GLOBAL_VALUE_OPTIONS.has(t)) {
|
|
873
|
+
if (!t.startsWith('-')) return { sub: k, configs };
|
|
874
|
+
if (GIT_GLOBAL_VALUE_OPTIONS.has(t)) {
|
|
875
|
+
if (t === '-c' && k + 1 < operands.length) configs.push(operands[k + 1].text);
|
|
876
|
+
k++;
|
|
877
|
+
continue;
|
|
878
|
+
}
|
|
830
879
|
if (GIT_GLOBAL_FLAGS.has(t)) continue;
|
|
831
880
|
const eq = t.indexOf('=');
|
|
832
|
-
|
|
833
|
-
|
|
881
|
+
if (t.startsWith('--') && eq !== -1 && GIT_GLOBAL_EQUALS_FORM_OPTIONS.has(t.slice(0, eq))) continue;
|
|
882
|
+
for (let j = k + 1; j + 1 < operands.length; j++) {
|
|
883
|
+
if (operands[j].text === '-c') configs.push(operands[++j].text);
|
|
884
|
+
}
|
|
885
|
+
return { sub: -1, configs };
|
|
834
886
|
}
|
|
835
|
-
return -1;
|
|
887
|
+
return { sub: -1, configs };
|
|
836
888
|
}
|
|
837
889
|
|
|
838
890
|
// #4856: the pathspec operands of a GIT_PATHSPEC_SUBCOMMANDS invocation. git
|
|
839
891
|
// permutes options, so one may follow a pathspec; after `--` every word is a
|
|
840
892
|
// pathspec. The subcommand is matched case-sensitively, as git looks it up.
|
|
841
893
|
function gitPathspecIndices(operands) {
|
|
842
|
-
const sub =
|
|
894
|
+
const { sub } = walkGitGlobals(operands);
|
|
843
895
|
const spec = sub === -1 ? undefined : GIT_PATHSPEC_SUBCOMMANDS.get(operands[sub].text);
|
|
844
896
|
if (!spec) return new Set();
|
|
845
897
|
const pathspecs = new Set();
|
|
@@ -1007,6 +1059,12 @@ function findSecretRead(command, depth) {
|
|
|
1007
1059
|
// `bash .env` (file mode) is caught by the operand check below.
|
|
1008
1060
|
}
|
|
1009
1061
|
|
|
1062
|
+
// Git -c can carry a shell script instead of a file-name operand (#5045).
|
|
1063
|
+
if ((base === 'git' || base === 'git.exe') && depth < MAX_NESTING_DEPTH) {
|
|
1064
|
+
const hit = scanGitConfig(operands, depth);
|
|
1065
|
+
if (hit) return hit;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1010
1068
|
if (NON_READING_COMMANDS.has(base)) continue;
|
|
1011
1069
|
|
|
1012
1070
|
const nameOnly = nameOnlyOperandIndices(base, operands);
|