@opengsd/gsd-core 1.6.0-rc.3 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/agents/gsd-advisor-researcher.md +2 -0
- package/agents/gsd-ai-researcher.md +2 -0
- package/agents/gsd-assumptions-analyzer.md +2 -0
- package/agents/gsd-doc-classifier.md +2 -0
- package/agents/gsd-doc-synthesizer.md +2 -0
- package/agents/gsd-domain-researcher.md +2 -0
- package/agents/gsd-eval-auditor.md +6 -9
- package/agents/gsd-phase-researcher.md +2 -0
- package/agents/gsd-project-researcher.md +2 -0
- package/agents/gsd-research-synthesizer.md +2 -0
- package/agents/gsd-ui-researcher.md +2 -0
- package/bin/install.js +219 -5
- package/gemini-extension.json +1 -1
- package/gsd-core/bin/gsd-tools.cjs +11 -1
- package/gsd-core/bin/lib/capability-registry.cjs +48 -48
- package/gsd-core/bin/lib/command-aliases.cjs +10 -1
- package/gsd-core/bin/lib/decisions.cjs +27 -0
- package/gsd-core/bin/lib/eval-command-router.cjs +21 -0
- package/gsd-core/bin/lib/eval.cjs +60 -0
- package/gsd-core/bin/lib/frontmatter.cjs +132 -13
- package/gsd-core/bin/lib/init.cjs +131 -26
- package/gsd-core/bin/lib/io.cjs +1 -0
- package/gsd-core/bin/lib/milestone.cjs +8 -0
- package/gsd-core/bin/lib/phase.cjs +29 -3
- package/gsd-core/bin/lib/plan-scan.cjs +2 -2
- package/gsd-core/bin/lib/roadmap.cjs +12 -1
- package/gsd-core/bin/lib/shell-command-projection.cjs +31 -1
- package/gsd-core/bin/lib/state.cjs +34 -8
- package/gsd-core/bin/lib/uat-predicate.cjs +13 -6
- package/gsd-core/bin/lib/verification.cjs +67 -6
- package/gsd-core/bin/lib/verify.cjs +8 -1
- package/gsd-core/bin/shared/config-defaults.manifest.json +3 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +2 -1
- package/gsd-core/bin/shared/model-catalog.json +8 -8
- package/gsd-core/references/untrusted-input-boundary.md +13 -0
- package/gsd-core/workflows/autonomous.md +53 -46
- package/gsd-core/workflows/complete-milestone.md +27 -8
- package/gsd-core/workflows/execute-phase.md +1 -1
- package/gsd-core/workflows/manager.md +17 -7
- package/gsd-core/workflows/new-project.md +78 -12
- package/gsd-core/workflows/profile-user.md +6 -2
- package/gsd-core/workflows/progress.md +37 -4
- package/gsd-core/workflows/quick.md +3 -1
- package/gsd-core/workflows/settings-advanced.md +10 -10
- package/gsd-core/workflows/ship.md +3 -1
- package/gsd-core/workflows/spec-phase.md +3 -1
- package/gsd-core/workflows/transition.md +14 -12
- package/gsd-core/workflows/ui-review.md +2 -6
- package/gsd-core/workflows/verify-work.md +44 -0
- package/hooks/dist/gsd-read-injection-scanner.js +49 -25
- package/hooks/gsd-read-injection-scanner.js +49 -25
- package/hooks/hooks.json +1 -1
- package/package.json +1 -1
- package/scripts/check-alias-drift.cjs +5 -0
- package/scripts/prompt-injection-scan.sh +5 -0
|
@@ -17,6 +17,8 @@ Spawned by `discuss-phase` via `Task()`. You do NOT present output directly to t
|
|
|
17
17
|
- Return structured markdown output for the main agent to synthesize
|
|
18
18
|
</role>
|
|
19
19
|
|
|
20
|
+
@~/.claude/gsd-core/references/untrusted-input-boundary.md
|
|
21
|
+
|
|
20
22
|
<documentation_lookup>
|
|
21
23
|
@~/.claude/gsd-core/references/research-documentation-lookup.md
|
|
22
24
|
</documentation_lookup>
|
|
@@ -16,6 +16,8 @@ You are a GSD AI researcher. Answer: "How do I correctly implement this AI syste
|
|
|
16
16
|
Write Sections 3–4b of AI-SPEC.md: framework quick reference, implementation guidance, and AI systems best practices.
|
|
17
17
|
</role>
|
|
18
18
|
|
|
19
|
+
@~/.claude/gsd-core/references/untrusted-input-boundary.md
|
|
20
|
+
|
|
19
21
|
<documentation_lookup>
|
|
20
22
|
@~/.claude/gsd-core/references/research-documentation-lookup.md
|
|
21
23
|
</documentation_lookup>
|
|
@@ -18,6 +18,8 @@ Spawned by `discuss-phase-assumptions` via `Task()`. You do NOT present output d
|
|
|
18
18
|
- Flag topics where codebase analysis alone is insufficient (needs external research)
|
|
19
19
|
</role>
|
|
20
20
|
|
|
21
|
+
@~/.claude/gsd-core/references/untrusted-input-boundary.md
|
|
22
|
+
|
|
21
23
|
<input>
|
|
22
24
|
Agent receives via prompt:
|
|
23
25
|
|
|
@@ -18,6 +18,8 @@ You are a GSD doc classifier. You read ONE document and write a structured class
|
|
|
18
18
|
If the prompt contains a `<required_reading>` block, use the `Read` tool to load every file listed there before doing anything else. That is your primary context.
|
|
19
19
|
</role>
|
|
20
20
|
|
|
21
|
+
@~/.claude/gsd-core/references/untrusted-input-boundary.md
|
|
22
|
+
|
|
21
23
|
<why_this_matters>
|
|
22
24
|
Your classification drives extraction. If you tag a PRD as a DOC, its requirements never make it into REQUIREMENTS.md. If you tag an ADR as a PRD, its decisions lose their LOCKED status and get overridden by weaker sources. Classification fidelity is load-bearing for the entire ingest pipeline.
|
|
23
25
|
</why_this_matters>
|
|
@@ -20,6 +20,8 @@ You do NOT prompt the user. You do NOT write PROJECT.md, REQUIREMENTS.md, or ROA
|
|
|
20
20
|
If the prompt contains a `<required_reading>` block, load every file listed there first — especially `references/doc-conflict-engine.md` which defines your conflict report format.
|
|
21
21
|
</role>
|
|
22
22
|
|
|
23
|
+
@~/.claude/gsd-core/references/untrusted-input-boundary.md
|
|
24
|
+
|
|
23
25
|
<why_this_matters>
|
|
24
26
|
You are the precedence-enforcing layer. Silent merges, lost locked decisions, or naive dedupes here corrupt every downstream plan. When in doubt, surface the conflict rather than pick.
|
|
25
27
|
</why_this_matters>
|
|
@@ -16,6 +16,8 @@ You are a GSD domain researcher. Answer: "What do domain experts actually care a
|
|
|
16
16
|
Research the business domain — not the technical framework. Write Section 1b of AI-SPEC.md.
|
|
17
17
|
</role>
|
|
18
18
|
|
|
19
|
+
@~/.claude/gsd-core/references/untrusted-input-boundary.md
|
|
20
|
+
|
|
19
21
|
<documentation_lookup>
|
|
20
22
|
@~/.claude/gsd-core/references/research-documentation-lookup.md
|
|
21
23
|
</documentation_lookup>
|
|
@@ -109,17 +109,14 @@ Score 5 components (ok / partial / missing):
|
|
|
109
109
|
</step>
|
|
110
110
|
|
|
111
111
|
<step name="calculate_scores">
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
112
|
+
Do NOT compute scores by hand. Call the deterministic verb with your audited inputs:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
_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}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; 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
|
|
116
|
+
gsd_run query eval.score --covered <covered_count> --total <total_dimensions> --infra <tooling>,<dataset>,<cicd>,<guardrails>,<tracing> --raw
|
|
116
117
|
```
|
|
117
118
|
|
|
118
|
-
|
|
119
|
-
- 80-100: **PRODUCTION READY** — deploy with monitoring
|
|
120
|
-
- 60-79: **NEEDS WORK** — address CRITICAL gaps before production
|
|
121
|
-
- 40-59: **SIGNIFICANT GAPS** — do not deploy
|
|
122
|
-
- 0-39: **NOT IMPLEMENTED** — review AI-SPEC.md and implement
|
|
119
|
+
where each infra component is `ok`, `partial`, or `missing` (from the audit_infrastructure step). Parse the JSON result — it returns `coverage_score`, `infra_score`, `overall_score`, and `verdict` (PRODUCTION READY / NEEDS WORK / SIGNIFICANT GAPS / NOT IMPLEMENTED). Use those values verbatim in EVAL-REVIEW.md; never recompute or override them.
|
|
123
120
|
</step>
|
|
124
121
|
|
|
125
122
|
<step name="write_eval_review">
|
|
@@ -35,6 +35,8 @@ Spawned by `/gsd:plan-phase` (integrated) or `/gsd:plan-phase --research-phase <
|
|
|
35
35
|
Claims tagged `[ASSUMED]` signal to the planner and discuss-phase that the information needs user confirmation before becoming a locked decision. Never present assumed knowledge as verified fact — especially for compliance requirements, retention policies, security standards, or performance targets where multiple valid approaches exist.
|
|
36
36
|
</role>
|
|
37
37
|
|
|
38
|
+
@~/.claude/gsd-core/references/untrusted-input-boundary.md
|
|
39
|
+
|
|
38
40
|
<documentation_lookup>
|
|
39
41
|
@~/.claude/gsd-core/references/research-documentation-lookup.md
|
|
40
42
|
</documentation_lookup>
|
|
@@ -32,6 +32,8 @@ Your files feed the roadmap:
|
|
|
32
32
|
**Be comprehensive but opinionated.** "Use X because Y" not "Options are X, Y, Z."
|
|
33
33
|
</role>
|
|
34
34
|
|
|
35
|
+
@~/.claude/gsd-core/references/untrusted-input-boundary.md
|
|
36
|
+
|
|
35
37
|
<documentation_lookup>
|
|
36
38
|
@~/.claude/gsd-core/references/research-documentation-lookup.md
|
|
37
39
|
</documentation_lookup>
|
|
@@ -32,6 +32,8 @@ If the prompt contains a `<required_reading>` block, you MUST use the `Read` too
|
|
|
32
32
|
- Commit ALL research files (researchers write but don't commit — you commit everything)
|
|
33
33
|
</role>
|
|
34
34
|
|
|
35
|
+
@~/.claude/gsd-core/references/untrusted-input-boundary.md
|
|
36
|
+
|
|
35
37
|
<downstream_consumer>
|
|
36
38
|
Your SUMMARY.md is consumed by the gsd-roadmapper agent which uses it to:
|
|
37
39
|
|
|
@@ -27,6 +27,8 @@ If the prompt contains a `<required_reading>` block, you MUST use the `Read` too
|
|
|
27
27
|
- Return structured result to orchestrator
|
|
28
28
|
</role>
|
|
29
29
|
|
|
30
|
+
@~/.claude/gsd-core/references/untrusted-input-boundary.md
|
|
31
|
+
|
|
30
32
|
<documentation_lookup>
|
|
31
33
|
@~/.claude/gsd-core/references/research-documentation-lookup.md
|
|
32
34
|
</documentation_lookup>
|
package/bin/install.js
CHANGED
|
@@ -11307,10 +11307,14 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
11307
11307
|
configureKiloPermissions(isGlobal, configDir);
|
|
11308
11308
|
}
|
|
11309
11309
|
|
|
11310
|
-
// For non-Claude runtimes,
|
|
11311
|
-
// so resolveModelInternal() returns '' instead of Claude
|
|
11312
|
-
//
|
|
11313
|
-
//
|
|
11310
|
+
// For non-Claude runtimes, DEFAULT resolve_model_ids to "omit" in ~/.gsd/defaults.json
|
|
11311
|
+
// when it is absent or falsy, so resolveModelInternal() returns '' instead of Claude
|
|
11312
|
+
// aliases (opus/sonnet/haiku) the runtime can't resolve. An explicit `true` opt-in
|
|
11313
|
+
// (resolveModelInternal returns full materialized model IDs) MUST be preserved —
|
|
11314
|
+
// rewriting it to "omit" would make generated agent manifests inherit the active
|
|
11315
|
+
// chat model instead of pinning the resolved model. See #1156 (default-to-omit
|
|
11316
|
+
// intent) and #1569 (preserve explicit true). Guard matches the #130-class pattern
|
|
11317
|
+
// on configureOpencodePermissions above.
|
|
11314
11318
|
if (runtime !== 'claude' && !process.env.GSD_TEST_MODE) {
|
|
11315
11319
|
const gsdDir = path.join(os.homedir(), '.gsd');
|
|
11316
11320
|
const defaultsPath = path.join(gsdDir, 'defaults.json');
|
|
@@ -11318,7 +11322,22 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
11318
11322
|
fs.mkdirSync(gsdDir, { recursive: true });
|
|
11319
11323
|
let defaults = {};
|
|
11320
11324
|
try { defaults = JSON.parse(fs.readFileSync(defaultsPath, 'utf8')); } catch { /* new file */ }
|
|
11321
|
-
|
|
11325
|
+
// Recover a malformed (valid-JSON-but-non-object) defaults.json to a fresh object so
|
|
11326
|
+
// the write below succeeds and the file is no longer broken. Without this, `null` /
|
|
11327
|
+
// `[]` / a number / a string bypass the parse catch and either throw a TypeError on
|
|
11328
|
+
// property access (swallowed by the outer try/catch, leaving the file broken) or get
|
|
11329
|
+
// a property set that won't round-trip through JSON.stringify. (#1657)
|
|
11330
|
+
if (defaults === null || typeof defaults !== 'object' || Array.isArray(defaults)) {
|
|
11331
|
+
defaults = {};
|
|
11332
|
+
}
|
|
11333
|
+
// Three-valued domain: false/absent → aliases; true → full IDs; "omit" → ''.
|
|
11334
|
+
// Honor ONLY an explicit canonical `true` opt-in (full model IDs) and an existing
|
|
11335
|
+
// "omit"; default everything else — absent, falsy, OR any non-canonical value — to
|
|
11336
|
+
// "omit", the safe non-Claude default. Allowlist-based so malformed values
|
|
11337
|
+
// (0, "", "yes", {}, …) don't leak Claude aliases the runtime can't resolve (#1569).
|
|
11338
|
+
const existing = defaults.resolve_model_ids;
|
|
11339
|
+
const shouldDefaultToOmit = existing !== true && existing !== 'omit';
|
|
11340
|
+
if (shouldDefaultToOmit) {
|
|
11322
11341
|
defaults.resolve_model_ids = 'omit';
|
|
11323
11342
|
fs.writeFileSync(defaultsPath, JSON.stringify(defaults, null, 2) + '\n');
|
|
11324
11343
|
console.log(` ${green}✓${reset} Set resolve_model_ids: "omit" in ~/.gsd/defaults.json`);
|
|
@@ -11774,6 +11793,189 @@ function homePathCoveredByRc(globalBin, homeDir, rcFileNames) {
|
|
|
11774
11793
|
return false;
|
|
11775
11794
|
}
|
|
11776
11795
|
|
|
11796
|
+
/**
|
|
11797
|
+
* Decode fish's universal-variable value escaping (the inverse of fish's
|
|
11798
|
+
* `full_escape`). fish serializes every non-`[A-Za-z0-9/_]` byte in
|
|
11799
|
+
* `fish_variables` — e.g. space -> `\x20`, hyphen -> `\x2d`, dot -> `\x2e` —
|
|
11800
|
+
* and joins list elements with the literal 4-char token `\x1e` (NOT a raw
|
|
11801
|
+
* 0x1e byte). Callers split on `\x1e` first, then decode each element here.
|
|
11802
|
+
*
|
|
11803
|
+
* Pure and total: any unrecognised `\`-sequence is passed through verbatim,
|
|
11804
|
+
* so `decode(fishEscape(p)) === p` holds for every path string. Exported for
|
|
11805
|
+
* a fast-check round-trip property test (#323).
|
|
11806
|
+
*
|
|
11807
|
+
* @param {string} s A single (already `\x1e`-split) escaped value.
|
|
11808
|
+
* @returns {string} The decoded literal.
|
|
11809
|
+
*/
|
|
11810
|
+
function decodeFishUniversalValue(s) {
|
|
11811
|
+
let out = '';
|
|
11812
|
+
for (let i = 0; i < s.length; i++) {
|
|
11813
|
+
const c = s[i];
|
|
11814
|
+
if (c !== '\\') { out += c; continue; }
|
|
11815
|
+
const n = s[i + 1];
|
|
11816
|
+
if (n === 'n') { out += '\n'; i += 1; }
|
|
11817
|
+
else if (n === 'r') { out += '\r'; i += 1; }
|
|
11818
|
+
else if (n === 't') { out += '\t'; i += 1; }
|
|
11819
|
+
else if (n === '\\') { out += '\\'; i += 1; }
|
|
11820
|
+
else if (n === 'x' || n === 'X') {
|
|
11821
|
+
const hex = s.slice(i + 2, i + 4);
|
|
11822
|
+
if (/^[0-9a-fA-F]{2}$/.test(hex)) { out += String.fromCharCode(parseInt(hex, 16)); i += 3; }
|
|
11823
|
+
else { out += c; }
|
|
11824
|
+
} else if (n === 'u') {
|
|
11825
|
+
const hex = s.slice(i + 2, i + 6);
|
|
11826
|
+
if (/^[0-9a-fA-F]{4}$/.test(hex)) { out += String.fromCharCode(parseInt(hex, 16)); i += 5; }
|
|
11827
|
+
else { out += c; }
|
|
11828
|
+
} else if (n === 'U') {
|
|
11829
|
+
const hex = s.slice(i + 2, i + 10);
|
|
11830
|
+
if (/^[0-9a-fA-F]{8}$/.test(hex)) { out += String.fromCodePoint(parseInt(hex, 16)); i += 9; }
|
|
11831
|
+
else { out += c; }
|
|
11832
|
+
} else { out += c; }
|
|
11833
|
+
}
|
|
11834
|
+
return out;
|
|
11835
|
+
}
|
|
11836
|
+
|
|
11837
|
+
/**
|
|
11838
|
+
* Check whether fish's configuration already places `globalBin` on PATH (#323).
|
|
11839
|
+
*
|
|
11840
|
+
* fish does not use the sh-style `export PATH=` rc files that
|
|
11841
|
+
* `homePathCoveredByRc()` parses, so a fish user whose `fish_user_paths`
|
|
11842
|
+
* already covers the global bin would otherwise see a false-positive
|
|
11843
|
+
* "not on your PATH" warning on every install. Two detection routes,
|
|
11844
|
+
* mirroring how `fish_add_path` actually persists:
|
|
11845
|
+
*
|
|
11846
|
+
* 1. The universal-variable store `fish_variables` — a
|
|
11847
|
+
* `SETUVAR fish_user_paths:<a>\x1e<b>…` line whose `\x1e`-separated
|
|
11848
|
+
* entries are absolute paths (fish does not HOME-expand them here).
|
|
11849
|
+
* 2. `config.fish` — explicit `fish_add_path …`, `set -gx PATH …`, or
|
|
11850
|
+
* `set -Ux fish_user_paths …` lines that name the directory after
|
|
11851
|
+
* HOME expansion.
|
|
11852
|
+
*
|
|
11853
|
+
* Best-effort and side-effect-free: any unreadable / missing file is ignored
|
|
11854
|
+
* (no fish subprocess is spawned). Honours `$XDG_CONFIG_HOME` and always also
|
|
11855
|
+
* checks `~/.config/fish`. Pass `fishConfigDir` to override the lookup
|
|
11856
|
+
* directory (tests).
|
|
11857
|
+
*
|
|
11858
|
+
* @param {string} globalBin Absolute path to npm's global bin directory.
|
|
11859
|
+
* @param {string} homeDir Absolute path used to substitute HOME / ~.
|
|
11860
|
+
* @param {string} [fishConfigDir] Override the fish config directory.
|
|
11861
|
+
* @returns {boolean} true iff fish config adds globalBin to PATH.
|
|
11862
|
+
*/
|
|
11863
|
+
function homePathCoveredByFishConfig(globalBin, homeDir, fishConfigDir) {
|
|
11864
|
+
if (!globalBin || !homeDir) return false;
|
|
11865
|
+
const path = require('path');
|
|
11866
|
+
const fs = require('fs');
|
|
11867
|
+
|
|
11868
|
+
const normalise = (p) => {
|
|
11869
|
+
if (!p) return '';
|
|
11870
|
+
let n = p.replace(/[\\/]+$/g, '');
|
|
11871
|
+
if (n === '') n = p.startsWith('/') ? '/' : p;
|
|
11872
|
+
return n;
|
|
11873
|
+
};
|
|
11874
|
+
|
|
11875
|
+
const targetAbs = normalise(path.resolve(globalBin));
|
|
11876
|
+
const homeAbs = path.resolve(homeDir);
|
|
11877
|
+
|
|
11878
|
+
const baseDirs = [];
|
|
11879
|
+
if (fishConfigDir) {
|
|
11880
|
+
baseDirs.push(fishConfigDir);
|
|
11881
|
+
} else {
|
|
11882
|
+
if (process.env.XDG_CONFIG_HOME) {
|
|
11883
|
+
baseDirs.push(path.join(process.env.XDG_CONFIG_HOME, 'fish'));
|
|
11884
|
+
}
|
|
11885
|
+
baseDirs.push(path.join(homeAbs, '.config', 'fish'));
|
|
11886
|
+
}
|
|
11887
|
+
|
|
11888
|
+
const expandHome = (segment) => {
|
|
11889
|
+
let s = segment;
|
|
11890
|
+
s = s.replace(/\$\{HOME\}/g, homeAbs).replace(/\$HOME/g, homeAbs);
|
|
11891
|
+
if (s.startsWith('~/') || s === '~') {
|
|
11892
|
+
s = s === '~' ? homeAbs : path.join(homeAbs, s.slice(2));
|
|
11893
|
+
}
|
|
11894
|
+
return s;
|
|
11895
|
+
};
|
|
11896
|
+
|
|
11897
|
+
// Compare an already-resolved absolute literal (a decoded fish_user_paths
|
|
11898
|
+
// entry — fish stores these resolved, never as `$VAR`/`~`). A literal `$`
|
|
11899
|
+
// here is part of the directory name, so it must NOT be treated as an
|
|
11900
|
+
// unexpanded variable.
|
|
11901
|
+
const matchesLiteral = (segment) => {
|
|
11902
|
+
if (!segment || !path.isAbsolute(segment)) return false;
|
|
11903
|
+
try {
|
|
11904
|
+
return normalise(path.resolve(segment)) === targetAbs;
|
|
11905
|
+
} catch {
|
|
11906
|
+
return false;
|
|
11907
|
+
}
|
|
11908
|
+
};
|
|
11909
|
+
|
|
11910
|
+
// Compare a config.fish shell token: strip surrounding quotes, expand the
|
|
11911
|
+
// common HOME forms, and skip anything still holding a `$` (an unexpanded
|
|
11912
|
+
// variable such as `$PATH` / `$fish_user_paths`) or still relative.
|
|
11913
|
+
const matchesTarget = (rawSegment) => {
|
|
11914
|
+
if (!rawSegment) return false;
|
|
11915
|
+
let seg = rawSegment.trim();
|
|
11916
|
+
if ((seg.startsWith('"') && seg.endsWith('"')) ||
|
|
11917
|
+
(seg.startsWith("'") && seg.endsWith("'"))) {
|
|
11918
|
+
seg = seg.slice(1, -1);
|
|
11919
|
+
}
|
|
11920
|
+
const expanded = expandHome(seg);
|
|
11921
|
+
if (expanded.includes('$')) return false;
|
|
11922
|
+
return matchesLiteral(expanded);
|
|
11923
|
+
};
|
|
11924
|
+
|
|
11925
|
+
const readLines = (filePath) => {
|
|
11926
|
+
try {
|
|
11927
|
+
return fs.readFileSync(filePath, 'utf8').split(/\r?\n/);
|
|
11928
|
+
} catch {
|
|
11929
|
+
return null;
|
|
11930
|
+
}
|
|
11931
|
+
};
|
|
11932
|
+
|
|
11933
|
+
for (const baseDir of baseDirs) {
|
|
11934
|
+
// Route 1: universal variable store.
|
|
11935
|
+
const uvarLines = readLines(path.join(baseDir, 'fish_variables'));
|
|
11936
|
+
if (uvarLines) {
|
|
11937
|
+
for (const rawLine of uvarLines) {
|
|
11938
|
+
const m = /^SETUVAR(?:\s+--\S+)*\s+fish_user_paths:(.*)$/.exec(rawLine);
|
|
11939
|
+
if (!m) continue;
|
|
11940
|
+
// Elements are joined by the literal `\x1e` token; decode each. The
|
|
11941
|
+
// decoded entry is an absolute literal — compare it directly.
|
|
11942
|
+
for (const entry of m[1].split('\\x1e')) {
|
|
11943
|
+
if (matchesLiteral(decodeFishUniversalValue(entry))) return true;
|
|
11944
|
+
}
|
|
11945
|
+
}
|
|
11946
|
+
}
|
|
11947
|
+
|
|
11948
|
+
// Route 2: config.fish explicit PATH mutations.
|
|
11949
|
+
const configLines = readLines(path.join(baseDir, 'config.fish'));
|
|
11950
|
+
if (configLines) {
|
|
11951
|
+
for (const rawLine of configLines) {
|
|
11952
|
+
const line = rawLine.replace(/^\s+/, '');
|
|
11953
|
+
if (line.startsWith('#')) continue;
|
|
11954
|
+
|
|
11955
|
+
let rest = null;
|
|
11956
|
+
let m;
|
|
11957
|
+
if ((m = /^fish_add_path\s+(.+)$/.exec(line))) {
|
|
11958
|
+
rest = m[1];
|
|
11959
|
+
} else if ((m = /^set\s+(?:-\S+\s+)*PATH\s+(.+)$/.exec(line))) {
|
|
11960
|
+
rest = m[1];
|
|
11961
|
+
} else if ((m = /^set\s+(?:-\S+\s+)*fish_user_paths\s+(.+)$/.exec(line))) {
|
|
11962
|
+
rest = m[1];
|
|
11963
|
+
}
|
|
11964
|
+
if (rest === null) continue;
|
|
11965
|
+
|
|
11966
|
+
// Tokens are whitespace-separated; flag tokens (`-g`, `--path`) and
|
|
11967
|
+
// variable references are skipped by matchesTarget / the `-` guard.
|
|
11968
|
+
for (const tok of rest.split(/\s+/)) {
|
|
11969
|
+
if (!tok || tok.startsWith('-')) continue;
|
|
11970
|
+
if (matchesTarget(tok)) return true;
|
|
11971
|
+
}
|
|
11972
|
+
}
|
|
11973
|
+
}
|
|
11974
|
+
}
|
|
11975
|
+
|
|
11976
|
+
return false;
|
|
11977
|
+
}
|
|
11978
|
+
|
|
11777
11979
|
/**
|
|
11778
11980
|
* Emit a PATH-export suggestion if globalBin is not already on PATH AND
|
|
11779
11981
|
* the user's shell rc files do not already cover it via a HOME-relative
|
|
@@ -11816,6 +12018,16 @@ function maybeSuggestPathExport(globalBin, homeDir) {
|
|
|
11816
12018
|
return;
|
|
11817
12019
|
}
|
|
11818
12020
|
|
|
12021
|
+
// Same idea for fish users: fish_user_paths / config.fish already covers the
|
|
12022
|
+
// dir, the current session just predates it. fish has no sh-style rc file so
|
|
12023
|
+
// homePathCoveredByRc never sees it — check the fish config explicitly (#323).
|
|
12024
|
+
if (homePathCoveredByFishConfig(globalBin, homeDir)) {
|
|
12025
|
+
console.log('');
|
|
12026
|
+
console.log(` ${yellow}⚠${reset} ${bold}${globalBin}${reset}'s directory is already on your PATH via fish's universal variables — open a new fish session (or run ${cyan}exec fish${reset}).`);
|
|
12027
|
+
console.log('');
|
|
12028
|
+
return;
|
|
12029
|
+
}
|
|
12030
|
+
|
|
11819
12031
|
console.log('');
|
|
11820
12032
|
console.log(` ${yellow}⚠${reset} ${bold}${globalBin}${reset} is not on your PATH.`);
|
|
11821
12033
|
console.log(` Add it with one of:`);
|
|
@@ -12153,6 +12365,8 @@ module.exports = {
|
|
|
12153
12365
|
USER_OWNED_ARTIFACTS,
|
|
12154
12366
|
finishInstall,
|
|
12155
12367
|
homePathCoveredByRc,
|
|
12368
|
+
homePathCoveredByFishConfig,
|
|
12369
|
+
decodeFishUniversalValue,
|
|
12156
12370
|
maybeSuggestPathExport,
|
|
12157
12371
|
runtimeMap,
|
|
12158
12372
|
allRuntimes,
|
package/gemini-extension.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gsd-core",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"description": "GSD Core — a meta-prompting, context engineering, and spec-driven development system for AI coding agents. Loads gsd's operating context into every Gemini CLI session.",
|
|
5
5
|
"contextFileName": "GEMINI.md"
|
|
6
6
|
}
|
|
@@ -222,6 +222,8 @@ const learnings = require('./lib/learnings.cjs');
|
|
|
222
222
|
const gapChecker = require('./lib/gap-checker.cjs');
|
|
223
223
|
const { routeStateCommand } = require('./lib/state-command-router.cjs');
|
|
224
224
|
const { routeVerifyCommand } = require('./lib/verify-command-router.cjs');
|
|
225
|
+
const { routeEvalCommand } = require('./lib/eval-command-router.cjs');
|
|
226
|
+
const evalMod = require('./lib/eval.cjs');
|
|
225
227
|
const { routeVerificationCommand } = require('./lib/verification-command-router.cjs');
|
|
226
228
|
const verification = require('./lib/verification.cjs');
|
|
227
229
|
const { routeInitCommand } = require('./lib/init-command-router.cjs');
|
|
@@ -640,7 +642,7 @@ async function main() {
|
|
|
640
642
|
'generate-dev-preferences, generate-slug, graphify, history-digest, init, intel, ' +
|
|
641
643
|
'capability, classify-confidence, git, learnings, list-seeds, list-todos, loop, milestone, package-legitimacy, phase, phase-plan-index, phases, profile-questionnaire, ' +
|
|
642
644
|
'profile-sample, progress, project-instruction-file, prompt-budget, requirements, research-plan, research-store, resolve-granularity, resolve-model, roadmap, scaffold, state, ' +
|
|
643
|
-
'task, template, user-story, validate, verify, verify-path-exists, verify-summary, workstream, worktree\n\n' +
|
|
645
|
+
'task, template, user-story, validate, verify, verify-path-exists, verify-summary, eval, workstream, worktree\n\n' +
|
|
644
646
|
'Global flags:\n' +
|
|
645
647
|
' --raw Emit raw output without post-processing\n' +
|
|
646
648
|
' --pick <field> Extract a single field from JSON output (dot/bracket notation)\n' +
|
|
@@ -694,6 +696,9 @@ async function main() {
|
|
|
694
696
|
// .planning/ access needed, and resolving project root would break workflow
|
|
695
697
|
// invocations that run before .planning/ exists (new-project Step 1).
|
|
696
698
|
'project-instruction-file',
|
|
699
|
+
// #1579: eval.score is pure arithmetic (covered/total + infra weights); it
|
|
700
|
+
// needs no .planning/ access, so skip the findProjectRoot traversal.
|
|
701
|
+
'eval',
|
|
697
702
|
]);
|
|
698
703
|
if (!SKIP_ROOT_RESOLUTION.has(command)) {
|
|
699
704
|
cwd = findProjectRoot(cwd);
|
|
@@ -1062,6 +1067,11 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1062
1067
|
break;
|
|
1063
1068
|
}
|
|
1064
1069
|
|
|
1070
|
+
case 'eval': {
|
|
1071
|
+
routeEvalCommand({ evalMod, args, cwd, raw, error });
|
|
1072
|
+
break;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1065
1075
|
// ─── Verification Status ───────────────────────────────────────────────
|
|
1066
1076
|
//
|
|
1067
1077
|
// verification status <phaseDir>
|