Merge branch 'next' into docs/612-bracket-phase-id-convention
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Security
|
||||
pr: 2113
|
||||
---
|
||||
**`gate="blocking-human"` checkpoints are no longer auto-approved by the execute-phase orchestrator** — the package-legitimacy gate (#2827) spans two layers: `gsd-executor` refuses to auto-approve a `gate="blocking-human"` checkpoint and escalates it via `checkpoint_return_format` so a human can vet the package, and `execute-phase`'s `checkpoint_handling` step decides what happens next. That step dispatched purely on checkpoint *type* and never read `gate`, so under `--auto` / `--chain` it immediately auto-approved the very checkpoint the executor had just refused to auto-approve (`human-verify → {user_response} = "approved"`). The slopsquatting defence was therefore inert in exactly the unattended mode where nobody is watching: an `[ASSUMED]`/`[SUS]` package reached install with no human ever seeing the verification prompt. `checkpoint_handling` now carves out `gate="blocking-human"` (and the package-legitimacy `what-built` markers) ahead of every auto-mode branch, routing those checkpoints to the standard present-to-user flow regardless of type. `references/checkpoints.md` documents the `gate` attribute and its two values for the first time — previously `blocking-human` appeared nowhere outside `agents/gsd-executor.md`, so no planner had a documented way to author a checkpoint that auto-mode could not bypass. The existing regression test asserted the executor half only; it now asserts the orchestrator half too, which is why it stayed green while the gate was open. (#2107)
|
||||
5
.changeset/agile-pandas-dance.md
Normal file
5
.changeset/agile-pandas-dance.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 2233
|
||||
---
|
||||
**Custom STATE.md frontmatter keys are no longer dropped on every mutating verb** — syncStateFrontmatter rebuilt the frontmatter from a fixed schema, silently dropping any custom key. It now carries forward existing keys the schema does not own. (#2202)
|
||||
5
.changeset/fierce-pumas-gather.md
Normal file
5
.changeset/fierce-pumas-gather.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 2184
|
||||
---
|
||||
**The Antigravity reviewer in `/gsd-review` no longer reviews blind** — `agy -p` never granted the agent the repo under review, so it frequently anchored on its own scratch directory and returned plan-text-only verdicts counted at full consensus weight. The reviewer is now granted the repo (capability-probed `--add-dir`) and anchored to the absolute repo root; a review that still runs without repo access is stamped `[reviewed-without-repo-access]` and down-weighted in the Consensus Summary. The cursor-agent prompt gains the same absolute-root anchor. (#2176)
|
||||
5
.changeset/jolly-jays-march.md
Normal file
5
.changeset/jolly-jays-march.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Added
|
||||
pr: 2174
|
||||
---
|
||||
**Opt-in absolute token count on the statusline context meter** — new `statusline.show_context_tokens` config (default `false`). When enabled, the meter shows the absolute context total after the percentage, e.g. "████░░░░░░ 46% (156k)", summing input, cache-creation, cache-read, and output tokens from the hook payload (a broader basis than the meter's percentage, which is derived from `used_percentage` and excludes output tokens — the two figures can diverge slightly). Default meter output is unchanged. (#2161)
|
||||
5
.changeset/noble-wasps-greet.md
Normal file
5
.changeset/noble-wasps-greet.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 2234
|
||||
---
|
||||
**`phase complete` no longer false-reports REQ-IDs as missing when the traceability table leads with a status column** — the parser required the REQ-ID in the first column, so a table shaped `| ☐ | REQ-01 | …` matched zero rows and every body REQ-ID was reported missing. It now matches REQ-IDs in any column. (#2203)
|
||||
5
.changeset/tidy-voles-glide.md
Normal file
5
.changeset/tidy-voles-glide.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 2235
|
||||
---
|
||||
**`commit_docs` no longer silently disables on CRLF `.gitignore` repos** — git check-ignore falsely reports a trailing-slash path (e.g. `.planning/`) as ignored when the .gitignore has CRLF line endings with blank lines. isGitIgnored now strips trailing slashes before querying, so the false positive cannot occur. (#2206)
|
||||
42
.out-of-scope/plan-md-execution-context-portability.md
Normal file
42
.out-of-scope/plan-md-execution-context-portability.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Clone-Portable `<execution_context>` in Committed PLAN.md
|
||||
|
||||
GSD does not make the `<execution_context>` block in committed `PLAN.md` files
|
||||
clone-portable — it does not rewrite the planner's install-relative
|
||||
`@…/gsd-core/…` references into repository-relative or install-neutral paths so
|
||||
that a committed plan reads identically across developers, machines, or runtimes.
|
||||
|
||||
## Why this is out of scope
|
||||
|
||||
PLAN.md is a **machine artifact**, not a human- or clone-facing document — the
|
||||
same principle that governs [plan-md-human-rendering.md](./plan-md-human-rendering.md)
|
||||
(from #2158). A committed PLAN.md is a per-run agent instruction set, produced by
|
||||
`gsd-planner` and consumed in place by `gsd-executor`, `gsd-plan-checker`, and
|
||||
`gsd-verifier`. There is no documented step in which a plan is read on another
|
||||
machine after `git clone` without a local GSD install.
|
||||
|
||||
Under that model, `<execution_context>`'s `@` references point at the reader's
|
||||
own local GSD install (Claude `~/.claude/gsd-core/…`, Cursor `.cursor/gsd-core/…`,
|
||||
or an absolute path for a `--local` install). They are install-relative by design,
|
||||
and the executor loads those workflows from its own installed copy — it never
|
||||
consumes the paths a *different* machine wrote into a committed plan.
|
||||
`/gsd-execute-phase` builds its own `<execution_context>` inline from the
|
||||
orchestrator's installed workflow (`workflows/execute-phase.md`), so the block a
|
||||
planner writes into a committed plan does not gate execution anywhere.
|
||||
|
||||
Making committed plans clone-portable would treat PLAN.md as a shared
|
||||
cross-developer document — the boundary #2158 declined to cross — for a block that
|
||||
no consumer reads across machines.
|
||||
|
||||
**Revisit if** GSD introduces a documented cross-developer / cross-machine contract
|
||||
for committed PLAN.md — a human- or teammate-facing use where plans are read after
|
||||
`git clone` without a local install — at which point `<execution_context>`
|
||||
portability becomes in scope.
|
||||
|
||||
## Prior requests
|
||||
|
||||
- #2238 — "Planner embeds machine-specific gsd-core paths in committed PLAN.md execution_context"
|
||||
|
||||
## Related
|
||||
|
||||
- `.out-of-scope/plan-md-human-rendering.md` — #2158, the governing "PLAN.md is a machine artifact" precedent.
|
||||
- `docs/reference/plan-md.md` — the `<execution_context>` reference, which describes the install-relative behaviour.
|
||||
@@ -315,7 +315,7 @@ For full automation-first patterns, server lifecycle, CLI handling:
|
||||
**Auto-mode checkpoint behavior** (when `AUTO_CFG` is `"true"`):
|
||||
|
||||
- **checkpoint:human-verify** → Auto-approve **except package-legitimacy checkpoints**. If checkpoint has `gate="blocking-human"` OR its purpose indicates package legitimacy verification (`what-built` mentions `Package verification required before install` or `Package install failed — human verification required`), do **not** auto-approve. STOP and return checkpoint_return_format for explicit human confirmation.
|
||||
- **checkpoint:decision** → Auto-select first option (planners front-load the recommended choice). Log `⚡ Auto-selected: [option name]`. Continue to next task.
|
||||
- **checkpoint:decision** → If checkpoint has `gate="blocking-human"`, do **not** auto-select — STOP and return checkpoint_return_format for an explicit human decision (a `blocking-human` decision exists because its default answer would be wrong to assume). Otherwise auto-select first option (planners front-load the recommended choice), log `⚡ Auto-selected: [option name]`, continue to next task.
|
||||
- **checkpoint:human-action** → STOP normally. Auth gates cannot be automated — return structured checkpoint message using checkpoint_return_format.
|
||||
|
||||
**Standard checkpoint behavior** (when `AUTO_CFG` is not `"true"`):
|
||||
@@ -340,6 +340,7 @@ When hitting checkpoint or auth gate, return this structure:
|
||||
## CHECKPOINT REACHED
|
||||
|
||||
**Type:** [human-verify | decision | human-action]
|
||||
**Gate:** [blocking | blocking-human] — copy the task's `gate` attribute verbatim so the orchestrator's carve-out sees it
|
||||
**Plan:** {phase}-{plan}
|
||||
**Progress:** {completed}/{total} tasks complete
|
||||
|
||||
|
||||
@@ -452,6 +452,7 @@ If `.planning/` is in `.gitignore`, `commit_docs` is automatically `false` regar
|
||||
| `hooks.workflow_guard` | boolean | `false` | Warn when file edits happen outside GSD workflow context (advises using `/gsd-quick` or `/gsd-fast`) |
|
||||
| `statusline.show_last_command` | boolean | `false` | Append `last: /<cmd>` suffix to the statusline showing the most recently invoked slash command. Opt-in; reads the active session transcript to extract the latest `<command-name>` tag (closes #2538) |
|
||||
| `statusline.context_position` | string | `"end"` | Position of the context-window meter. `"end"` (default) renders at line tail; `"front"` renders immediately after the model name so the meter stays visible in narrow terminals. Closes #2937 |
|
||||
| `statusline.show_context_tokens` | boolean | `false` | Append the absolute token count (e.g. `(156k)`) after the context meter's percentage. Sums input, cache-creation, cache-read, and output tokens from the hook payload — a broader basis than the meter's percentage (which excludes output tokens), so the two figures can diverge slightly. Opt-in; the meter is unchanged when the flag is absent |
|
||||
|
||||
The prompt injection guard hook (`gsd-prompt-guard.js`) is always active and cannot be disabled — it's a security feature, not a workflow toggle.
|
||||
|
||||
|
||||
@@ -118,7 +118,7 @@ Output: PostFeed and PostCard components wired to /api/feed.
|
||||
|
||||
### `<execution_context>`
|
||||
|
||||
Lists workflow files the executor reads before starting. Always includes the execute-plan workflow; adds the checkpoints reference when the plan contains checkpoint tasks:
|
||||
Lists the workflow files associated with executing the plan. Always includes the execute-plan workflow; adds the checkpoints reference when the plan contains checkpoint tasks:
|
||||
|
||||
```xml
|
||||
<execution_context>
|
||||
@@ -127,6 +127,8 @@ Lists workflow files the executor reads before starting. Always includes the exe
|
||||
</execution_context>
|
||||
```
|
||||
|
||||
These `@` paths point at the local GSD install, not at repository files. The prefix shown here (`~/.claude/gsd-core/…`) is the Claude global-install location; other runtimes and local installs resolve to their own install directory — for example `.cursor/gsd-core/…`, or an absolute project path for a `--local` install. Because the prefix is install-relative, this block is not clone-portable: a committed plan carries whichever prefix the authoring install had. Execution does not depend on it — `/gsd-execute-phase` loads the execute-plan workflow from its own installed copy — so the block records the execution context rather than resolvable repository references. Contrast `<context>` (below), whose repository-relative `@` paths resolve after a `git clone`.
|
||||
|
||||
### `<context>`
|
||||
|
||||
References source files the executor needs to read. Includes project-level planning docs and any source files whose patterns or types the plan must replicate. Prior plan `SUMMARY.md` files are included only when there is a genuine dependency (imported types, shared decision) — not reflexively:
|
||||
|
||||
@@ -67,9 +67,10 @@ function createRuntimeArtifactInstallPlan(args) {
|
||||
// to kind.stage() for agents kind entries with a converter (convertedAgentsKind).
|
||||
// NO _stampNonClaudeRuntimeDefaults — agents are NOT stamped in the inline loop.
|
||||
const os = _require('node:os');
|
||||
const { posixNormalize } = _require('./shell-command-projection.cjs');
|
||||
const homedirFn = homedir ?? (() => os.homedir());
|
||||
const resolvedTarget = path.resolve(layout.configDir).replace(/\\/g, '/');
|
||||
const homeDir = homedirFn().replace(/\\/g, '/');
|
||||
const resolvedTarget = posixNormalize(path.resolve(layout.configDir));
|
||||
const homeDir = posixNormalize(homedirFn());
|
||||
const isGlobal = scope === 'global';
|
||||
const isOpencode = layout.runtime === 'opencode';
|
||||
const isWindowsHost = (platform ?? process.platform) === 'win32';
|
||||
|
||||
@@ -71,6 +71,7 @@
|
||||
"workflow.context_coverage_gate",
|
||||
"statusline.show_last_command",
|
||||
"statusline.context_position",
|
||||
"statusline.show_context_tokens",
|
||||
"workflow.max_discuss_passes",
|
||||
"features.thinking_partner",
|
||||
"context",
|
||||
|
||||
@@ -9,6 +9,18 @@ Plans execute autonomously. Checkpoints formalize interaction points where human
|
||||
3. **User only does what requires human judgment** - Visual checks, UX evaluation, "does this feel right?"
|
||||
4. **Secrets come from user, automation comes from Claude** - Ask for API keys, then Claude uses them via CLI
|
||||
5. **Auto-mode bypasses verification/decision checkpoints** — When `workflow._auto_chain_active` or `workflow.auto_advance` is true in config: human-verify auto-approves, decision auto-selects first option, human-action still stops (auth gates cannot be automated)
|
||||
6. **`gate="blocking-human"` is never auto-approved** — a checkpoint carrying this gate stops for a human in *every* mode, including auto-mode, regardless of its type. Rule 5 does not apply to it.
|
||||
|
||||
**The `gate` attribute:**
|
||||
|
||||
| Value | Auto-mode behavior | Use for |
|
||||
|-------|--------------------|---------|
|
||||
| `gate="blocking"` | Bypassed per rule 5 (human-verify auto-approves, decision auto-selects) | The default. Post-hoc verification and implementation choices that are safe to take the recommended path on when unattended. |
|
||||
| `gate="blocking-human"` | **Never bypassed.** Stops for a human in auto-mode too. | Irreversible or trust-establishing steps a human must actually see: package-legitimacy verification before install, and any decision whose default answer would be wrong to assume. |
|
||||
|
||||
Reach for `gate="blocking-human"` whenever auto-approving the checkpoint would defeat its purpose. If the checkpoint exists because a human must *decide* something, `blocking` is the wrong gate — auto-mode will decide it for them.
|
||||
|
||||
The gate spans two layers, and both must honor it. `gsd-executor` refuses to auto-approve a `gate="blocking-human"` checkpoint and escalates it via `checkpoint_return_format` precisely so a human sees it; `execute-phase`'s `checkpoint_handling` step then decides what the user is actually shown. An orchestrator that dispatches on checkpoint *type* alone would auto-approve the very checkpoint the executor just refused to auto-approve, nullifying that refusal one layer up and letting an unattended `--auto` / `--chain` run install a package no human ever vetted.
|
||||
</overview>
|
||||
|
||||
<checkpoint_types>
|
||||
|
||||
@@ -1056,11 +1056,13 @@ AUTO_MODE=$(gsd_run query check auto-mode --pick active 2>/dev/null || echo "fal
|
||||
```
|
||||
|
||||
When executor returns a checkpoint AND `AUTO_MODE` is `true`:
|
||||
- **human-verify** → Auto-spawn continuation agent with `{user_response}` = `"approved"`. Log `⚡ Auto-approved checkpoint`.
|
||||
- **decision** → Auto-spawn continuation agent with `{user_response}` = first option from checkpoint details. Log `⚡ Auto-selected: [option]`.
|
||||
- **human-verify** → Auto-spawn continuation agent with `{user_response}` = `"approved"`. Log `⚡ Auto-approved checkpoint`. **Except `blocking-human`.**
|
||||
- **decision** → Auto-spawn continuation agent with `{user_response}` = first option from checkpoint details. Log `⚡ Auto-selected: [option]`. **Except `blocking-human`.**
|
||||
- **human-action** → Present to user (existing behavior below). Auth gates cannot be automated.
|
||||
|
||||
**Standard flow (not auto-mode, or human-action type):**
|
||||
**Carve-out — overrides all branches above.** If the returned `Gate:` is `blocking-human`, or its `<what-built>` mentions `Package verification required before install` or `Package install failed — human verification required`, never auto-approve or auto-select, regardless of type. Present to user (standard flow below). Log `⛔ blocking-human gate — auto-mode suspended`.
|
||||
|
||||
**Standard flow (not auto-mode, human-action, or blocking-human):**
|
||||
|
||||
1. Spawn agent for checkpoint plan
|
||||
2. Agent runs until checkpoint task or auth gate → returns structured state
|
||||
|
||||
@@ -366,7 +366,12 @@ fi
|
||||
# prompt as an ARGUMENT, not stdin. A full review prompt can exceed the OS argument limit, so
|
||||
# reference the prompt file by path rather than inlining it. Capture stderr so a failure is
|
||||
# diagnosable instead of a silent empty result.
|
||||
CURSOR_PROMPT_ARG="Read the file at /tmp/gsd-review-prompt-{phase}.md in full and carry out the review request it contains. Output only the resulting markdown review. Do not edit any files."
|
||||
# #2176: same absolute-root anchor as the Antigravity block — cursor-agent runs
|
||||
# in the repo cwd, but repo-relative references in the assembled prompt still
|
||||
# need an explicit root to resolve against. rev-parse (not bare pwd) so the
|
||||
# anchor is correct even when /gsd:review is invoked from a repo subdirectory.
|
||||
_CURSOR_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
||||
CURSOR_PROMPT_ARG="Read the file at /tmp/gsd-review-prompt-{phase}.md in full and carry out the review request it contains. The repository under review is at $_CURSOR_ROOT — resolve every relative file path in the review request against that absolute root. Output only the resulting markdown review. Do not edit any files."
|
||||
cursor-agent -p --mode ask --trust --output-format text "$CURSOR_PROMPT_ARG" 2>/tmp/gsd-review-cursor-{phase}.err > /tmp/gsd-review-cursor-{phase}.md
|
||||
if [ ! -s /tmp/gsd-review-cursor-{phase}.md ]; then
|
||||
echo "Cursor review failed or returned empty output. stderr:" > /tmp/gsd-review-cursor-{phase}.md
|
||||
@@ -458,7 +463,19 @@ if [ -n "$AGY_MODEL" ] && [ "$AGY_MODEL" != "null" ]; then
|
||||
else
|
||||
set --
|
||||
fi
|
||||
_AGY_PROMPT="Read the file at /tmp/gsd-review-prompt-{phase}.md in full and carry out the review request it contains. Output only the resulting markdown review. Do not edit any files."
|
||||
# #2176: grant the reviewer the repo under review. Without --add-dir, agy's
|
||||
# permission context never receives the cwd repo — the agent anchors on its own
|
||||
# ~/.gemini/antigravity-cli/scratch dir and reviews the plan text in isolation
|
||||
# (the exact failure the Review Instructions forbid). Capability-probed like the
|
||||
# Codex bypass flag so an older agy without --add-dir still runs; the prompt
|
||||
# anchor below keeps absolute-path reads possible on that fallback.
|
||||
if agy --help 2>/dev/null | grep -q -- '--add-dir'; then
|
||||
set -- "$@" --add-dir "$_AGY_WS"
|
||||
fi
|
||||
# #2176: anchor the prompt to the absolute repo root so repo-relative references
|
||||
# in the assembled review prompt resolve even on the no---add-dir fallback, and
|
||||
# require an explicit self-report if the reviewer still cannot read the repo.
|
||||
_AGY_PROMPT="Read the file at /tmp/gsd-review-prompt-{phase}.md in full and carry out the review request it contains. The repository under review is at $_AGY_WS — resolve every relative file path in the review request against that absolute root and verify claims against those files. If you cannot read files under $_AGY_WS, begin your output with the exact line REVIEWED-WITHOUT-REPO-ACCESS before the review. Output only the resulting markdown review. Do not edit any files."
|
||||
# Capability-probe an external wall-clock killer (GNU coreutils `timeout` or the
|
||||
# macOS Homebrew `gtimeout`). Stock macOS ships NEITHER — a bare `timeout …` would
|
||||
# fail with rc 127 ("command not found") and silently lose the reviewer, so fall
|
||||
@@ -525,6 +542,26 @@ if [ ! -s /tmp/gsd-review-antigravity-{phase}.md ]; then
|
||||
echo "If no agy run started, that is the pre-session-stall case: check whether a new ~/.gemini/antigravity-cli/brain/<conv-id>/ dir appeared within ~30s of launch."
|
||||
} > /tmp/gsd-review-antigravity-{phase}.md
|
||||
fi
|
||||
|
||||
# #2176: blind-review marker. Two tells that the reviewer ran without repo
|
||||
# access: the prompt's mandated REVIEWED-WITHOUT-REPO-ACCESS self-report in the
|
||||
# first lines of output, or the agent DECLARING the scratch dir as its
|
||||
# workspace. Both patterns are anchored — the self-report to the head of the
|
||||
# file, the scratch tell to a workspace-declaration phrasing — so a grounded
|
||||
# review that merely QUOTES these strings (e.g. reviewing this very file) is
|
||||
# never mis-stamped. Stamp a machine-readable marker so the Consensus Summary
|
||||
# down-weights the review instead of counting an ungrounded verdict at full
|
||||
# weight. (Temp file + mv, no in-place sed — BSD/GNU safe.)
|
||||
if [ -s /tmp/gsd-review-antigravity-{phase}.md ] && \
|
||||
{ head -5 /tmp/gsd-review-antigravity-{phase}.md | grep -q 'REVIEWED-WITHOUT-REPO-ACCESS' || \
|
||||
grep -qiE '(workspace|working) (directory|dir).{0,40}antigravity-cli/scratch' /tmp/gsd-review-antigravity-{phase}.md; }; then
|
||||
{
|
||||
echo "> [reviewed-without-repo-access] This reviewer ran without visibility into the repo under review — down-weight its verdict in the Consensus Summary."
|
||||
echo ""
|
||||
cat /tmp/gsd-review-antigravity-{phase}.md
|
||||
} > /tmp/gsd-review-antigravity-{phase}.md.tmp && \
|
||||
mv /tmp/gsd-review-antigravity-{phase}.md.tmp /tmp/gsd-review-antigravity-{phase}.md
|
||||
fi
|
||||
```
|
||||
|
||||
**Ollama (local, OpenAI-compatible):**
|
||||
@@ -837,7 +874,7 @@ trimmed_reviewers: # only present if at least one reviewer was trimmed
|
||||
|
||||
## Consensus Summary
|
||||
|
||||
{synthesize common concerns across all reviewers. CodeRabbit is a diff-only reviewer (it never received the source-grounding prompt), so do not weight its verdict as a grounded plan review — fold in its diff findings, but base plan-level consensus on the prompt-fed reviewers.}
|
||||
{synthesize common concerns across all reviewers. CodeRabbit is a diff-only reviewer (it never received the source-grounding prompt), so do not weight its verdict as a grounded plan review — fold in its diff findings, but base plan-level consensus on the prompt-fed reviewers. A reviewer output carrying the `[reviewed-without-repo-access]` marker (or beginning with `REVIEWED-WITHOUT-REPO-ACCESS`) ran without repo access (#2176) — treat it the same way: note its concerns, but do not count its verdict at full consensus weight.}
|
||||
|
||||
### Agreed Strengths
|
||||
{strengths mentioned by 2+ reviewers}
|
||||
|
||||
@@ -286,6 +286,36 @@ function formatGsdState(s) {
|
||||
return parts.join(' · ');
|
||||
}
|
||||
|
||||
// --- Context token count (opt-in) ---------------------------------------------
|
||||
|
||||
/**
|
||||
* Format a token count compactly: 156342 → '156k', 1234567 → '1.2M'.
|
||||
*/
|
||||
function formatTokens(tokens) {
|
||||
// Promote to the M branch when k-rounding would reach 1000 (999,500-999,999
|
||||
// must render "1.0M", never "1000k").
|
||||
if (tokens >= 1000000 || Math.round(tokens / 1000) >= 1000) {
|
||||
return (tokens / 1000000).toFixed(1) + 'M';
|
||||
}
|
||||
if (tokens >= 1000) return Math.round(tokens / 1000) + 'k';
|
||||
return String(tokens);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pure function: build the token-count suffix for the context meter from the
|
||||
* hook input's context_window.current_usage block. Sums input, cache-creation,
|
||||
* cache-read, and output tokens (the same total Claude Code's /context shows).
|
||||
* Returns ' (156k)' or '' when usage is absent/empty.
|
||||
*/
|
||||
function contextTokenSuffix(currentUsage) {
|
||||
if (!currentUsage || typeof currentUsage !== 'object') return '';
|
||||
const total = (Number(currentUsage.input_tokens) || 0) +
|
||||
(Number(currentUsage.cache_creation_input_tokens) || 0) +
|
||||
(Number(currentUsage.cache_read_input_tokens) || 0) +
|
||||
(Number(currentUsage.output_tokens) || 0);
|
||||
return total > 0 ? ` (${formatTokens(total)})` : '';
|
||||
}
|
||||
|
||||
// --- stdin ------------------------------------------------------------------
|
||||
|
||||
function runStatusline() {
|
||||
@@ -304,6 +334,11 @@ function runStatusline() {
|
||||
const session = data.session_id || '';
|
||||
const remaining = data.context_window?.remaining_percentage;
|
||||
|
||||
// Read .planning config once — used by the context meter (token suffix)
|
||||
// and the last-command/position block below. Fail-soft to {}.
|
||||
let cfg = {};
|
||||
try { cfg = readGsdConfig(dir); } catch (e) {}
|
||||
|
||||
// Context window display (shows USED percentage scaled to usable context)
|
||||
// Claude Code reserves a buffer for autocompact. By default this is ~16.5%
|
||||
// of the total window, but users can override it via CLAUDE_CODE_AUTO_COMPACT_WINDOW
|
||||
@@ -349,15 +384,21 @@ function runStatusline() {
|
||||
const filled = Math.floor(used / 10);
|
||||
const bar = '█'.repeat(filled) + '░'.repeat(10 - filled);
|
||||
|
||||
// Opt-in absolute token count after the percentage (statusline.show_context_tokens)
|
||||
let tokenSuffix = '';
|
||||
if (getConfigValue(cfg, 'statusline.show_context_tokens') === true) {
|
||||
tokenSuffix = contextTokenSuffix(data.context_window?.current_usage);
|
||||
}
|
||||
|
||||
// Color based on usable context thresholds
|
||||
if (used < 50) {
|
||||
ctx = ` \x1b[32m${bar} ${used}%\x1b[0m`;
|
||||
ctx = ` \x1b[32m${bar} ${used}%${tokenSuffix}\x1b[0m`;
|
||||
} else if (used < 65) {
|
||||
ctx = ` \x1b[33m${bar} ${used}%\x1b[0m`;
|
||||
ctx = ` \x1b[33m${bar} ${used}%${tokenSuffix}\x1b[0m`;
|
||||
} else if (used < 80) {
|
||||
ctx = ` \x1b[38;5;208m${bar} ${used}%\x1b[0m`;
|
||||
ctx = ` \x1b[38;5;208m${bar} ${used}%${tokenSuffix}\x1b[0m`;
|
||||
} else {
|
||||
ctx = ` \x1b[5;31m💀 ${bar} ${used}%\x1b[0m`;
|
||||
ctx = ` \x1b[5;31m💀 ${bar} ${used}%${tokenSuffix}\x1b[0m`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,7 +463,6 @@ function runStatusline() {
|
||||
let lastCmdSuffix = '';
|
||||
let position = 'end';
|
||||
try {
|
||||
const cfg = readGsdConfig(dir);
|
||||
if (getConfigValue(cfg, 'statusline.show_last_command') === true) {
|
||||
const transcriptPath = data.transcript_path;
|
||||
const lastCmd = readLastSlashCommand(transcriptPath);
|
||||
@@ -531,6 +571,8 @@ module.exports = {
|
||||
composeStatusline,
|
||||
isInstalledAheadOfLatest,
|
||||
evaluateUpdateCache,
|
||||
formatTokens,
|
||||
contextTokenSuffix,
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -38,7 +38,7 @@ const { evaluatePredicate } = gatePredicateEval;
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
import apiCoverageMod = require('./api-coverage.cjs');
|
||||
const { detectApiIntegration, validateCoverageMatrix } = apiCoverageMod;
|
||||
import { execTool } from './shell-command-projection.cjs';
|
||||
import { execTool, posixNormalize } from './shell-command-projection.cjs';
|
||||
|
||||
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -1035,7 +1035,7 @@ function cmdApiCoverageVerifyPre(projectDir: string, args: string[], raw: boolea
|
||||
// .planning/phases/ (or a milestone archive). The raw arg is never used as a
|
||||
// path, so `..`, absolute paths, and arbitrary directories cannot reach a
|
||||
// file read. Mirrors cmdVerifySchemaDrift's token-match approach.
|
||||
let token = phaseArg.replace(/\\/g, '/').split('/').filter(Boolean).pop() || '';
|
||||
let token = posixNormalize(phaseArg).split('/').filter(Boolean).pop() || '';
|
||||
// A token like ".." or "." carries no phase identity → unresolvable.
|
||||
if (token === '.' || token === '..') token = '';
|
||||
|
||||
|
||||
@@ -175,10 +175,14 @@ interface ParsedConfig {
|
||||
const _gitIgnoredCache = new Map<string, boolean>();
|
||||
|
||||
function isGitIgnored(cwd: string, targetPath: string): boolean {
|
||||
const key = cwd + '::' + targetPath;
|
||||
// #2206: strip trailing slashes — `git check-ignore` has a quirk where a
|
||||
// CRLF .gitignore with blank lines falsely reports a trailing-slash path
|
||||
// (e.g. `.planning/`) as ignored. Normalizing here protects every call site.
|
||||
const normalized = targetPath.replace(/\/+$/, '');
|
||||
const key = cwd + '::' + normalized;
|
||||
if (_gitIgnoredCache.has(key)) return _gitIgnoredCache.get(key)!;
|
||||
// --no-index checks .gitignore rules regardless of whether the file is tracked.
|
||||
const result = execGit(['check-ignore', '-q', '--no-index', '--', targetPath], { cwd });
|
||||
const result = execGit(['check-ignore', '-q', '--no-index', '--', normalized], { cwd });
|
||||
const ignored = result.exitCode === 0;
|
||||
_gitIgnoredCache.set(key, ignored);
|
||||
return ignored;
|
||||
|
||||
@@ -763,6 +763,13 @@ function cmdConfigSet(cwd: string, keyPath: string | undefined, value: string |
|
||||
const VALID_CONTEXT_POSITIONS = ['front', 'end'];
|
||||
if (kp === 'statusline.context_position') assertEnumValue(parsedValue, val, VALID_CONTEXT_POSITIONS, 'statusline.context_position');
|
||||
|
||||
// statusline.show_context_tokens — boolean only
|
||||
if (kp === 'statusline.show_context_tokens') {
|
||||
if (typeof parsedValue !== 'boolean') {
|
||||
error(`Invalid statusline.show_context_tokens '${val}'. Must be a boolean (true or false).`);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallow scope + profile enum validation (#3424)
|
||||
const VALID_FALLOW_SCOPES = ['phase', 'repo'];
|
||||
if (kp === 'code_quality.fallow.scope') assertEnumValue(parsedValue, val, VALID_FALLOW_SCOPES, 'code_quality.fallow.scope');
|
||||
|
||||
@@ -24,12 +24,18 @@ const { comparePhaseNum } = phaseIdModule;
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
import planningWorkspace = require('./planning-workspace.cjs');
|
||||
const { findContextMdIn } = planningWorkspace;
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
import shellCommandProjection = require('./shell-command-projection.cjs');
|
||||
|
||||
// ─── Path helpers ────────────────────────────────────────────────────────────
|
||||
|
||||
/** Normalize a relative path to always use forward slashes (cross-platform). */
|
||||
/**
|
||||
* Normalize a relative path to always use forward slashes (cross-platform).
|
||||
* Delegates to the single separator seam in shell-command-projection so there is
|
||||
* exactly one implementation of native→POSIX conversion across the codebase.
|
||||
*/
|
||||
function toPosixPath(p: string): string {
|
||||
return p.split(path.sep).join('/');
|
||||
return shellCommandProjection.toPosixPath(p);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
'use strict';
|
||||
|
||||
import fs from 'node:fs';
|
||||
import { platformWriteSync } from './shell-command-projection.cjs';
|
||||
import { platformWriteSync, posixNormalize } from './shell-command-projection.cjs';
|
||||
import { formatGsdSlash } from './runtime-slash.cjs';
|
||||
|
||||
// ─── Constants ───────────────────────────────────────────────────────────────
|
||||
@@ -79,7 +79,7 @@ type DriftCategory = 'barrel' | 'migration' | 'route' | 'new_dir';
|
||||
*/
|
||||
function classifyFile(file: unknown): DriftCategory | null {
|
||||
if (typeof file !== 'string' || !file) return null;
|
||||
const norm = file.replace(/\\/g, '/');
|
||||
const norm = posixNormalize(file);
|
||||
if (MIGRATION_RES.some((r) => r.test(norm))) return 'migration';
|
||||
if (ROUTE_RES.some((r) => r.test(norm))) return 'route';
|
||||
if (BARREL_RE.test(norm)) return 'barrel';
|
||||
@@ -95,7 +95,7 @@ function classifyFile(file: unknown): DriftCategory | null {
|
||||
* check `structureMd.includes('src/lib')` holds.
|
||||
*/
|
||||
function isPathMapped(file: string, structureMd: string): boolean {
|
||||
const norm = file.replace(/\\/g, '/');
|
||||
const norm = posixNormalize(file);
|
||||
const parts = norm.split('/');
|
||||
// Check prefixes from longest to shortest; any hit means "mapped".
|
||||
for (let i = parts.length - 1; i >= 1; i--) {
|
||||
@@ -191,7 +191,7 @@ function detectDrift(input: unknown): DetectDriftResult | SkippedResult {
|
||||
const seen = new Map<string, string>();
|
||||
|
||||
for (const rawFile of added) {
|
||||
const file = rawFile.replace(/\\/g, '/');
|
||||
const file = posixNormalize(rawFile);
|
||||
const specific = classifyFile(file);
|
||||
let category: string | null = specific;
|
||||
if (!category) {
|
||||
@@ -318,7 +318,7 @@ function chooseAffectedPaths(paths: string[]): string[] {
|
||||
const out = new Set<string>();
|
||||
for (const raw of paths || []) {
|
||||
if (typeof raw !== 'string' || !raw) continue;
|
||||
const file = raw.replace(/\\/g, '/');
|
||||
const file = posixNormalize(raw);
|
||||
const parts = file.split('/');
|
||||
if (parts.length === 0) continue;
|
||||
const top = parts[0];
|
||||
|
||||
12
src/init.cts
12
src/init.cts
@@ -9,7 +9,7 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import os from 'node:os';
|
||||
import { execGit, platformWriteSync, platformReadSync } from './shell-command-projection.cjs';
|
||||
import { execGit, platformWriteSync, platformReadSync, toNativePath, posixNormalize } from './shell-command-projection.cjs';
|
||||
import { realClock } from './clock.cjs';
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports -- io.cjs is an export= CommonJS module
|
||||
import io = require('./io.cjs');
|
||||
@@ -282,7 +282,7 @@ function getInitGitState(cwd: string): GitState {
|
||||
}
|
||||
resolved = path.resolve(resolved);
|
||||
if (process.platform === 'win32') {
|
||||
return resolved.replace(/\//g, '\\').toLowerCase();
|
||||
return toNativePath(resolved).toLowerCase();
|
||||
}
|
||||
return resolved;
|
||||
};
|
||||
@@ -293,7 +293,7 @@ function getInitGitState(cwd: string): GitState {
|
||||
try {
|
||||
const prefixResult = execGit(['rev-parse', '--show-prefix'], { cwd, timeout: 5000 }) as unknown as Record<string, unknown>;
|
||||
if (prefixResult['exitCode'] === 0) {
|
||||
const prefix = (typeof prefixResult['stdout'] === 'string' ? prefixResult['stdout'] : '').trim().replace(/\\/g, '/');
|
||||
const prefix = posixNormalize((typeof prefixResult['stdout'] === 'string' ? prefixResult['stdout'] : '').trim());
|
||||
inNestedSubdir = prefix.length > 0 && prefix !== '.' && prefix !== './';
|
||||
resolvedByGitPrefix = true;
|
||||
}
|
||||
@@ -309,7 +309,7 @@ function getInitGitState(cwd: string): GitState {
|
||||
inNestedSubdir = false;
|
||||
} else {
|
||||
const rel = path.relative(rootNorm, cwdNorm);
|
||||
const relNorm = process.platform === 'win32' ? rel.replace(/\//g, '\\') : rel;
|
||||
const relNorm = toNativePath(rel);
|
||||
inNestedSubdir =
|
||||
relNorm !== '' &&
|
||||
relNorm !== '.' &&
|
||||
@@ -323,7 +323,7 @@ function getInitGitState(cwd: string): GitState {
|
||||
}
|
||||
|
||||
if (inNestedSubdir && typeof worktreeRoot === 'string') {
|
||||
const toComparableRaw = (p: string) => p.replace(/\\/g, '/').replace(/\/+$/g, '').toLowerCase();
|
||||
const toComparableRaw = (p: string) => posixNormalize(p).replace(/\/+$/g, '').toLowerCase();
|
||||
if (toComparableRaw(worktreeRoot) === toComparableRaw(String(cwd))) {
|
||||
inNestedSubdir = false;
|
||||
}
|
||||
@@ -2226,7 +2226,7 @@ function buildAgentSkillsBlock(
|
||||
if (entry.kind === 'directive') {
|
||||
return `- Load the \`${entry.name}\` skill via the Skill tool before proceeding (plugin-provided).`;
|
||||
}
|
||||
return `- @${String(entry.ref).replace(/\\/g, '/')}`;
|
||||
return `- @${posixNormalize(String(entry.ref))}`;
|
||||
}).join('\n');
|
||||
return `<agent_skills>\nRead these user-configured skills:\n${lines}\n</agent_skills>`;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import runtimeArtifactLayout = require('./runtime-artifact-layout.cjs');
|
||||
import runtimeArtifactInstallPlan = require('./runtime-artifact-install-plan.cjs');
|
||||
import runtimeNamePolicy = require('./runtime-name-policy.cjs');
|
||||
import installProfiles = require('./install-profiles.cjs');
|
||||
import { posixNormalize } from './shell-command-projection.cjs';
|
||||
|
||||
const { processAttribution } = runtimeArtifactConversion;
|
||||
// resolveRuntimeArtifactLayout: accessed via module ref (not destructured) so
|
||||
@@ -970,8 +971,8 @@ function installOpencodeFamilyArtifacts(
|
||||
isGlobal,
|
||||
isOpencode: behaviors.skipHomePrefixSubstitution === true,
|
||||
isWindowsHost: process.platform === 'win32',
|
||||
resolvedTarget: path.resolve(configDir).replace(/\\/g, '/'),
|
||||
homeDir: os.homedir().replace(/\\/g, '/'),
|
||||
resolvedTarget: posixNormalize(path.resolve(configDir)),
|
||||
homeDir: posixNormalize(os.homedir()),
|
||||
});
|
||||
|
||||
const commandDir = runtimeArtifactInstallPlan.assertDestWithinConfigHome(configDir, 'command');
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
import path from 'node:path';
|
||||
import { posixNormalize } from './shell-command-projection.cjs';
|
||||
|
||||
/** An unvalidated migration record supplied by the caller. */
|
||||
export type MigrationRecord = Record<string, unknown>;
|
||||
@@ -64,7 +65,7 @@ function requireActionEvidence(action: MigrationAction, field: string, migration
|
||||
|
||||
function validateSafeRelPath(relPath: string, migration: MigrationRecord, actionType: string): void {
|
||||
const source = actionSource(migration, { relPath });
|
||||
const normalized = relPath.replace(/\\/g, '/');
|
||||
const normalized = posixNormalize(relPath);
|
||||
if (path.isAbsolute(normalized) || path.win32.isAbsolute(normalized)) {
|
||||
throw new Error(`migration action ${actionType} relPath must stay inside configDir: ${source}`);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
type MigrationRecord,
|
||||
type MigrationAction,
|
||||
} from './installer-migration-authoring.cjs';
|
||||
import { platformWriteSync, retryRenameSync } from './shell-command-projection.cjs';
|
||||
import { platformWriteSync, retryRenameSync, posixNormalize } from './shell-command-projection.cjs';
|
||||
import { realClock, type Clock } from './clock.cjs';
|
||||
|
||||
const MANIFEST_NAME = 'gsd-file-manifest.json';
|
||||
@@ -139,7 +139,7 @@ function normalizeRelPath(relPath: string): string {
|
||||
if (typeof relPath !== 'string' || relPath.trim() === '') {
|
||||
throw new Error('migration action relPath must be a non-empty string');
|
||||
}
|
||||
const normalized = relPath.replace(/\\/g, '/');
|
||||
const normalized = posixNormalize(relPath);
|
||||
if (path.isAbsolute(normalized) || path.win32.isAbsolute(normalized)) {
|
||||
throw new Error(`migration action relPath must stay inside configDir: ${relPath}`);
|
||||
}
|
||||
|
||||
@@ -1672,7 +1672,11 @@ function cmdPhaseComplete(cwd: string, phaseNum: string, raw: boolean): void {
|
||||
? reqContent.slice(traceabilityHeadingMatch.index)
|
||||
: '';
|
||||
const tableReqIds = new Set<string>();
|
||||
const tableRowPat = /^\|\s*([A-Z][A-Z0-9]*-\d+)\s*\|/gm;
|
||||
// #2203: match REQ-IDs in any pipe-delimited cell (not just the first
|
||||
// column) so a traceability table that leads with a status column (e.g.
|
||||
// | ☐ | REQ-01 | …) is parsed correctly instead of reporting every row
|
||||
// as missing.
|
||||
const tableRowPat = /\|\s*([A-Z][A-Z0-9]*-\d+)\s*\|/g;
|
||||
let tableMatch: RegExpExecArray | null;
|
||||
while ((tableMatch = tableRowPat.exec(traceabilitySection)) !== null) {
|
||||
tableReqIds.add(tableMatch[1]);
|
||||
|
||||
@@ -24,6 +24,7 @@ const { readGsdCommandNames, transformContentToHyphen } = commandRoster;
|
||||
import runtimeNamePolicy = require('./runtime-name-policy.cjs');
|
||||
const { getDirName } = runtimeNamePolicy;
|
||||
import capabilityRegistry = require('./capability-registry.cjs');
|
||||
import { posixNormalize } from './shell-command-projection.cjs';
|
||||
|
||||
// #1383: resolve GSD's version WITHOUT a top-level
|
||||
// `require('../../../package.json')`. That require ran at module load on every
|
||||
@@ -2349,8 +2350,8 @@ function computePathPrefix({ isGlobal, isOpencode, isWindowsHost: _isWindowsHost
|
||||
// Without this, path.join on Windows produces a backslash prefix that
|
||||
// leaks into markdown content and breaks cross-platform substring checks.
|
||||
// See DEFECT.WINDOWS-PATH-LEAK-IN-MARKDOWN-CONTENT in CONTEXT.md.
|
||||
const posixTarget = String(resolvedTarget).replace(/\\/g, '/');
|
||||
const posixHome = homeDir ? String(homeDir).replace(/\\/g, '/') : homeDir;
|
||||
const posixTarget = posixNormalize(String(resolvedTarget));
|
||||
const posixHome = homeDir ? posixNormalize(String(homeDir)) : homeDir;
|
||||
if (isGlobal && posixTarget.startsWith(posixHome) && !isOpencode) {
|
||||
return '$HOME' + posixTarget.slice(posixHome.length) + '/';
|
||||
}
|
||||
@@ -2696,8 +2697,8 @@ function rewriteStagedSkillBodies(stagedDir, opts) {
|
||||
} = opts;
|
||||
if (!fs.existsSync(stagedDir)) return;
|
||||
|
||||
const resolvedTarget = path.resolve(configDir).replace(/\\/g, '/');
|
||||
const homeDir = homedir().replace(/\\/g, '/');
|
||||
const resolvedTarget = posixNormalize(path.resolve(configDir));
|
||||
const homeDir = posixNormalize(homedir());
|
||||
const isGlobal = scope === 'global';
|
||||
const isOpencode = false; // #2087: opencode installs via the combined-family engine path, never through the generic rewrite
|
||||
const isWindowsHost = platform === 'win32';
|
||||
@@ -2734,8 +2735,8 @@ function rewriteStagedCommandBodies(stagedDir, opts) {
|
||||
} = opts;
|
||||
if (!fs.existsSync(stagedDir)) return stagedDir;
|
||||
|
||||
const resolvedTarget = path.resolve(configDir).replace(/\\/g, '/');
|
||||
const homeDir = homedir().replace(/\\/g, '/');
|
||||
const resolvedTarget = posixNormalize(path.resolve(configDir));
|
||||
const homeDir = posixNormalize(homedir());
|
||||
const isGlobal = scope === 'global';
|
||||
const isOpencode = false; // #2087: opencode installs via the combined-family engine path, never through the generic rewrite
|
||||
const isWindowsHost = platform === 'win32';
|
||||
|
||||
@@ -177,9 +177,10 @@ function createRuntimeArtifactInstallPlan(args: CreateRuntimeArtifactInstallPlan
|
||||
// to kind.stage() for agents kind entries with a converter (convertedAgentsKind).
|
||||
// NO _stampNonClaudeRuntimeDefaults — agents are NOT stamped in the inline loop.
|
||||
const os = _require('node:os') as typeof import('node:os');
|
||||
const { posixNormalize } = _require('./shell-command-projection.cjs') as { posixNormalize: (p: string) => string };
|
||||
const homedirFn: () => string = homedir ?? (() => os.homedir());
|
||||
const resolvedTarget = path.resolve(layout.configDir).replace(/\\/g, '/');
|
||||
const homeDir = homedirFn().replace(/\\/g, '/');
|
||||
const resolvedTarget = posixNormalize(path.resolve(layout.configDir));
|
||||
const homeDir = posixNormalize(homedirFn());
|
||||
const isGlobal = scope === 'global';
|
||||
const isOpencode = layout.runtime === 'opencode';
|
||||
const isWindowsHost = (platform ?? process.platform) === 'win32';
|
||||
|
||||
@@ -30,6 +30,7 @@ import runtimeArtifactConversion = require('./runtime-artifact-conversion.cjs');
|
||||
const conversionExports = runtimeArtifactConversion as Record<string, unknown> & {
|
||||
readGsdCommandNames?: () => string[];
|
||||
};
|
||||
import { posixNormalize } from './shell-command-projection.cjs';
|
||||
|
||||
// In .cts (CommonJS output) files, `require` is available as a global.
|
||||
const _require: NodeRequire = require;
|
||||
@@ -279,7 +280,7 @@ function kimiAgentsKind(destSubpath: string, prefix: string, configDir: string):
|
||||
if (!entry.isFile() || !entry.name.endsWith('.md')) continue;
|
||||
const agentPath = path.join(stagedAgents, entry.name);
|
||||
subagents.push({
|
||||
path: path.join('agents', entry.name).replace(/\\/g, '/'),
|
||||
path: posixNormalize(path.join('agents', entry.name)),
|
||||
content: fs.readFileSync(agentPath, 'utf8'),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -310,7 +310,7 @@ function normalizeNodePath(execPath: string, opts?: NodeNormOpts): string {
|
||||
const env = (opts && opts.env) || process.env;
|
||||
const existsSync = (opts && opts.existsSync) || fs.existsSync;
|
||||
|
||||
const normalizedForMatch = execPath.replace(/\\/g, '/');
|
||||
const normalizedForMatch = shellCmdProjection.posixNormalize(execPath);
|
||||
if (/\/fnm_multishells\/[0-9]+_[0-9]+\/node(\.exe)?$/i.test(normalizedForMatch)) {
|
||||
const candidates: string[] = [];
|
||||
if (env.FNM_DIR) {
|
||||
@@ -363,7 +363,7 @@ function resolveNodeRunner(opts?: NodeNormOpts): string | null {
|
||||
const execPath = typeof process.execPath === 'string' ? process.execPath : '';
|
||||
if (!execPath) return null;
|
||||
const stablePath = normalizeNodePath(execPath, opts);
|
||||
return JSON.stringify(stablePath.replace(/\\/g, '/'));
|
||||
return JSON.stringify(shellCmdProjection.posixNormalize(stablePath));
|
||||
}
|
||||
|
||||
interface BashRunnerOpts {
|
||||
@@ -389,7 +389,7 @@ function resolveBashRunner(opts?: BashRunnerOpts): string | null {
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (candidate && exists(candidate)) {
|
||||
return JSON.stringify(candidate.replace(/\\/g, '/'));
|
||||
return JSON.stringify(shellCmdProjection.posixNormalize(candidate));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -449,7 +449,7 @@ function rewriteLegacyManagedNodeHookCommands(settings: Settings, absoluteRunner
|
||||
scriptPath = m[2] || m[3] || m[4] || '';
|
||||
} else {
|
||||
_runnerToken = m[1];
|
||||
const runnerPath = (m[2] || m[3] || m[4] || '').replace(/\\/g, '/');
|
||||
const runnerPath = shellCmdProjection.posixNormalize(m[2] || m[3] || m[4] || '');
|
||||
const stableRunner = normalizeNodePath(runnerPath);
|
||||
if (stableRunner === runnerPath && platform !== 'win32') continue;
|
||||
scriptToken = m[5];
|
||||
@@ -694,10 +694,10 @@ function buildCodexHookWindowsShimIR(scriptAbsPath: string, absoluteRunnerToken:
|
||||
} catch {
|
||||
interpreter = absoluteRunnerToken;
|
||||
}
|
||||
const targetAbs = scriptAbsPath.replace(/\\/g, '/');
|
||||
const targetAbs = shellCmdProjection.posixNormalize(scriptAbsPath);
|
||||
const scriptQuoted = JSON.stringify(targetAbs);
|
||||
const cmdPath = scriptAbsPath.replace(/\.js$/, '.cmd');
|
||||
const hookCommand = JSON.stringify(cmdPath.replace(/\\/g, '/'));
|
||||
const hookCommand = JSON.stringify(shellCmdProjection.posixNormalize(cmdPath));
|
||||
const runnerQuoted = JSON.stringify(interpreter);
|
||||
return {
|
||||
invocation: { interpreter, target: scriptAbsPath },
|
||||
@@ -726,7 +726,7 @@ function ensureCodexHooksJsonSessionStart(targetDir: string, opts: EnsureCodexSe
|
||||
const hooksJsonPath = path.join(targetDir, 'hooks.json');
|
||||
if (!absoluteRunner) return { changed: false, wrote: false, path: hooksJsonPath };
|
||||
|
||||
const scriptPath = path.resolve(targetDir, 'hooks', 'gsd-check-update.js').replace(/\\/g, '/');
|
||||
const scriptPath = shellCmdProjection.posixNormalize(path.resolve(targetDir, 'hooks', 'gsd-check-update.js'));
|
||||
const cmdShimPath = scriptPath.replace(/\.js$/, '.cmd');
|
||||
|
||||
let managedCommand: string | undefined;
|
||||
@@ -757,7 +757,7 @@ function ensureCodexHooksJsonSessionStart(targetDir: string, opts: EnsureCodexSe
|
||||
if (!managedCommand) return { changed: false, wrote: false, path: hooksJsonPath };
|
||||
|
||||
const commandWindows = platform === 'win32'
|
||||
? JSON.stringify(cmdShimPath.replace(/\\/g, '/'))
|
||||
? JSON.stringify(shellCmdProjection.posixNormalize(cmdShimPath))
|
||||
: undefined;
|
||||
|
||||
return reconcileCodexHooksJsonSessionStart(targetDir, { managedCommand, commandWindows });
|
||||
@@ -778,7 +778,7 @@ function ensureCodexHooksJsonEvent(targetDir: string, eventName: string, opts: E
|
||||
const hooksJsonPath = path.join(targetDir, 'hooks.json');
|
||||
if (!absoluteRunner) return { changed: false, wrote: false, path: hooksJsonPath };
|
||||
|
||||
const scriptPath = path.resolve(targetDir, 'hooks', 'gsd-context-monitor.js').replace(/\\/g, '/');
|
||||
const scriptPath = shellCmdProjection.posixNormalize(path.resolve(targetDir, 'hooks', 'gsd-context-monitor.js'));
|
||||
|
||||
let managedCommand: string | undefined;
|
||||
if (platform === 'win32') {
|
||||
@@ -846,7 +846,7 @@ function buildHookCommand(configDir: string, hookName: string, opts?: BuildHookC
|
||||
});
|
||||
return JSON.stringify(`${portableBaseDir}/hooks/${hookName}`);
|
||||
}
|
||||
return JSON.stringify(configDir.replace(/\\/g, '/') + '/hooks/' + hookName);
|
||||
return JSON.stringify(shellCmdProjection.posixNormalize(configDir) + '/hooks/' + hookName);
|
||||
}
|
||||
|
||||
const nodeRunner = resolveNodeRunner();
|
||||
@@ -866,7 +866,7 @@ function buildHookCommand(configDir: string, hookName: string, opts?: BuildHookC
|
||||
});
|
||||
}
|
||||
|
||||
const hooksPath = configDir.replace(/\\/g, '/') + '/hooks/' + hookName;
|
||||
const hooksPath = shellCmdProjection.posixNormalize(configDir) + '/hooks/' + hookName;
|
||||
return projectManagedHookCommand({
|
||||
absoluteRunner: runner,
|
||||
scriptPath: hooksPath,
|
||||
@@ -1027,7 +1027,7 @@ function writeClineArtifacts(targetDir: string, isGlobalInstall: boolean): strin
|
||||
function buildCursorHookEntry(scriptPath: string): Record<string, unknown> {
|
||||
return {
|
||||
type: 'command',
|
||||
command: scriptPath.replace(/\\/g, '/'),
|
||||
command: shellCmdProjection.posixNormalize(scriptPath),
|
||||
[GSD_CURSOR_HOOK_MARKER]: true,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
* This module does not read the filesystem directly.
|
||||
*/
|
||||
|
||||
import { posixNormalize } from './shell-command-projection.cjs';
|
||||
|
||||
// ─── ORM Patterns ───────────────────────────────────────────────────────────
|
||||
|
||||
export interface SchemaPattern {
|
||||
@@ -82,7 +84,7 @@ export function detectSchemaFiles(files: string[]): DetectSchemaFilesResult {
|
||||
const matches: string[] = [];
|
||||
const orms = new Set<string>();
|
||||
for (const rawFile of files) {
|
||||
const file = rawFile.replace(/\\/g, '/');
|
||||
const file = posixNormalize(rawFile);
|
||||
for (const { pattern, orm } of SCHEMA_PATTERNS) {
|
||||
if (pattern.test(file)) {
|
||||
matches.push(rawFile);
|
||||
|
||||
@@ -18,6 +18,43 @@ import fs from 'node:fs';
|
||||
// at load time and become un-mockable.
|
||||
import childProcess from 'node:child_process';
|
||||
|
||||
/**
|
||||
* Convert a filesystem path to POSIX form (forward slashes) by translating the
|
||||
* platform-native separator. Single seam for native→POSIX conversion.
|
||||
*
|
||||
* Prefer this over `p.replace(/\\/g, '/')`: the regex form hardcodes both
|
||||
* separators and corrupts POSIX paths containing a literal backslash (a legal
|
||||
* filename character). Splitting on `path.sep` only ever touches real
|
||||
* separators — a no-op on POSIX, `\`→`/` on Windows.
|
||||
*/
|
||||
export function toPosixPath(p: string): string {
|
||||
return p.split(path.sep).join(path.posix.sep);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a filesystem path to the platform-native separator form. No-op on
|
||||
* POSIX; `/`→`\` on Windows. Prefer this over a
|
||||
* `process.platform === 'win32' ? p.replace(/\//g, '\\') : p` ternary.
|
||||
*/
|
||||
export function toNativePath(p: string): string {
|
||||
return p.split(path.posix.sep).join(path.sep);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize ALL backslashes to forward slashes, unconditionally and independent
|
||||
* of the running OS. Use this when emitting a path into a POSIX/bash target
|
||||
* (which may differ from the running platform — e.g. generating a Windows config
|
||||
* on a Linux runner) or when parsing input whose separators are unpredictable.
|
||||
*
|
||||
* Contrast `toPosixPath`, which is running-OS-relative (splits on `path.sep`) and
|
||||
* is for *this machine's* filesystem paths. Do NOT use `toPosixPath` for
|
||||
* target-platform projection — on a Linux runner it would not convert a
|
||||
* Windows-target path's backslashes.
|
||||
*/
|
||||
export function posixNormalize(p: string): string {
|
||||
return p.replace(/\\/g, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true when a managed hook command must be prefixed with PowerShell's
|
||||
* call operator so a quoted executable token is invokable by the target
|
||||
@@ -95,7 +132,7 @@ export function buildLocalShellHookCommand({ localPrefix, hookFile, bashRunner,
|
||||
export function formatManagedHookScriptToken(scriptPath: string, opts: { platform?: string } = {}): string | null {
|
||||
const platform = opts.platform || process.platform;
|
||||
if (platform !== 'win32') return null;
|
||||
return JSON.stringify(scriptPath.replace(/\\/g, '/'));
|
||||
return JSON.stringify(posixNormalize(scriptPath));
|
||||
}
|
||||
|
||||
export function projectLocalHookPrefix({ runtime: _runtime = 'claude', dirName, hookPathStyle }: { runtime?: string; dirName?: string | null; hookPathStyle?: string | null }): string | undefined | null {
|
||||
@@ -114,8 +151,8 @@ export function projectLocalHookPrefix({ runtime: _runtime = 'claude', dirName,
|
||||
}
|
||||
|
||||
export function projectPortableHookBaseDir({ configDir, homeDir }: { configDir?: string | null; homeDir?: string | null }): string {
|
||||
const normalizedConfigDir = String(configDir || '').replace(/\\/g, '/');
|
||||
const normalizedHome = String(homeDir || '').replace(/\\/g, '/');
|
||||
const normalizedConfigDir = posixNormalize(String(configDir || ''));
|
||||
const normalizedHome = posixNormalize(String(homeDir || ''));
|
||||
if (!normalizedConfigDir || !normalizedHome) return normalizedConfigDir;
|
||||
return normalizedConfigDir.startsWith(normalizedHome)
|
||||
? '$HOME' + normalizedConfigDir.slice(normalizedHome.length)
|
||||
@@ -145,7 +182,7 @@ export function projectManagedHookCommand({ absoluteRunner, scriptPath, runtime
|
||||
platform?: string;
|
||||
}): string | null {
|
||||
if (!absoluteRunner || !scriptPath) return null;
|
||||
const normalizedScriptPath = platform === 'win32' ? scriptPath.replace(/\\/g, '/') : scriptPath;
|
||||
const normalizedScriptPath = platform === 'win32' ? posixNormalize(scriptPath) : scriptPath;
|
||||
return projectShellCommandText({
|
||||
runnerToken: absoluteRunner,
|
||||
argTokens: [JSON.stringify(normalizedScriptPath)],
|
||||
@@ -245,15 +282,15 @@ export function isManagedHookCommand(commandText: unknown, opts: { surface?: str
|
||||
if (Array.isArray(opts.args) && opts.args.length > 0) {
|
||||
for (const arg of opts.args) {
|
||||
if (typeof arg !== 'string') continue;
|
||||
const argBasename = arg.replace(/\\/g, '/').split('/').pop() || '';
|
||||
const argBasename = posixNormalize(arg).split('/').pop() || '';
|
||||
if (isManagedHookBasename(argBasename, { surface })) return true;
|
||||
}
|
||||
}
|
||||
|
||||
const normalizedCommand = commandText.replace(/\\/g, '/');
|
||||
const normalizedCommand = posixNormalize(commandText);
|
||||
|
||||
if (typeof opts.configDir === 'string' && opts.configDir.length > 0) {
|
||||
const normalizedHooksDir = `${path.join(opts.configDir, 'hooks').replace(/\\/g, '/')}/`;
|
||||
const normalizedHooksDir = `${posixNormalize(path.join(opts.configDir, 'hooks'))}/`;
|
||||
if (!normalizedCommand.includes(normalizedHooksDir)) return false;
|
||||
}
|
||||
|
||||
@@ -295,7 +332,7 @@ export function projectLegacySettingsHookCommand({
|
||||
platform?: string;
|
||||
}): string | null {
|
||||
if (!absoluteRunner || !scriptPath) return null;
|
||||
const normalizedScriptPath = platform === 'win32' ? scriptPath.replace(/\\/g, '/') : scriptPath;
|
||||
const normalizedScriptPath = platform === 'win32' ? posixNormalize(scriptPath) : scriptPath;
|
||||
// #1693: a script path already carrying a `"$CLAUDE_PROJECT_DIR"`-anchored
|
||||
// quoted prefix (local installs) is already a valid shell token — only the
|
||||
// variable is quoted, the rest is bare. JSON.stringify-ing it on Windows
|
||||
@@ -378,7 +415,7 @@ export function projectPathActionProjection({
|
||||
let shellActions: ShellAction[];
|
||||
if (isWin32) {
|
||||
const psTargetDir = escapePowerShellSingleQuoted(targetDir);
|
||||
const bashTargetDir = escapeSingleQuotedShellLiteral(String(targetDir).replace(/\\/g, '/'));
|
||||
const bashTargetDir = escapeSingleQuotedShellLiteral(posixNormalize(String(targetDir)));
|
||||
shellActions = [
|
||||
{
|
||||
label: 'PowerShell',
|
||||
|
||||
@@ -1650,6 +1650,16 @@ function syncStateFrontmatter(content: string, cwd: string | undefined): string
|
||||
derivedFm['progress'] = normalizeProgressNumbers(existingFm['progress']);
|
||||
}
|
||||
|
||||
// #2202: carry forward any existing frontmatter key that the schema does not
|
||||
// own, so custom/unknown keys are not silently dropped on every mutating verb.
|
||||
// Schema-owned keys (already in derivedFm from buildStateFrontmatter + the
|
||||
// preserve guards above) still win.
|
||||
for (const key of Object.keys(existingFm)) {
|
||||
if (!(key in derivedFm) && existingFm[key] !== undefined) {
|
||||
derivedFm[key] = existingFm[key];
|
||||
}
|
||||
}
|
||||
|
||||
const yamlStr = reconstructFrontmatter(derivedFm as unknown as Frontmatter);
|
||||
return `---\n${yamlStr}\n---\n\n${body}`;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { platformWriteSync } from './shell-command-projection.cjs';
|
||||
import { platformWriteSync, posixNormalize } from './shell-command-projection.cjs';
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
import installProfiles = require('./install-profiles.cjs');
|
||||
const {
|
||||
@@ -354,8 +354,8 @@ function applySurface(runtimeConfigDir: string, layout: Layout, manifest: Map<st
|
||||
// surface-path agents lack path-prefix rewrites and Co-Authored-By trailers,
|
||||
// diverging from a fresh install.
|
||||
const _homedirFn: () => string = opts?.homedir ?? (() => os.homedir());
|
||||
const _resolvedTarget = path.resolve(layout.configDir).replace(/\\/g, '/');
|
||||
const _homeDir = _homedirFn().replace(/\\/g, '/');
|
||||
const _resolvedTarget = posixNormalize(path.resolve(layout.configDir));
|
||||
const _homeDir = posixNormalize(_homedirFn());
|
||||
const _isGlobal = (layout.scope ?? 'global') === 'global';
|
||||
const _isOpencode = layout.runtime === 'opencode';
|
||||
const _isWindowsHost = (opts?.platform ?? process.platform) === 'win32';
|
||||
|
||||
@@ -22,7 +22,7 @@ import stateMod = require('./state.cjs');
|
||||
import modelProfilesMod = require('./model-profiles.cjs');
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports -- plan-scan.cjs is an export= CommonJS module
|
||||
import planScanMod = require('./plan-scan.cjs');
|
||||
import { execGit, platformReadSync as safeReadFile, platformWriteSync } from './shell-command-projection.cjs';
|
||||
import { execGit, platformReadSync as safeReadFile, platformWriteSync, posixNormalize } from './shell-command-projection.cjs';
|
||||
import { PACKAGE_NAME } from './package-identity.cjs';
|
||||
import { formatGsdSlash, resolveRuntime } from './runtime-slash.cjs';
|
||||
import { detectSchemaFiles, checkSchemaDrift } from './schema-detect.cjs';
|
||||
@@ -1157,7 +1157,7 @@ function cmdValidateConsistency(cwd: string, raw: boolean): void {
|
||||
|
||||
for (const dir of dirs) {
|
||||
const phasePath = path.join(phaseRoot, dir);
|
||||
const phaseLabel = path.relative(planBase, phasePath).replace(/\\/g, '/');
|
||||
const phaseLabel = posixNormalize(path.relative(planBase, phasePath));
|
||||
const phaseFiles = fs.readdirSync(phasePath);
|
||||
const plans = phaseFiles.filter((f) => f.endsWith('-PLAN.md')).sort();
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { execGit as execGitSeam } from './shell-command-projection.cjs';
|
||||
import { execGit as execGitSeam, posixNormalize } from './shell-command-projection.cjs';
|
||||
|
||||
// Default timeout for worktree-related git subprocess calls.
|
||||
// 10 s is generous enough for normal git operations on large repos while still
|
||||
@@ -588,7 +588,7 @@ function rescueSummaryArtifacts(
|
||||
// relPath is the path relative to the worktree root (e.g. ".planning/q1-SUMMARY.md")
|
||||
// Normalize to forward slashes so the Set comparison against `git status --porcelain`
|
||||
// output works on Windows too (git always emits forward slashes in porcelain output).
|
||||
const relPath = absPath.slice(worktreePath.length).replace(/^[/\\]/, '').replace(/\\/g, '/');
|
||||
const relPath = posixNormalize(absPath.slice(worktreePath.length).replace(/^[/\\]/, ''));
|
||||
|
||||
// #706: skip rescue when the SUMMARY is already committed on the branch.
|
||||
// Use `git cat-file -e HEAD:<relPath>` (not `ls-files --error-unmatch`) so
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"gsd-domain-researcher.md": 7032,
|
||||
"gsd-eval-auditor.md": 12496,
|
||||
"gsd-eval-planner.md": 7008,
|
||||
"gsd-executor.md": 43607,
|
||||
"gsd-executor.md": 43973,
|
||||
"gsd-framework-selector.md": 6778,
|
||||
"gsd-integration-checker.md": 15238,
|
||||
"gsd-intel-updater.md": 18166,
|
||||
|
||||
192
tests/antigravity-repo-grounding.test.cjs
Normal file
192
tests/antigravity-repo-grounding.test.cjs
Normal file
@@ -0,0 +1,192 @@
|
||||
// allow-test-rule: source-text-is-the-product (see #2073)
|
||||
// gsd-core/workflows/review.md is a workflow document whose bash blocks ARE
|
||||
// what /gsd-review loads and executes at runtime. Asserting the invocation
|
||||
// shape asserts the deployed contract — this is behavioral coverage of the
|
||||
// workflow, not a source-grep over application code.
|
||||
|
||||
/**
|
||||
* Antigravity reviewer repo-grounding tests (#2176)
|
||||
*
|
||||
* The agy block invoked the CLI without granting it the repo under review:
|
||||
* no --add-dir on either invocation arm, and no absolute repo-root anchor in
|
||||
* _AGY_PROMPT. The agent frequently anchored on its own
|
||||
* ~/.gemini/antigravity-cli/scratch dir, reviewed the plan text in isolation
|
||||
* (the exact failure the block's Review Instructions forbid), and its
|
||||
* ungrounded verdict flowed into the Consensus Summary at full weight,
|
||||
* undetected.
|
||||
*
|
||||
* These tests pin the fix: capability-probed --add-dir (mirrors the Codex
|
||||
* bypass-flag probe), absolute-root prompt anchor (agy AND the cursor-agent
|
||||
* block, which shared the anchor gap), a mandated self-report line, a stamped
|
||||
* blind-review marker, and consensus down-weighting of marked reviews.
|
||||
* Each assertion fails against the pre-fix block.
|
||||
*/
|
||||
|
||||
const { test, describe } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const ROOT = path.join(__dirname, '..');
|
||||
const REVIEW_PATH = path.join(ROOT, 'gsd-core', 'workflows', 'review.md');
|
||||
|
||||
function reviewContent() {
|
||||
return fs.readFileSync(REVIEW_PATH, 'utf-8');
|
||||
}
|
||||
|
||||
function agyBashBlock() {
|
||||
const fences = reviewContent().match(/```bash[\s\S]*?```/g) || [];
|
||||
const agy = fences.find((f) => /\bagy\b/.test(f) && /gsd-review-antigravity/.test(f));
|
||||
assert.ok(agy, 'review.md should contain the agy invocation bash block');
|
||||
return agy;
|
||||
}
|
||||
|
||||
function cursorBashBlock() {
|
||||
const fences = reviewContent().match(/```bash[\s\S]*?```/g) || [];
|
||||
const cursor = fences.find((f) => /cursor-agent -p/.test(f));
|
||||
assert.ok(cursor, 'review.md should contain the cursor-agent invocation bash block');
|
||||
return cursor;
|
||||
}
|
||||
|
||||
describe('Antigravity reviewer repo grounding in /gsd-review (#2176)', () => {
|
||||
test('probes agy for --add-dir support (capability-probe idiom, mirrors the Codex block)', () => {
|
||||
const block = agyBashBlock();
|
||||
assert.ok(
|
||||
/agy --help 2>\/dev\/null \| grep -q -- '--add-dir'/.test(block),
|
||||
'agy block must capability-probe --add-dir via `agy --help | grep -q` so older CLIs still run',
|
||||
);
|
||||
});
|
||||
|
||||
test('passes --add-dir with the repo root when supported', () => {
|
||||
const block = agyBashBlock();
|
||||
assert.ok(
|
||||
/set -- "\$@" --add-dir "\$_AGY_WS"/.test(block),
|
||||
'the probed arm must append --add-dir "$_AGY_WS" so both invocation arms (which expand "$@") receive it',
|
||||
);
|
||||
});
|
||||
|
||||
test('_AGY_PROMPT is anchored to the absolute repo root', () => {
|
||||
const block = agyBashBlock();
|
||||
const promptLine = block.split('\n').find((l) => l.startsWith('_AGY_PROMPT='));
|
||||
assert.ok(promptLine, 'agy block must define _AGY_PROMPT');
|
||||
assert.ok(
|
||||
/\$_AGY_WS/.test(promptLine),
|
||||
'_AGY_PROMPT must embed the absolute repo root ($_AGY_WS) so repo-relative references resolve on the no---add-dir fallback',
|
||||
);
|
||||
});
|
||||
|
||||
test('_AGY_PROMPT mandates a REVIEWED-WITHOUT-REPO-ACCESS self-report', () => {
|
||||
const block = agyBashBlock();
|
||||
const promptLine = block.split('\n').find((l) => l.startsWith('_AGY_PROMPT='));
|
||||
assert.ok(
|
||||
/REVIEWED-WITHOUT-REPO-ACCESS/.test(promptLine),
|
||||
'_AGY_PROMPT must require the exact self-report line when the reviewer cannot read the repo',
|
||||
);
|
||||
});
|
||||
|
||||
test('stamps a blind-review marker on self-reported or scratch-anchored output', () => {
|
||||
const block = agyBashBlock();
|
||||
assert.ok(
|
||||
/head -5 [^|]*\| grep -q 'REVIEWED-WITHOUT-REPO-ACCESS'/.test(block),
|
||||
'the self-report tell must be anchored to the head of the output, not a whole-body substring',
|
||||
);
|
||||
assert.ok(
|
||||
/grep -qiE '\(workspace\|working\) \(directory\|dir\)\.\{0,40\}antigravity-cli\/scratch'/.test(block),
|
||||
'the scratch tell must be anchored to a workspace-declaration phrasing whose bridge (.{0,40}) can span the dotted ~/.gemini/ path prefix',
|
||||
);
|
||||
assert.ok(
|
||||
/\[reviewed-without-repo-access\]/.test(block),
|
||||
'detected blind reviews must be stamped with the [reviewed-without-repo-access] marker',
|
||||
);
|
||||
});
|
||||
|
||||
test('marker stamping avoids sed -i (BSD/GNU divergence) — uses temp file + mv', () => {
|
||||
const block = agyBashBlock();
|
||||
assert.ok(!/sed -i/.test(block), 'agy block must not use sed -i (BSD vs GNU incompatibility)');
|
||||
assert.ok(
|
||||
/\.tmp && \\?\s*\n?\s*mv /.test(block),
|
||||
'marker stamping should rewrite via temp file + mv',
|
||||
);
|
||||
});
|
||||
|
||||
test('Consensus Summary down-weights marked blind reviews', () => {
|
||||
const content = reviewContent();
|
||||
const consensusIdx = content.indexOf('## Consensus Summary');
|
||||
assert.ok(consensusIdx >= 0, 'review.md should contain the Consensus Summary section');
|
||||
const consensus = content.slice(consensusIdx, consensusIdx + 2000);
|
||||
assert.ok(
|
||||
/\[reviewed-without-repo-access\]/.test(consensus),
|
||||
'consensus instructions must reference the blind-review marker',
|
||||
);
|
||||
assert.ok(
|
||||
/REVIEWED-WITHOUT-REPO-ACCESS/.test(consensus),
|
||||
'consensus instructions must also honor the raw self-report line',
|
||||
);
|
||||
assert.ok(
|
||||
/not count its verdict at full consensus weight/.test(consensus),
|
||||
'marked reviews must be down-weighted, not counted at full weight',
|
||||
);
|
||||
});
|
||||
|
||||
test('blind-review detection behaves correctly on synthetic transcripts', () => {
|
||||
// Behavioral, not string-on-string: extract the actual detection compound
|
||||
// from the fence, point it at a temp file, and run it through bash for
|
||||
// ungrounded and grounded transcript shapes.
|
||||
const os = require('os');
|
||||
const { execFileSync } = require('node:child_process');
|
||||
const { toPosixPath } = require('../gsd-core/bin/lib/shell-command-projection.cjs');
|
||||
const block = agyBashBlock();
|
||||
const m = block.match(/\{ head -5[\s\S]*?\}; then/);
|
||||
assert.ok(m, 'detection compound not found in the agy block');
|
||||
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'agy-detect-'));
|
||||
const out = path.join(tmp, 'review-out.md');
|
||||
const detect = m[0]
|
||||
.replace(/\}; then$/, '}')
|
||||
// Convert the native path to POSIX form so it survives bash on Windows
|
||||
// runners (Git Bash accepts D:/... but eats backslashes).
|
||||
.replaceAll('/tmp/gsd-review-antigravity-{phase}.md', toPosixPath(out));
|
||||
const runDetect = (content) => {
|
||||
fs.writeFileSync(out, content);
|
||||
try {
|
||||
execFileSync('bash', ['-c', detect], { stdio: 'ignore' });
|
||||
return true; // exit 0 → blind review detected
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
try {
|
||||
// Ungrounded tells — must be stamped
|
||||
assert.equal(runDetect('REVIEWED-WITHOUT-REPO-ACCESS\n\n## Review\nPlan-only review.\n'), true,
|
||||
'self-report line in the head must be detected');
|
||||
assert.equal(runDetect('## Review\nMy working directory is ~/.gemini/antigravity-cli/scratch.\nPlan-only review.\n'), true,
|
||||
'full dotted scratch path in a workspace declaration must be detected (#2184 re-review Major)');
|
||||
assert.equal(runDetect('Workspace directory: /home/me/.gemini/antigravity-cli/scratch\n'), true,
|
||||
'colon-style workspace declaration must be detected');
|
||||
// Grounded shapes — must NOT be stamped
|
||||
assert.equal(runDetect('## Review\nVerified hooks/gsd-statusline.js against the plan. Solid.\n'), false,
|
||||
'ordinary grounded review must not be stamped');
|
||||
assert.equal(runDetect('## Review\nThe workflow mentions antigravity-cli/scratch as the agy scratch dir; the guard there is correct.\n'), false,
|
||||
'grounded review merely quoting the scratch path must not be stamped');
|
||||
assert.equal(
|
||||
runDetect('## Review\n\nGrounded findings below.\n\n### Details\nLine 20 of the workflow mentions REVIEWED-WITHOUT-REPO-ACCESS as the self-report marker; fine.\n'),
|
||||
false,
|
||||
'self-report string quoted beyond the first 5 lines must not be stamped');
|
||||
} finally {
|
||||
require('./helpers.cjs').cleanup(tmp);
|
||||
}
|
||||
});
|
||||
|
||||
test('cursor-agent prompt carries the same absolute-root anchor (identical gap, #2176 AC5)', () => {
|
||||
const block = cursorBashBlock();
|
||||
assert.ok(
|
||||
/_CURSOR_ROOT="\$\(git rev-parse --show-toplevel 2>\/dev\/null \|\| pwd\)"/.test(block),
|
||||
'cursor anchor must resolve the repo TOP-LEVEL (rev-parse, not bare pwd) so subdirectory invocations anchor correctly',
|
||||
);
|
||||
const promptLine = block.split('\n').find((l) => l.startsWith('CURSOR_PROMPT_ARG='));
|
||||
assert.ok(promptLine, 'cursor block must define CURSOR_PROMPT_ARG');
|
||||
assert.ok(
|
||||
/repository under review is at \$_CURSOR_ROOT/.test(promptLine),
|
||||
'CURSOR_PROMPT_ARG must anchor repo-relative references to the absolute repo root',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -16,7 +16,7 @@
|
||||
"agents/gsd-domain-researcher.md": "1db46cac3f4d9889",
|
||||
"agents/gsd-eval-auditor.md": "1b8391f1aafb067f",
|
||||
"agents/gsd-eval-planner.md": "3d10fd11147f6857",
|
||||
"agents/gsd-executor.md": "152ca51a10b99ff8",
|
||||
"agents/gsd-executor.md": "6b7b461429de7d35",
|
||||
"agents/gsd-framework-selector.md": "daa62c79619c76bf",
|
||||
"agents/gsd-integration-checker.md": "0643cd2d779b131c",
|
||||
"agents/gsd-intel-updater.md": "26c1f1e028c6346a",
|
||||
@@ -41,7 +41,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "600e9ed0cb6ed7bb",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "b67705431d738ae7",
|
||||
"gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "8bc541aabc2e143c",
|
||||
@@ -55,7 +55,7 @@
|
||||
"gsd-core/references/api-coverage.md": "205a43c5fa7c221c",
|
||||
"gsd-core/references/artifact-types.md": "e176817364a7cbf4",
|
||||
"gsd-core/references/autonomous-smart-discuss.md": "efd80aca449032ad",
|
||||
"gsd-core/references/checkpoints.md": "2de680837faa9752",
|
||||
"gsd-core/references/checkpoints.md": "509700508de43306",
|
||||
"gsd-core/references/common-bug-patterns.md": "780145be56352626",
|
||||
"gsd-core/references/context-budget.md": "177520ead2ae3a23",
|
||||
"gsd-core/references/continuation-format.md": "580287399ad3ba68",
|
||||
@@ -233,7 +233,7 @@
|
||||
"gsd-core/workflows/docs-update.md": "8e986e26d0e6e1a0",
|
||||
"gsd-core/workflows/edit-phase.md": "fc932e82ba1f585a",
|
||||
"gsd-core/workflows/eval-review.md": "eb4040eaa5b8497f",
|
||||
"gsd-core/workflows/execute-phase.md": "82beafd82b24210c",
|
||||
"gsd-core/workflows/execute-phase.md": "894d0e9efed72258",
|
||||
"gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "55d0706e80a2554a",
|
||||
"gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028",
|
||||
"gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "9b7107b31b60a3b9",
|
||||
@@ -285,7 +285,7 @@
|
||||
"gsd-core/workflows/remove-phase.md": "23b9eb0858a2535e",
|
||||
"gsd-core/workflows/remove-workspace.md": "d0bd7e0601138798",
|
||||
"gsd-core/workflows/resume-project.md": "98e2cf8908e73a52",
|
||||
"gsd-core/workflows/review.md": "43c052bba1cbd4ac",
|
||||
"gsd-core/workflows/review.md": "c4622380e61615c8",
|
||||
"gsd-core/workflows/scan.md": "a7fecd67e5cd655f",
|
||||
"gsd-core/workflows/secure-phase.md": "52ddc46233e8fa66",
|
||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
||||
@@ -328,7 +328,7 @@
|
||||
"hooks/gsd-read-guard.js": "9e423cd03e2d1b16",
|
||||
"hooks/gsd-read-injection-scanner.js": "eefea61f9b0e464c",
|
||||
"hooks/gsd-session-state.sh": "e54379ba86bf1b6d",
|
||||
"hooks/gsd-statusline.js": "8ae31be7a006204b",
|
||||
"hooks/gsd-statusline.js": "9e64af477e774de6",
|
||||
"hooks/gsd-update-banner.js": "55143a25f978f301",
|
||||
"hooks/gsd-validate-commit.sh": "bf5dd61d33cb3a38",
|
||||
"hooks/gsd-windsurf-pre-command.js": "948be1c6d14c79cd",
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"agents/gsd-domain-researcher.md": "671c9ea949889c4a",
|
||||
"agents/gsd-eval-auditor.md": "fcaec7b00f94c435",
|
||||
"agents/gsd-eval-planner.md": "a4a5b4b3f7828ba3",
|
||||
"agents/gsd-executor.md": "46f911cd211034b0",
|
||||
"agents/gsd-executor.md": "ed2c39fa8c03d056",
|
||||
"agents/gsd-framework-selector.md": "4b77eebbe9288d80",
|
||||
"agents/gsd-integration-checker.md": "fa53e2d78be1de74",
|
||||
"agents/gsd-intel-updater.md": "fa40e685d7441ace",
|
||||
@@ -112,7 +112,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "37fb6081fcb18038",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "b67705431d738ae7",
|
||||
"gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -126,7 +126,7 @@
|
||||
"gsd-core/references/api-coverage.md": "66264d41dfd9154a",
|
||||
"gsd-core/references/artifact-types.md": "a6d2e1f9453ffbf5",
|
||||
"gsd-core/references/autonomous-smart-discuss.md": "2fc710cde0ec7785",
|
||||
"gsd-core/references/checkpoints.md": "6aa620c6ca38bdf0",
|
||||
"gsd-core/references/checkpoints.md": "b1530628d693fc85",
|
||||
"gsd-core/references/common-bug-patterns.md": "780145be56352626",
|
||||
"gsd-core/references/context-budget.md": "f1ce57bf418824af",
|
||||
"gsd-core/references/continuation-format.md": "ce73e57d053ab8a7",
|
||||
@@ -304,7 +304,7 @@
|
||||
"gsd-core/workflows/docs-update.md": "f35922d15b7061c9",
|
||||
"gsd-core/workflows/edit-phase.md": "966a3eadd1bebc04",
|
||||
"gsd-core/workflows/eval-review.md": "f898936e2cfe4130",
|
||||
"gsd-core/workflows/execute-phase.md": "1b2d0b868f574e80",
|
||||
"gsd-core/workflows/execute-phase.md": "7d42e81188b3613a",
|
||||
"gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "4e265392b3f2ba0e",
|
||||
"gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028",
|
||||
"gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "811b6d8489571581",
|
||||
@@ -356,7 +356,7 @@
|
||||
"gsd-core/workflows/remove-phase.md": "df9a45f0b1880999",
|
||||
"gsd-core/workflows/remove-workspace.md": "a7ca66db6b7c132c",
|
||||
"gsd-core/workflows/resume-project.md": "f28da1200e4545f4",
|
||||
"gsd-core/workflows/review.md": "ad7c0f372ed986ae",
|
||||
"gsd-core/workflows/review.md": "3ce57633978035ad",
|
||||
"gsd-core/workflows/scan.md": "003883d71c37da7d",
|
||||
"gsd-core/workflows/secure-phase.md": "22ab3bb5da494ea7",
|
||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
||||
@@ -399,7 +399,7 @@
|
||||
"hooks/gsd-read-guard.js": "9e423cd03e2d1b16",
|
||||
"hooks/gsd-read-injection-scanner.js": "c8800819f7443a15",
|
||||
"hooks/gsd-session-state.sh": "e54379ba86bf1b6d",
|
||||
"hooks/gsd-statusline.js": "3be32d2012c77fc1",
|
||||
"hooks/gsd-statusline.js": "3e633cd082663b99",
|
||||
"hooks/gsd-update-banner.js": "55143a25f978f301",
|
||||
"hooks/gsd-validate-commit.sh": "bf5dd61d33cb3a38",
|
||||
"hooks/gsd-windsurf-pre-command.js": "948be1c6d14c79cd",
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"agents/gsd-domain-researcher.md": "f1e03df842ddfb95",
|
||||
"agents/gsd-eval-auditor.md": "d0f45fff7370bb0b",
|
||||
"agents/gsd-eval-planner.md": "9cc049b82897daa4",
|
||||
"agents/gsd-executor.md": "bf1de739df0c9245",
|
||||
"agents/gsd-executor.md": "62f37fae936e0d2f",
|
||||
"agents/gsd-framework-selector.md": "85005d716f9d98f7",
|
||||
"agents/gsd-integration-checker.md": "17a8ee731986564d",
|
||||
"agents/gsd-intel-updater.md": "4953a465db9dadc1",
|
||||
@@ -111,7 +111,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "37fb6081fcb18038",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "b67705431d738ae7",
|
||||
"gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -125,7 +125,7 @@
|
||||
"gsd-core/references/api-coverage.md": "205a43c5fa7c221c",
|
||||
"gsd-core/references/artifact-types.md": "251040866a3a1818",
|
||||
"gsd-core/references/autonomous-smart-discuss.md": "2fc710cde0ec7785",
|
||||
"gsd-core/references/checkpoints.md": "6aa620c6ca38bdf0",
|
||||
"gsd-core/references/checkpoints.md": "b1530628d693fc85",
|
||||
"gsd-core/references/common-bug-patterns.md": "780145be56352626",
|
||||
"gsd-core/references/context-budget.md": "6bfac08025ea3106",
|
||||
"gsd-core/references/continuation-format.md": "580287399ad3ba68",
|
||||
@@ -303,7 +303,7 @@
|
||||
"gsd-core/workflows/docs-update.md": "cd753783ab95da00",
|
||||
"gsd-core/workflows/edit-phase.md": "dbbb6191f5a8b65e",
|
||||
"gsd-core/workflows/eval-review.md": "086a1f2b3c11462c",
|
||||
"gsd-core/workflows/execute-phase.md": "d7d8ac751aa2915e",
|
||||
"gsd-core/workflows/execute-phase.md": "e5bc721629beaa01",
|
||||
"gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "6d38bfd540030da4",
|
||||
"gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028",
|
||||
"gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "611b2be3bd133eb1",
|
||||
@@ -355,7 +355,7 @@
|
||||
"gsd-core/workflows/remove-phase.md": "8effc8742d58a11a",
|
||||
"gsd-core/workflows/remove-workspace.md": "10882656198d9075",
|
||||
"gsd-core/workflows/resume-project.md": "af9761bcec0f6fe9",
|
||||
"gsd-core/workflows/review.md": "eec3a15bebb7fcf0",
|
||||
"gsd-core/workflows/review.md": "15b8defb2d791092",
|
||||
"gsd-core/workflows/scan.md": "75c670d08cee8680",
|
||||
"gsd-core/workflows/secure-phase.md": "8030d2b2a5bfdf07",
|
||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
||||
@@ -398,7 +398,7 @@
|
||||
"hooks/gsd-read-guard.js": "9e423cd03e2d1b16",
|
||||
"hooks/gsd-read-injection-scanner.js": "00d2449afefd2e5f",
|
||||
"hooks/gsd-session-state.sh": "e54379ba86bf1b6d",
|
||||
"hooks/gsd-statusline.js": "7c315416ffc99a9a",
|
||||
"hooks/gsd-statusline.js": "920558a55eb86587",
|
||||
"hooks/gsd-update-banner.js": "b457746cb76c1957",
|
||||
"hooks/gsd-validate-commit.sh": "bf5dd61d33cb3a38",
|
||||
"hooks/gsd-windsurf-pre-command.js": "948be1c6d14c79cd",
|
||||
|
||||
12
tests/fixtures/golden-install-parity/claude.json
vendored
12
tests/fixtures/golden-install-parity/claude.json
vendored
@@ -15,7 +15,7 @@
|
||||
"agents/gsd-domain-researcher.md": "5f7d366251b957fe",
|
||||
"agents/gsd-eval-auditor.md": "fea2759beff0a642",
|
||||
"agents/gsd-eval-planner.md": "112f6730f23854e3",
|
||||
"agents/gsd-executor.md": "22fc2f17098a4d14",
|
||||
"agents/gsd-executor.md": "d1750d26580daa7f",
|
||||
"agents/gsd-framework-selector.md": "c350ee693cb1aa4e",
|
||||
"agents/gsd-integration-checker.md": "c8b4e65dee89c8ea",
|
||||
"agents/gsd-intel-updater.md": "5b41e05f90ce89d9",
|
||||
@@ -40,7 +40,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "37fb6081fcb18038",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "b67705431d738ae7",
|
||||
"gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -54,7 +54,7 @@
|
||||
"gsd-core/references/api-coverage.md": "205a43c5fa7c221c",
|
||||
"gsd-core/references/artifact-types.md": "8bd01fd75a2ba70e",
|
||||
"gsd-core/references/autonomous-smart-discuss.md": "2fc710cde0ec7785",
|
||||
"gsd-core/references/checkpoints.md": "6aa620c6ca38bdf0",
|
||||
"gsd-core/references/checkpoints.md": "b1530628d693fc85",
|
||||
"gsd-core/references/common-bug-patterns.md": "780145be56352626",
|
||||
"gsd-core/references/context-budget.md": "533eae480bfc4bb8",
|
||||
"gsd-core/references/continuation-format.md": "580287399ad3ba68",
|
||||
@@ -232,7 +232,7 @@
|
||||
"gsd-core/workflows/docs-update.md": "63082608d3ae92be",
|
||||
"gsd-core/workflows/edit-phase.md": "8323bfe10faa0c0a",
|
||||
"gsd-core/workflows/eval-review.md": "f59e8329dae1e528",
|
||||
"gsd-core/workflows/execute-phase.md": "4aa35e8cb9d68cde",
|
||||
"gsd-core/workflows/execute-phase.md": "5231186012da87a2",
|
||||
"gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "facb0e816d87a0c7",
|
||||
"gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028",
|
||||
"gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "811b6d8489571581",
|
||||
@@ -284,7 +284,7 @@
|
||||
"gsd-core/workflows/remove-phase.md": "ada8a0546c686483",
|
||||
"gsd-core/workflows/remove-workspace.md": "f3ab3a88a7e9e1ed",
|
||||
"gsd-core/workflows/resume-project.md": "7f8dc986f0f35d96",
|
||||
"gsd-core/workflows/review.md": "b0baf1dafebe3821",
|
||||
"gsd-core/workflows/review.md": "f1dbb3769d461ec2",
|
||||
"gsd-core/workflows/scan.md": "47371c2073d6c0be",
|
||||
"gsd-core/workflows/secure-phase.md": "d6ac1f4db6a5da75",
|
||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
||||
@@ -327,7 +327,7 @@
|
||||
"hooks/gsd-read-guard.js": "9e423cd03e2d1b16",
|
||||
"hooks/gsd-read-injection-scanner.js": "00d2449afefd2e5f",
|
||||
"hooks/gsd-session-state.sh": "e54379ba86bf1b6d",
|
||||
"hooks/gsd-statusline.js": "7c315416ffc99a9a",
|
||||
"hooks/gsd-statusline.js": "920558a55eb86587",
|
||||
"hooks/gsd-update-banner.js": "b457746cb76c1957",
|
||||
"hooks/gsd-validate-commit.sh": "bf5dd61d33cb3a38",
|
||||
"hooks/gsd-windsurf-pre-command.js": "948be1c6d14c79cd",
|
||||
|
||||
10
tests/fixtures/golden-install-parity/cline.json
vendored
10
tests/fixtures/golden-install-parity/cline.json
vendored
@@ -19,7 +19,7 @@
|
||||
"agents/gsd-domain-researcher.md": "0fecdaea86466a56",
|
||||
"agents/gsd-eval-auditor.md": "36c44303085df2f8",
|
||||
"agents/gsd-eval-planner.md": "3ddea88a69b4da3f",
|
||||
"agents/gsd-executor.md": "441e624e9685e505",
|
||||
"agents/gsd-executor.md": "2ca77ad7d9909f82",
|
||||
"agents/gsd-framework-selector.md": "564669d479433f15",
|
||||
"agents/gsd-integration-checker.md": "1bbbdd3d420b994e",
|
||||
"agents/gsd-intel-updater.md": "42c40fffbc720d0b",
|
||||
@@ -44,7 +44,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "a84914f7cab74332",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "b67705431d738ae7",
|
||||
"gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -58,7 +58,7 @@
|
||||
"gsd-core/references/api-coverage.md": "66264d41dfd9154a",
|
||||
"gsd-core/references/artifact-types.md": "a6d2e1f9453ffbf5",
|
||||
"gsd-core/references/autonomous-smart-discuss.md": "2fc710cde0ec7785",
|
||||
"gsd-core/references/checkpoints.md": "9a7ba3a17ece1698",
|
||||
"gsd-core/references/checkpoints.md": "4919e8aa1130fb04",
|
||||
"gsd-core/references/common-bug-patterns.md": "780145be56352626",
|
||||
"gsd-core/references/context-budget.md": "c154ba00dbbf4477",
|
||||
"gsd-core/references/continuation-format.md": "ce73e57d053ab8a7",
|
||||
@@ -236,7 +236,7 @@
|
||||
"gsd-core/workflows/docs-update.md": "39f288623a8f6f32",
|
||||
"gsd-core/workflows/edit-phase.md": "9c9fadc047c61d74",
|
||||
"gsd-core/workflows/eval-review.md": "3e1d7829ed2ed494",
|
||||
"gsd-core/workflows/execute-phase.md": "ae12e044fb2f3b57",
|
||||
"gsd-core/workflows/execute-phase.md": "0d9ff2faecb72225",
|
||||
"gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "67ebc93f51968cb6",
|
||||
"gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028",
|
||||
"gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "82e6cfe1e1b1ec0e",
|
||||
@@ -288,7 +288,7 @@
|
||||
"gsd-core/workflows/remove-phase.md": "e336350f8113a328",
|
||||
"gsd-core/workflows/remove-workspace.md": "e685dfbd736dfd90",
|
||||
"gsd-core/workflows/resume-project.md": "e23981178fa37b3d",
|
||||
"gsd-core/workflows/review.md": "6c689f4ff8146d28",
|
||||
"gsd-core/workflows/review.md": "8c996dc10e923685",
|
||||
"gsd-core/workflows/scan.md": "dfd92717caea0ce7",
|
||||
"gsd-core/workflows/secure-phase.md": "00de56d6d993bb2c",
|
||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"agents/gsd-domain-researcher.md": "1c1a800108a2b225",
|
||||
"agents/gsd-eval-auditor.md": "99012004b14ea602",
|
||||
"agents/gsd-eval-planner.md": "4ebdd7fe9cbb0cfe",
|
||||
"agents/gsd-executor.md": "d0bfc88c01a63181",
|
||||
"agents/gsd-executor.md": "d61e084540bb6ee2",
|
||||
"agents/gsd-framework-selector.md": "7726fccc86bfeb50",
|
||||
"agents/gsd-integration-checker.md": "2d8339790bbb2dc3",
|
||||
"agents/gsd-intel-updater.md": "c51339956197cbd3",
|
||||
@@ -112,7 +112,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "37fb6081fcb18038",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "b67705431d738ae7",
|
||||
"gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -126,7 +126,7 @@
|
||||
"gsd-core/references/api-coverage.md": "66264d41dfd9154a",
|
||||
"gsd-core/references/artifact-types.md": "a6d2e1f9453ffbf5",
|
||||
"gsd-core/references/autonomous-smart-discuss.md": "2fc710cde0ec7785",
|
||||
"gsd-core/references/checkpoints.md": "6aa620c6ca38bdf0",
|
||||
"gsd-core/references/checkpoints.md": "b1530628d693fc85",
|
||||
"gsd-core/references/common-bug-patterns.md": "780145be56352626",
|
||||
"gsd-core/references/context-budget.md": "f1ce57bf418824af",
|
||||
"gsd-core/references/continuation-format.md": "ce73e57d053ab8a7",
|
||||
@@ -304,7 +304,7 @@
|
||||
"gsd-core/workflows/docs-update.md": "f35922d15b7061c9",
|
||||
"gsd-core/workflows/edit-phase.md": "966a3eadd1bebc04",
|
||||
"gsd-core/workflows/eval-review.md": "f898936e2cfe4130",
|
||||
"gsd-core/workflows/execute-phase.md": "cf72b6db051f9189",
|
||||
"gsd-core/workflows/execute-phase.md": "f17719f6b780cd2b",
|
||||
"gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "4e265392b3f2ba0e",
|
||||
"gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028",
|
||||
"gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "811b6d8489571581",
|
||||
@@ -356,7 +356,7 @@
|
||||
"gsd-core/workflows/remove-phase.md": "df9a45f0b1880999",
|
||||
"gsd-core/workflows/remove-workspace.md": "a7ca66db6b7c132c",
|
||||
"gsd-core/workflows/resume-project.md": "f28da1200e4545f4",
|
||||
"gsd-core/workflows/review.md": "ad7c0f372ed986ae",
|
||||
"gsd-core/workflows/review.md": "3ce57633978035ad",
|
||||
"gsd-core/workflows/scan.md": "003883d71c37da7d",
|
||||
"gsd-core/workflows/secure-phase.md": "22ab3bb5da494ea7",
|
||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
||||
@@ -399,7 +399,7 @@
|
||||
"hooks/gsd-read-guard.js": "9e423cd03e2d1b16",
|
||||
"hooks/gsd-read-injection-scanner.js": "7f7a7615b303369a",
|
||||
"hooks/gsd-session-state.sh": "e54379ba86bf1b6d",
|
||||
"hooks/gsd-statusline.js": "ef8dcb6d64fd4493",
|
||||
"hooks/gsd-statusline.js": "6ab2905537f885ba",
|
||||
"hooks/gsd-update-banner.js": "55143a25f978f301",
|
||||
"hooks/gsd-validate-commit.sh": "bf5dd61d33cb3a38",
|
||||
"hooks/gsd-windsurf-pre-command.js": "948be1c6d14c79cd",
|
||||
|
||||
12
tests/fixtures/golden-install-parity/codex.json
vendored
12
tests/fixtures/golden-install-parity/codex.json
vendored
@@ -102,8 +102,8 @@
|
||||
"agents/gsd-eval-auditor.toml": "9b81d61b3c5f722d",
|
||||
"agents/gsd-eval-planner.md": "73f2ad2ff2797a51",
|
||||
"agents/gsd-eval-planner.toml": "09468ad1a34ac468",
|
||||
"agents/gsd-executor.md": "93a05c1436bc4458",
|
||||
"agents/gsd-executor.toml": "f5a55d4eacd8613c",
|
||||
"agents/gsd-executor.md": "0393e5f72e932127",
|
||||
"agents/gsd-executor.toml": "cdb7bdc04d3b6651",
|
||||
"agents/gsd-framework-selector.md": "ebae32430887d2e0",
|
||||
"agents/gsd-framework-selector.toml": "637e4e021b7ec380",
|
||||
"agents/gsd-integration-checker.md": "9cc875676cf7d741",
|
||||
@@ -147,7 +147,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "37fb6081fcb18038",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "b67705431d738ae7",
|
||||
"gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -161,7 +161,7 @@
|
||||
"gsd-core/references/api-coverage.md": "524382216a8e713f",
|
||||
"gsd-core/references/artifact-types.md": "3218cafb0c92dc32",
|
||||
"gsd-core/references/autonomous-smart-discuss.md": "4156025334411073",
|
||||
"gsd-core/references/checkpoints.md": "9feb961f644afa96",
|
||||
"gsd-core/references/checkpoints.md": "afc6933d99f73358",
|
||||
"gsd-core/references/common-bug-patterns.md": "780145be56352626",
|
||||
"gsd-core/references/context-budget.md": "0b67ea1d5db4bc08",
|
||||
"gsd-core/references/continuation-format.md": "e64c0da2b3d0f2f0",
|
||||
@@ -339,7 +339,7 @@
|
||||
"gsd-core/workflows/docs-update.md": "e255317df939e302",
|
||||
"gsd-core/workflows/edit-phase.md": "e592a4d85ce5380f",
|
||||
"gsd-core/workflows/eval-review.md": "63d0d0670b54c244",
|
||||
"gsd-core/workflows/execute-phase.md": "e62b664cf4db4d55",
|
||||
"gsd-core/workflows/execute-phase.md": "b15bbe2f8a8d8d02",
|
||||
"gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "f4cacd27d37bac65",
|
||||
"gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028",
|
||||
"gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "811b6d8489571581",
|
||||
@@ -391,7 +391,7 @@
|
||||
"gsd-core/workflows/remove-phase.md": "9ee0fddd11a0d9d4",
|
||||
"gsd-core/workflows/remove-workspace.md": "19d7465aaa50cb62",
|
||||
"gsd-core/workflows/resume-project.md": "9965f87eb278f7f8",
|
||||
"gsd-core/workflows/review.md": "5faef3f4feb45c99",
|
||||
"gsd-core/workflows/review.md": "a980007626185b70",
|
||||
"gsd-core/workflows/scan.md": "1a3caa5d724d39e9",
|
||||
"gsd-core/workflows/secure-phase.md": "ab387a4bca381c18",
|
||||
"gsd-core/workflows/session-report.md": "dd8fa011c9394075",
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"agents/gsd-domain-researcher.agent.md": "d603239b3e9fe428",
|
||||
"agents/gsd-eval-auditor.agent.md": "3c03009564de55c8",
|
||||
"agents/gsd-eval-planner.agent.md": "14751876fc2b5f16",
|
||||
"agents/gsd-executor.agent.md": "0058082fef5a4985",
|
||||
"agents/gsd-executor.agent.md": "2ee1020062d8d800",
|
||||
"agents/gsd-framework-selector.agent.md": "cafeec0b3489be45",
|
||||
"agents/gsd-integration-checker.agent.md": "30439b804927acc7",
|
||||
"agents/gsd-intel-updater.agent.md": "238c1a886f35a25c",
|
||||
@@ -42,7 +42,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "600e9ed0cb6ed7bb",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "b67705431d738ae7",
|
||||
"gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "10226e9512dd44bf",
|
||||
@@ -56,7 +56,7 @@
|
||||
"gsd-core/references/api-coverage.md": "205a43c5fa7c221c",
|
||||
"gsd-core/references/artifact-types.md": "f992de8b2b1a4420",
|
||||
"gsd-core/references/autonomous-smart-discuss.md": "efd80aca449032ad",
|
||||
"gsd-core/references/checkpoints.md": "c70b323dcb1583d5",
|
||||
"gsd-core/references/checkpoints.md": "53ca1c205dda8b65",
|
||||
"gsd-core/references/common-bug-patterns.md": "780145be56352626",
|
||||
"gsd-core/references/context-budget.md": "435474d5e10be65a",
|
||||
"gsd-core/references/continuation-format.md": "580287399ad3ba68",
|
||||
@@ -234,7 +234,7 @@
|
||||
"gsd-core/workflows/docs-update.md": "9292cfa3c52c434e",
|
||||
"gsd-core/workflows/edit-phase.md": "8667c28b22b1599f",
|
||||
"gsd-core/workflows/eval-review.md": "81c8e72ba3862856",
|
||||
"gsd-core/workflows/execute-phase.md": "6f7aa92705d3527e",
|
||||
"gsd-core/workflows/execute-phase.md": "80a1e0722f72dbb1",
|
||||
"gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "63b712920f21a40f",
|
||||
"gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028",
|
||||
"gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "1b73ab2fc47c9dbf",
|
||||
@@ -286,7 +286,7 @@
|
||||
"gsd-core/workflows/remove-phase.md": "e262654e319d1bc4",
|
||||
"gsd-core/workflows/remove-workspace.md": "ceddfeef5f2d6754",
|
||||
"gsd-core/workflows/resume-project.md": "40db7f350f5866d8",
|
||||
"gsd-core/workflows/review.md": "4b649f31865a1785",
|
||||
"gsd-core/workflows/review.md": "1f44a33c50b1cd36",
|
||||
"gsd-core/workflows/scan.md": "dcc2f76d0850e2fb",
|
||||
"gsd-core/workflows/secure-phase.md": "9bec6635ee1cbaed",
|
||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
||||
|
||||
10
tests/fixtures/golden-install-parity/cursor.json
vendored
10
tests/fixtures/golden-install-parity/cursor.json
vendored
@@ -16,7 +16,7 @@
|
||||
"agents/gsd-domain-researcher.md": "56395dbdabf076f6",
|
||||
"agents/gsd-eval-auditor.md": "ad2840fd5cd76172",
|
||||
"agents/gsd-eval-planner.md": "2049dac060d00eda",
|
||||
"agents/gsd-executor.md": "1fb2c778178cfa1c",
|
||||
"agents/gsd-executor.md": "e416f9c48fb44c75",
|
||||
"agents/gsd-framework-selector.md": "4b77eebbe9288d80",
|
||||
"agents/gsd-integration-checker.md": "5da30584d06b878c",
|
||||
"agents/gsd-intel-updater.md": "b8971c5d96e63b38",
|
||||
@@ -112,7 +112,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "db16ded31dd6eecf",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "b67705431d738ae7",
|
||||
"gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -126,7 +126,7 @@
|
||||
"gsd-core/references/api-coverage.md": "205a43c5fa7c221c",
|
||||
"gsd-core/references/artifact-types.md": "8bd01fd75a2ba70e",
|
||||
"gsd-core/references/autonomous-smart-discuss.md": "273b371c5751f35a",
|
||||
"gsd-core/references/checkpoints.md": "3001eeccb319781b",
|
||||
"gsd-core/references/checkpoints.md": "f852c96c5fe25015",
|
||||
"gsd-core/references/common-bug-patterns.md": "780145be56352626",
|
||||
"gsd-core/references/context-budget.md": "304dcdab82a27623",
|
||||
"gsd-core/references/continuation-format.md": "580287399ad3ba68",
|
||||
@@ -304,7 +304,7 @@
|
||||
"gsd-core/workflows/docs-update.md": "e86d7d7e2e3dac6d",
|
||||
"gsd-core/workflows/edit-phase.md": "8323bfe10faa0c0a",
|
||||
"gsd-core/workflows/eval-review.md": "a86279dd98dd5c03",
|
||||
"gsd-core/workflows/execute-phase.md": "cf1b15f505b519d8",
|
||||
"gsd-core/workflows/execute-phase.md": "b3267f23af6444b2",
|
||||
"gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "facb0e816d87a0c7",
|
||||
"gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028",
|
||||
"gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "811b6d8489571581",
|
||||
@@ -356,7 +356,7 @@
|
||||
"gsd-core/workflows/remove-phase.md": "ada8a0546c686483",
|
||||
"gsd-core/workflows/remove-workspace.md": "433affcd1a200826",
|
||||
"gsd-core/workflows/resume-project.md": "7f8dc986f0f35d96",
|
||||
"gsd-core/workflows/review.md": "3ba8bb85c8ede5b8",
|
||||
"gsd-core/workflows/review.md": "ad115fadce422eae",
|
||||
"gsd-core/workflows/scan.md": "47371c2073d6c0be",
|
||||
"gsd-core/workflows/secure-phase.md": "3063b0b6f7b56d46",
|
||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
||||
|
||||
12
tests/fixtures/golden-install-parity/hermes.json
vendored
12
tests/fixtures/golden-install-parity/hermes.json
vendored
@@ -16,7 +16,7 @@
|
||||
"agents/gsd-domain-researcher.md": "412cdbb05ba252ea",
|
||||
"agents/gsd-eval-auditor.md": "4ffb265063c318e5",
|
||||
"agents/gsd-eval-planner.md": "03448fc9c5774b56",
|
||||
"agents/gsd-executor.md": "18365948be7c2dff",
|
||||
"agents/gsd-executor.md": "9c00261ee92596bb",
|
||||
"agents/gsd-framework-selector.md": "ea9981d65d6b3429",
|
||||
"agents/gsd-integration-checker.md": "35b4f2969d279871",
|
||||
"agents/gsd-intel-updater.md": "5fe5edfae2719cb8",
|
||||
@@ -41,7 +41,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "b3f927ee4b4c4711",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "b67705431d738ae7",
|
||||
"gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -55,7 +55,7 @@
|
||||
"gsd-core/references/api-coverage.md": "205a43c5fa7c221c",
|
||||
"gsd-core/references/artifact-types.md": "8bd01fd75a2ba70e",
|
||||
"gsd-core/references/autonomous-smart-discuss.md": "2fc710cde0ec7785",
|
||||
"gsd-core/references/checkpoints.md": "db8a7425ed808e24",
|
||||
"gsd-core/references/checkpoints.md": "36b2de4768b228af",
|
||||
"gsd-core/references/common-bug-patterns.md": "780145be56352626",
|
||||
"gsd-core/references/context-budget.md": "b93e9f47aa1b1753",
|
||||
"gsd-core/references/continuation-format.md": "580287399ad3ba68",
|
||||
@@ -233,7 +233,7 @@
|
||||
"gsd-core/workflows/docs-update.md": "4d6c06e611d83b6d",
|
||||
"gsd-core/workflows/edit-phase.md": "7f27003f20e88fb8",
|
||||
"gsd-core/workflows/eval-review.md": "f510e5762212dc6f",
|
||||
"gsd-core/workflows/execute-phase.md": "f36c7b810285838c",
|
||||
"gsd-core/workflows/execute-phase.md": "371ab0a4dae7a049",
|
||||
"gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "ceb8758c22660c1a",
|
||||
"gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028",
|
||||
"gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "c9ad17d6cc6dfe45",
|
||||
@@ -285,7 +285,7 @@
|
||||
"gsd-core/workflows/remove-phase.md": "fce799aae3ab2715",
|
||||
"gsd-core/workflows/remove-workspace.md": "8facde381657dd71",
|
||||
"gsd-core/workflows/resume-project.md": "a0443839f1f83c2d",
|
||||
"gsd-core/workflows/review.md": "761dd1ae5be40613",
|
||||
"gsd-core/workflows/review.md": "d8c53e495b067afe",
|
||||
"gsd-core/workflows/scan.md": "b28f65d88c522767",
|
||||
"gsd-core/workflows/secure-phase.md": "a503dc469fd7a252",
|
||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
||||
@@ -328,7 +328,7 @@
|
||||
"hooks/gsd-read-guard.js": "1f58b020a91f032b",
|
||||
"hooks/gsd-read-injection-scanner.js": "f358eca3fa1eab24",
|
||||
"hooks/gsd-session-state.sh": "e54379ba86bf1b6d",
|
||||
"hooks/gsd-statusline.js": "861808560e60b233",
|
||||
"hooks/gsd-statusline.js": "34d2ae303558b917",
|
||||
"hooks/gsd-update-banner.js": "b457746cb76c1957",
|
||||
"hooks/gsd-validate-commit.sh": "bf5dd61d33cb3a38",
|
||||
"hooks/gsd-windsurf-pre-command.js": "948be1c6d14c79cd",
|
||||
|
||||
10
tests/fixtures/golden-install-parity/kilo.json
vendored
10
tests/fixtures/golden-install-parity/kilo.json
vendored
@@ -16,7 +16,7 @@
|
||||
"agents/gsd-domain-researcher.md": "a3874d80bcbc7380",
|
||||
"agents/gsd-eval-auditor.md": "630d4cd3bd6ea195",
|
||||
"agents/gsd-eval-planner.md": "3db12cde12aeb2c1",
|
||||
"agents/gsd-executor.md": "c9aca9129bd4cab3",
|
||||
"agents/gsd-executor.md": "24a1c8c8e829d2d1",
|
||||
"agents/gsd-framework-selector.md": "ad5f2c6b9bec6270",
|
||||
"agents/gsd-integration-checker.md": "c503e2f4a3d8ec05",
|
||||
"agents/gsd-intel-updater.md": "231393da62a45b2e",
|
||||
@@ -112,7 +112,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "37fb6081fcb18038",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "b67705431d738ae7",
|
||||
"gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -126,7 +126,7 @@
|
||||
"gsd-core/references/api-coverage.md": "205a43c5fa7c221c",
|
||||
"gsd-core/references/artifact-types.md": "8bd01fd75a2ba70e",
|
||||
"gsd-core/references/autonomous-smart-discuss.md": "3986d58011bf9006",
|
||||
"gsd-core/references/checkpoints.md": "9feb961f644afa96",
|
||||
"gsd-core/references/checkpoints.md": "afc6933d99f73358",
|
||||
"gsd-core/references/common-bug-patterns.md": "780145be56352626",
|
||||
"gsd-core/references/context-budget.md": "533eae480bfc4bb8",
|
||||
"gsd-core/references/continuation-format.md": "580287399ad3ba68",
|
||||
@@ -304,7 +304,7 @@
|
||||
"gsd-core/workflows/docs-update.md": "79afaaf19fd527cc",
|
||||
"gsd-core/workflows/edit-phase.md": "8323bfe10faa0c0a",
|
||||
"gsd-core/workflows/eval-review.md": "926eda8bbee28b23",
|
||||
"gsd-core/workflows/execute-phase.md": "ec448a1e25854abf",
|
||||
"gsd-core/workflows/execute-phase.md": "43c8c218f8933cc8",
|
||||
"gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "facb0e816d87a0c7",
|
||||
"gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028",
|
||||
"gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "811b6d8489571581",
|
||||
@@ -356,7 +356,7 @@
|
||||
"gsd-core/workflows/remove-phase.md": "ada8a0546c686483",
|
||||
"gsd-core/workflows/remove-workspace.md": "fc83f362a2d0a1b7",
|
||||
"gsd-core/workflows/resume-project.md": "7f8dc986f0f35d96",
|
||||
"gsd-core/workflows/review.md": "f8109b9ec1f56962",
|
||||
"gsd-core/workflows/review.md": "3d62fa3096f46ecb",
|
||||
"gsd-core/workflows/scan.md": "47371c2073d6c0be",
|
||||
"gsd-core/workflows/secure-phase.md": "4977cf9e0462745b",
|
||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
||||
|
||||
12
tests/fixtures/golden-install-parity/kimi.json
vendored
12
tests/fixtures/golden-install-parity/kimi.json
vendored
@@ -18,7 +18,7 @@
|
||||
".kimi/hooks/gsd-read-guard.js": "9e423cd03e2d1b16",
|
||||
".kimi/hooks/gsd-read-injection-scanner.js": "c519598b9257aafa",
|
||||
".kimi/hooks/gsd-session-state.sh": "e54379ba86bf1b6d",
|
||||
".kimi/hooks/gsd-statusline.js": "2736b0885aa97bbf",
|
||||
".kimi/hooks/gsd-statusline.js": "ae0c0e7a934c5deb",
|
||||
".kimi/hooks/gsd-update-banner.js": "55143a25f978f301",
|
||||
".kimi/hooks/gsd-validate-commit.sh": "bf5dd61d33cb3a38",
|
||||
".kimi/hooks/gsd-windsurf-pre-command.js": "948be1c6d14c79cd",
|
||||
@@ -61,7 +61,7 @@
|
||||
"agents/subagents/gsd-eval-auditor.yaml": "e3d868bd5fefe938",
|
||||
"agents/subagents/gsd-eval-planner.md": "70f8c5727bfb9876",
|
||||
"agents/subagents/gsd-eval-planner.yaml": "df8499f7af297ec2",
|
||||
"agents/subagents/gsd-executor.md": "278a569a7306244c",
|
||||
"agents/subagents/gsd-executor.md": "d3b2806c227c6a27",
|
||||
"agents/subagents/gsd-executor.yaml": "e29422986636fd64",
|
||||
"agents/subagents/gsd-framework-selector.md": "a15b7aa1e0576e16",
|
||||
"agents/subagents/gsd-framework-selector.yaml": "fb52c31cde27b0e3",
|
||||
@@ -105,7 +105,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "37fb6081fcb18038",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "b67705431d738ae7",
|
||||
"gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -119,7 +119,7 @@
|
||||
"gsd-core/references/api-coverage.md": "66264d41dfd9154a",
|
||||
"gsd-core/references/artifact-types.md": "a6d2e1f9453ffbf5",
|
||||
"gsd-core/references/autonomous-smart-discuss.md": "2fc710cde0ec7785",
|
||||
"gsd-core/references/checkpoints.md": "6aa620c6ca38bdf0",
|
||||
"gsd-core/references/checkpoints.md": "b1530628d693fc85",
|
||||
"gsd-core/references/common-bug-patterns.md": "780145be56352626",
|
||||
"gsd-core/references/context-budget.md": "f1ce57bf418824af",
|
||||
"gsd-core/references/continuation-format.md": "ce73e57d053ab8a7",
|
||||
@@ -297,7 +297,7 @@
|
||||
"gsd-core/workflows/docs-update.md": "f35922d15b7061c9",
|
||||
"gsd-core/workflows/edit-phase.md": "966a3eadd1bebc04",
|
||||
"gsd-core/workflows/eval-review.md": "f898936e2cfe4130",
|
||||
"gsd-core/workflows/execute-phase.md": "91fe17be2d178f24",
|
||||
"gsd-core/workflows/execute-phase.md": "39def0423fb4eb45",
|
||||
"gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "4e265392b3f2ba0e",
|
||||
"gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028",
|
||||
"gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "811b6d8489571581",
|
||||
@@ -349,7 +349,7 @@
|
||||
"gsd-core/workflows/remove-phase.md": "df9a45f0b1880999",
|
||||
"gsd-core/workflows/remove-workspace.md": "a7ca66db6b7c132c",
|
||||
"gsd-core/workflows/resume-project.md": "f28da1200e4545f4",
|
||||
"gsd-core/workflows/review.md": "ad7c0f372ed986ae",
|
||||
"gsd-core/workflows/review.md": "3ce57633978035ad",
|
||||
"gsd-core/workflows/scan.md": "003883d71c37da7d",
|
||||
"gsd-core/workflows/secure-phase.md": "22ab3bb5da494ea7",
|
||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"agents/gsd-domain-researcher.md": "71250e759ca9e723",
|
||||
"agents/gsd-eval-auditor.md": "c88890105f32ace6",
|
||||
"agents/gsd-eval-planner.md": "60bddb70a937f796",
|
||||
"agents/gsd-executor.md": "b091b0126895ae03",
|
||||
"agents/gsd-executor.md": "cad2c28464d535df",
|
||||
"agents/gsd-framework-selector.md": "1c0a10355e787675",
|
||||
"agents/gsd-integration-checker.md": "a9de5928e5a5c649",
|
||||
"agents/gsd-intel-updater.md": "493e07482fa6198a",
|
||||
@@ -112,7 +112,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "37fb6081fcb18038",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "b67705431d738ae7",
|
||||
"gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -126,7 +126,7 @@
|
||||
"gsd-core/references/api-coverage.md": "205a43c5fa7c221c",
|
||||
"gsd-core/references/artifact-types.md": "218c55caf8aff6df",
|
||||
"gsd-core/references/autonomous-smart-discuss.md": "3986d58011bf9006",
|
||||
"gsd-core/references/checkpoints.md": "9feb961f644afa96",
|
||||
"gsd-core/references/checkpoints.md": "afc6933d99f73358",
|
||||
"gsd-core/references/common-bug-patterns.md": "780145be56352626",
|
||||
"gsd-core/references/context-budget.md": "355826e667f9ccd1",
|
||||
"gsd-core/references/continuation-format.md": "580287399ad3ba68",
|
||||
@@ -304,7 +304,7 @@
|
||||
"gsd-core/workflows/docs-update.md": "850366c2ef8fb780",
|
||||
"gsd-core/workflows/edit-phase.md": "1876c855fb0a0a39",
|
||||
"gsd-core/workflows/eval-review.md": "5394694d29ad7543",
|
||||
"gsd-core/workflows/execute-phase.md": "9026c312d564381f",
|
||||
"gsd-core/workflows/execute-phase.md": "2a1c33e0bda26211",
|
||||
"gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "1804215577f1ad35",
|
||||
"gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028",
|
||||
"gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "baa2c401af10a80a",
|
||||
@@ -356,7 +356,7 @@
|
||||
"gsd-core/workflows/remove-phase.md": "dea4661e8f89596f",
|
||||
"gsd-core/workflows/remove-workspace.md": "446847e71aa52504",
|
||||
"gsd-core/workflows/resume-project.md": "ad9f06a10bab8cc0",
|
||||
"gsd-core/workflows/review.md": "2d28a6683ff587de",
|
||||
"gsd-core/workflows/review.md": "98e441bde98a9d92",
|
||||
"gsd-core/workflows/scan.md": "ad8ebcad4626d4a8",
|
||||
"gsd-core/workflows/secure-phase.md": "71e6e689e80288ec",
|
||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
||||
@@ -399,7 +399,7 @@
|
||||
"hooks/gsd-read-guard.js": "9e423cd03e2d1b16",
|
||||
"hooks/gsd-read-injection-scanner.js": "f72060dfe035f706",
|
||||
"hooks/gsd-session-state.sh": "e54379ba86bf1b6d",
|
||||
"hooks/gsd-statusline.js": "9c132b5985800462",
|
||||
"hooks/gsd-statusline.js": "3dd40389fa786dd6",
|
||||
"hooks/gsd-update-banner.js": "55143a25f978f301",
|
||||
"hooks/gsd-validate-commit.sh": "bf5dd61d33cb3a38",
|
||||
"hooks/gsd-windsurf-pre-command.js": "948be1c6d14c79cd",
|
||||
|
||||
10
tests/fixtures/golden-install-parity/pi.json
vendored
10
tests/fixtures/golden-install-parity/pi.json
vendored
@@ -8,7 +8,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "37fb6081fcb18038",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "b67705431d738ae7",
|
||||
"gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -22,7 +22,7 @@
|
||||
"gsd-core/references/api-coverage.md": "66264d41dfd9154a",
|
||||
"gsd-core/references/artifact-types.md": "a6d2e1f9453ffbf5",
|
||||
"gsd-core/references/autonomous-smart-discuss.md": "2fc710cde0ec7785",
|
||||
"gsd-core/references/checkpoints.md": "6aa620c6ca38bdf0",
|
||||
"gsd-core/references/checkpoints.md": "b1530628d693fc85",
|
||||
"gsd-core/references/common-bug-patterns.md": "780145be56352626",
|
||||
"gsd-core/references/context-budget.md": "f1ce57bf418824af",
|
||||
"gsd-core/references/continuation-format.md": "ce73e57d053ab8a7",
|
||||
@@ -200,7 +200,7 @@
|
||||
"gsd-core/workflows/docs-update.md": "f35922d15b7061c9",
|
||||
"gsd-core/workflows/edit-phase.md": "966a3eadd1bebc04",
|
||||
"gsd-core/workflows/eval-review.md": "f898936e2cfe4130",
|
||||
"gsd-core/workflows/execute-phase.md": "8f6c2443655aceb1",
|
||||
"gsd-core/workflows/execute-phase.md": "8f6dc95cc8030259",
|
||||
"gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "4e265392b3f2ba0e",
|
||||
"gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028",
|
||||
"gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "811b6d8489571581",
|
||||
@@ -252,7 +252,7 @@
|
||||
"gsd-core/workflows/remove-phase.md": "df9a45f0b1880999",
|
||||
"gsd-core/workflows/remove-workspace.md": "a7ca66db6b7c132c",
|
||||
"gsd-core/workflows/resume-project.md": "f28da1200e4545f4",
|
||||
"gsd-core/workflows/review.md": "ad7c0f372ed986ae",
|
||||
"gsd-core/workflows/review.md": "3ce57633978035ad",
|
||||
"gsd-core/workflows/scan.md": "003883d71c37da7d",
|
||||
"gsd-core/workflows/secure-phase.md": "22ab3bb5da494ea7",
|
||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
||||
@@ -295,7 +295,7 @@
|
||||
"hooks/gsd-read-guard.js": "9e423cd03e2d1b16",
|
||||
"hooks/gsd-read-injection-scanner.js": "f454242c010804cf",
|
||||
"hooks/gsd-session-state.sh": "e54379ba86bf1b6d",
|
||||
"hooks/gsd-statusline.js": "5539e1ae859b987e",
|
||||
"hooks/gsd-statusline.js": "41b0080cf0f3e7e9",
|
||||
"hooks/gsd-update-banner.js": "55143a25f978f301",
|
||||
"hooks/gsd-validate-commit.sh": "bf5dd61d33cb3a38",
|
||||
"hooks/gsd-windsurf-pre-command.js": "948be1c6d14c79cd",
|
||||
|
||||
12
tests/fixtures/golden-install-parity/qwen.json
vendored
12
tests/fixtures/golden-install-parity/qwen.json
vendored
@@ -16,7 +16,7 @@
|
||||
"agents/gsd-domain-researcher.md": "bd054bb27beed2a7",
|
||||
"agents/gsd-eval-auditor.md": "57cc7458ab5de6b7",
|
||||
"agents/gsd-eval-planner.md": "01b665728dde4ccf",
|
||||
"agents/gsd-executor.md": "b50d53e45df6431c",
|
||||
"agents/gsd-executor.md": "72c02583b569a25f",
|
||||
"agents/gsd-framework-selector.md": "82ba6abea84226b7",
|
||||
"agents/gsd-integration-checker.md": "90835dbc7dfa1691",
|
||||
"agents/gsd-intel-updater.md": "3cc4f6ddd04676ec",
|
||||
@@ -41,7 +41,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "6454021dec4d9563",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "b67705431d738ae7",
|
||||
"gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -55,7 +55,7 @@
|
||||
"gsd-core/references/api-coverage.md": "205a43c5fa7c221c",
|
||||
"gsd-core/references/artifact-types.md": "8bd01fd75a2ba70e",
|
||||
"gsd-core/references/autonomous-smart-discuss.md": "2fc710cde0ec7785",
|
||||
"gsd-core/references/checkpoints.md": "046171320f816346",
|
||||
"gsd-core/references/checkpoints.md": "83c7ac100419e9e0",
|
||||
"gsd-core/references/common-bug-patterns.md": "780145be56352626",
|
||||
"gsd-core/references/context-budget.md": "a1351dd40ef8691f",
|
||||
"gsd-core/references/continuation-format.md": "580287399ad3ba68",
|
||||
@@ -233,7 +233,7 @@
|
||||
"gsd-core/workflows/docs-update.md": "45d2f0d173c84e07",
|
||||
"gsd-core/workflows/edit-phase.md": "0fb5e0123cfc6f36",
|
||||
"gsd-core/workflows/eval-review.md": "6dee8a1e40ececd4",
|
||||
"gsd-core/workflows/execute-phase.md": "60deeb0a791c3cd5",
|
||||
"gsd-core/workflows/execute-phase.md": "b6d1f7dbc3c81a4f",
|
||||
"gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "36af8d91e4ae8b9c",
|
||||
"gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028",
|
||||
"gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "98db1ba4c39cd784",
|
||||
@@ -285,7 +285,7 @@
|
||||
"gsd-core/workflows/remove-phase.md": "e8ae4fbbfac700f0",
|
||||
"gsd-core/workflows/remove-workspace.md": "4ac64de862dc650e",
|
||||
"gsd-core/workflows/resume-project.md": "7f20769f302e5427",
|
||||
"gsd-core/workflows/review.md": "bcbc20cb8df021cc",
|
||||
"gsd-core/workflows/review.md": "7e1d8901d796338f",
|
||||
"gsd-core/workflows/scan.md": "949692db4834dd27",
|
||||
"gsd-core/workflows/secure-phase.md": "ef7b5ad194b687bf",
|
||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
||||
@@ -328,7 +328,7 @@
|
||||
"hooks/gsd-read-guard.js": "2c8d417d12b51040",
|
||||
"hooks/gsd-read-injection-scanner.js": "396574bd25e99ff9",
|
||||
"hooks/gsd-session-state.sh": "e54379ba86bf1b6d",
|
||||
"hooks/gsd-statusline.js": "739140996a3c0d49",
|
||||
"hooks/gsd-statusline.js": "5686cf31c721b2b1",
|
||||
"hooks/gsd-update-banner.js": "b457746cb76c1957",
|
||||
"hooks/gsd-validate-commit.sh": "bf5dd61d33cb3a38",
|
||||
"hooks/gsd-windsurf-pre-command.js": "948be1c6d14c79cd",
|
||||
|
||||
10
tests/fixtures/golden-install-parity/trae.json
vendored
10
tests/fixtures/golden-install-parity/trae.json
vendored
@@ -16,7 +16,7 @@
|
||||
"agents/gsd-domain-researcher.md": "b80f76874c04e515",
|
||||
"agents/gsd-eval-auditor.md": "470bf16303ec4d2e",
|
||||
"agents/gsd-eval-planner.md": "22334fde85723c9d",
|
||||
"agents/gsd-executor.md": "45eeb443a6e6dbe8",
|
||||
"agents/gsd-executor.md": "e5b96a52bd3c96c8",
|
||||
"agents/gsd-framework-selector.md": "7726fccc86bfeb50",
|
||||
"agents/gsd-integration-checker.md": "7cd2072984411c7f",
|
||||
"agents/gsd-intel-updater.md": "83de6ba9172891c3",
|
||||
@@ -41,7 +41,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "c28c9076058c5e73",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "b67705431d738ae7",
|
||||
"gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -55,7 +55,7 @@
|
||||
"gsd-core/references/api-coverage.md": "205a43c5fa7c221c",
|
||||
"gsd-core/references/artifact-types.md": "8bd01fd75a2ba70e",
|
||||
"gsd-core/references/autonomous-smart-discuss.md": "2fc710cde0ec7785",
|
||||
"gsd-core/references/checkpoints.md": "4b8c645ffa695067",
|
||||
"gsd-core/references/checkpoints.md": "610690dba4d600c1",
|
||||
"gsd-core/references/common-bug-patterns.md": "780145be56352626",
|
||||
"gsd-core/references/context-budget.md": "b7e9640063775c98",
|
||||
"gsd-core/references/continuation-format.md": "580287399ad3ba68",
|
||||
@@ -233,7 +233,7 @@
|
||||
"gsd-core/workflows/docs-update.md": "f13571f08e083356",
|
||||
"gsd-core/workflows/edit-phase.md": "7facd0faa33c8cad",
|
||||
"gsd-core/workflows/eval-review.md": "37d545d4f0db4927",
|
||||
"gsd-core/workflows/execute-phase.md": "aa5abb8c77a00153",
|
||||
"gsd-core/workflows/execute-phase.md": "80ffe75fda2a90ba",
|
||||
"gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "c985a30317a1aa6b",
|
||||
"gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028",
|
||||
"gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "0a9e915170c7121c",
|
||||
@@ -285,7 +285,7 @@
|
||||
"gsd-core/workflows/remove-phase.md": "a46c2fe853bf4e86",
|
||||
"gsd-core/workflows/remove-workspace.md": "ae0e1c6d4438d663",
|
||||
"gsd-core/workflows/resume-project.md": "f242e4c8aba18ea2",
|
||||
"gsd-core/workflows/review.md": "835cf8c9594f17c1",
|
||||
"gsd-core/workflows/review.md": "5fd9c6a9d7777eee",
|
||||
"gsd-core/workflows/scan.md": "63631467651d9ca8",
|
||||
"gsd-core/workflows/secure-phase.md": "4a647aec1e4d2dfe",
|
||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"agents/gsd-domain-researcher.md": "56395dbdabf076f6",
|
||||
"agents/gsd-eval-auditor.md": "fb64fc5acf359747",
|
||||
"agents/gsd-eval-planner.md": "2049dac060d00eda",
|
||||
"agents/gsd-executor.md": "406fdcc0597bea77",
|
||||
"agents/gsd-executor.md": "1eb9f58c5e9da94d",
|
||||
"agents/gsd-framework-selector.md": "4b77eebbe9288d80",
|
||||
"agents/gsd-integration-checker.md": "4ffb37fb230c2b90",
|
||||
"agents/gsd-intel-updater.md": "a81d77c143c02108",
|
||||
@@ -41,7 +41,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "568e7c6bdd415c1a",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "b67705431d738ae7",
|
||||
"gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -55,7 +55,7 @@
|
||||
"gsd-core/references/api-coverage.md": "205a43c5fa7c221c",
|
||||
"gsd-core/references/artifact-types.md": "8bd01fd75a2ba70e",
|
||||
"gsd-core/references/autonomous-smart-discuss.md": "273b371c5751f35a",
|
||||
"gsd-core/references/checkpoints.md": "808e4fcaa2fda15c",
|
||||
"gsd-core/references/checkpoints.md": "de9f3b7bd86a44ac",
|
||||
"gsd-core/references/common-bug-patterns.md": "780145be56352626",
|
||||
"gsd-core/references/context-budget.md": "ff843cd6139c8564",
|
||||
"gsd-core/references/continuation-format.md": "580287399ad3ba68",
|
||||
@@ -233,7 +233,7 @@
|
||||
"gsd-core/workflows/docs-update.md": "70f73cc8c27e0ca0",
|
||||
"gsd-core/workflows/edit-phase.md": "c0ae7d0063f3e789",
|
||||
"gsd-core/workflows/eval-review.md": "b28be79ef29f16fd",
|
||||
"gsd-core/workflows/execute-phase.md": "1dac1bdda0696dc0",
|
||||
"gsd-core/workflows/execute-phase.md": "d6bf589fb0370bc7",
|
||||
"gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "47ae5482f8e64100",
|
||||
"gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028",
|
||||
"gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "15bca39a75c664be",
|
||||
@@ -285,7 +285,7 @@
|
||||
"gsd-core/workflows/remove-phase.md": "e7a6af429b36e77b",
|
||||
"gsd-core/workflows/remove-workspace.md": "b5e60fbb33b3e33a",
|
||||
"gsd-core/workflows/resume-project.md": "82cfe1b8cb17c085",
|
||||
"gsd-core/workflows/review.md": "3e72508a5dccd45a",
|
||||
"gsd-core/workflows/review.md": "a63e0b5bb3b73730",
|
||||
"gsd-core/workflows/scan.md": "12c11b2edc165df9",
|
||||
"gsd-core/workflows/secure-phase.md": "185a15d389951e6e",
|
||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
||||
|
||||
10
tests/fixtures/golden-install-parity/zcode.json
vendored
10
tests/fixtures/golden-install-parity/zcode.json
vendored
@@ -16,7 +16,7 @@
|
||||
"agents/gsd-domain-researcher.md": "049f588663814fa2",
|
||||
"agents/gsd-eval-auditor.md": "54870d3b07433525",
|
||||
"agents/gsd-eval-planner.md": "552e9fa164c51ce8",
|
||||
"agents/gsd-executor.md": "7bc50a045cb23811",
|
||||
"agents/gsd-executor.md": "fdd9635cee849c82",
|
||||
"agents/gsd-framework-selector.md": "8a795f230436ad2e",
|
||||
"agents/gsd-integration-checker.md": "c1760a0bbd4f7bf5",
|
||||
"agents/gsd-intel-updater.md": "944f1d903e2e9e09",
|
||||
@@ -112,7 +112,7 @@
|
||||
"gsd-core/bin/gsd-tools.cjs": "37fb6081fcb18038",
|
||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
|
||||
"gsd-core/bin/shared/config-schema.manifest.json": "b67705431d738ae7",
|
||||
"gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3",
|
||||
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||
@@ -126,7 +126,7 @@
|
||||
"gsd-core/references/api-coverage.md": "66264d41dfd9154a",
|
||||
"gsd-core/references/artifact-types.md": "a6d2e1f9453ffbf5",
|
||||
"gsd-core/references/autonomous-smart-discuss.md": "2fc710cde0ec7785",
|
||||
"gsd-core/references/checkpoints.md": "6aa620c6ca38bdf0",
|
||||
"gsd-core/references/checkpoints.md": "b1530628d693fc85",
|
||||
"gsd-core/references/common-bug-patterns.md": "780145be56352626",
|
||||
"gsd-core/references/context-budget.md": "f1ce57bf418824af",
|
||||
"gsd-core/references/continuation-format.md": "ce73e57d053ab8a7",
|
||||
@@ -304,7 +304,7 @@
|
||||
"gsd-core/workflows/docs-update.md": "f35922d15b7061c9",
|
||||
"gsd-core/workflows/edit-phase.md": "966a3eadd1bebc04",
|
||||
"gsd-core/workflows/eval-review.md": "f898936e2cfe4130",
|
||||
"gsd-core/workflows/execute-phase.md": "bb4f1120804c5a90",
|
||||
"gsd-core/workflows/execute-phase.md": "55c0e6f659e27ff2",
|
||||
"gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "4e265392b3f2ba0e",
|
||||
"gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028",
|
||||
"gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "811b6d8489571581",
|
||||
@@ -356,7 +356,7 @@
|
||||
"gsd-core/workflows/remove-phase.md": "df9a45f0b1880999",
|
||||
"gsd-core/workflows/remove-workspace.md": "a7ca66db6b7c132c",
|
||||
"gsd-core/workflows/resume-project.md": "f28da1200e4545f4",
|
||||
"gsd-core/workflows/review.md": "ad7c0f372ed986ae",
|
||||
"gsd-core/workflows/review.md": "3ce57633978035ad",
|
||||
"gsd-core/workflows/scan.md": "003883d71c37da7d",
|
||||
"gsd-core/workflows/secure-phase.md": "22ab3bb5da494ea7",
|
||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
||||
|
||||
@@ -1190,5 +1190,176 @@ test('config-set rejects invalid statusline.context_position', () => {
|
||||
cleanup(tmpDir);
|
||||
}
|
||||
});
|
||||
|
||||
// Same write-path enforcement for the boolean statusline.show_context_tokens
|
||||
// key (#2161) — mirrors the workflow.post_planning_gaps precedent the issue's
|
||||
// scope names (tests/post-planning-gaps-2493.test.cjs).
|
||||
test('config-set statusline.show_context_tokens true → persisted as boolean', () => {
|
||||
const tmpDir = createTempProject();
|
||||
try {
|
||||
const r = runGsdTools(['config-set', 'statusline.show_context_tokens', 'true'], tmpDir);
|
||||
assert.ok(r.success, r.error);
|
||||
const config = JSON.parse(
|
||||
fs.readFileSync(path.join(tmpDir, '.planning', 'config.json'), 'utf-8'));
|
||||
assert.strictEqual(config.statusline.show_context_tokens, true);
|
||||
} finally {
|
||||
cleanup(tmpDir);
|
||||
}
|
||||
});
|
||||
|
||||
test('config-set statusline.show_context_tokens yes → rejected', () => {
|
||||
const tmpDir = createTempProject();
|
||||
try {
|
||||
const r = runGsdTools(['config-set', 'statusline.show_context_tokens', 'yes'], tmpDir);
|
||||
assert.equal(r.success, false, 'non-boolean value must be rejected');
|
||||
assert.match(r.error || r.output, /boolean|true|false/i);
|
||||
} finally {
|
||||
cleanup(tmpDir);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────
|
||||
// Context meter token count (statusline.show_context_tokens)
|
||||
// ────────────────────────────────────────────────────────────────────────
|
||||
{
|
||||
const { test, describe } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const { execFileSync } = require('node:child_process');
|
||||
const { cleanup } = require('./helpers.cjs');
|
||||
const { formatTokens, contextTokenSuffix } = require('../hooks/gsd-statusline.js');
|
||||
const { VALID_CONFIG_KEYS } = require('../gsd-core/bin/lib/config-schema.cjs');
|
||||
|
||||
const hookPath = path.join(__dirname, '..', 'hooks', 'gsd-statusline.js');
|
||||
|
||||
describe('config schema: statusline.show_context_tokens', () => {
|
||||
test('registers statusline.show_context_tokens', () => {
|
||||
assert.ok(
|
||||
VALID_CONFIG_KEYS.has('statusline.show_context_tokens'),
|
||||
'statusline.show_context_tokens must be in VALID_CONFIG_KEYS',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatTokens', () => {
|
||||
test('passes small counts through', () => {
|
||||
assert.equal(formatTokens(0), '0');
|
||||
assert.equal(formatTokens(999), '999');
|
||||
});
|
||||
test('rounds thousands to k', () => {
|
||||
assert.equal(formatTokens(1000), '1k');
|
||||
assert.equal(formatTokens(156342), '156k');
|
||||
assert.equal(formatTokens(156700), '157k');
|
||||
});
|
||||
test('formats millions with one decimal', () => {
|
||||
assert.equal(formatTokens(1000000), '1.0M');
|
||||
assert.equal(formatTokens(1234567), '1.2M');
|
||||
});
|
||||
test('k-to-M threshold boundary: limit-1 / limit / limit+1', () => {
|
||||
// 999,999 k-rounds to 1000 — must promote to the M branch, never "1000k"
|
||||
assert.equal(formatTokens(999999), '1.0M');
|
||||
assert.equal(formatTokens(1000000), '1.0M');
|
||||
assert.equal(formatTokens(1000001), '1.0M');
|
||||
// 999,499 is the last value that still k-rounds below 1000
|
||||
assert.equal(formatTokens(999499), '999k');
|
||||
assert.equal(formatTokens(999500), '1.0M');
|
||||
});
|
||||
});
|
||||
|
||||
describe('contextTokenSuffix', () => {
|
||||
test('returns empty string for absent/malformed usage', () => {
|
||||
assert.equal(contextTokenSuffix(null), '');
|
||||
assert.equal(contextTokenSuffix(undefined), '');
|
||||
assert.equal(contextTokenSuffix('nope'), '');
|
||||
assert.equal(contextTokenSuffix({}), '');
|
||||
});
|
||||
test('sums all four token dimensions', () => {
|
||||
const suffix = contextTokenSuffix({
|
||||
input_tokens: 1000,
|
||||
cache_creation_input_tokens: 2000,
|
||||
cache_read_input_tokens: 150000,
|
||||
output_tokens: 3000,
|
||||
});
|
||||
assert.equal(suffix, ' (156k)');
|
||||
});
|
||||
test('tolerates missing dimensions', () => {
|
||||
assert.equal(contextTokenSuffix({ input_tokens: 500 }), ' (500)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('statusline output token suffix (e2e)', () => {
|
||||
function makeProject(flag) {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ctx-tokens-'));
|
||||
fs.mkdirSync(path.join(dir, '.planning'), { recursive: true });
|
||||
if (flag !== undefined) {
|
||||
fs.writeFileSync(
|
||||
path.join(dir, '.planning', 'config.json'),
|
||||
JSON.stringify({ statusline: { show_context_tokens: flag } }),
|
||||
);
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
function runHook(dir) {
|
||||
const payload = JSON.stringify({
|
||||
model: { display_name: 'Claude' },
|
||||
workspace: { current_dir: dir },
|
||||
session_id: `test-tokens-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
||||
context_window: {
|
||||
remaining_percentage: 70,
|
||||
total_tokens: 200000,
|
||||
current_usage: {
|
||||
input_tokens: 1000,
|
||||
cache_read_input_tokens: 150000,
|
||||
output_tokens: 5000,
|
||||
},
|
||||
},
|
||||
});
|
||||
let stdout = '';
|
||||
try {
|
||||
stdout = execFileSync(process.execPath, [hookPath], {
|
||||
input: payload, encoding: 'utf8', timeout: 4000,
|
||||
});
|
||||
} catch (e) {
|
||||
stdout = e.stdout || '';
|
||||
}
|
||||
// eslint-disable-next-line no-control-regex -- stripping ANSI SGR sequences from captured CLI output
|
||||
return stdout.replace(/\x1b\[[0-9;]*m/g, '');
|
||||
}
|
||||
|
||||
test('flag=true appends the token count after the percentage', () => {
|
||||
const dir = makeProject(true);
|
||||
try {
|
||||
const out = runHook(dir);
|
||||
assert.match(out, /% \(156k\)/, `expected "(156k)" after the meter %; got: ${out}`);
|
||||
} finally {
|
||||
cleanup(dir);
|
||||
}
|
||||
});
|
||||
|
||||
test('default (flag absent) meter is unchanged — no token count', () => {
|
||||
const dir = makeProject(undefined);
|
||||
try {
|
||||
const out = runHook(dir);
|
||||
assert.doesNotMatch(out, /\(\d+(?:\.\d+)?[kM]?\)/, `expected no token suffix; got: ${out}`);
|
||||
} finally {
|
||||
cleanup(dir);
|
||||
}
|
||||
});
|
||||
|
||||
test('flag=false meter is unchanged — no token count', () => {
|
||||
const dir = makeProject(false);
|
||||
try {
|
||||
const out = runHook(dir);
|
||||
assert.doesNotMatch(out, /\(\d+(?:\.\d+)?[kM]?\)/, `expected no token suffix; got: ${out}`);
|
||||
} finally {
|
||||
cleanup(dir);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
*
|
||||
* Verifies that the three agents (researcher, planner, executor) contain the
|
||||
* interlocking instruction text that forms the slopsquatting defence gate.
|
||||
*
|
||||
* The gate spans TWO layers. The executor stops at a `gate="blocking-human"`
|
||||
* checkpoint and hands it up; the execute-phase orchestrator then decides
|
||||
* whether the human ever sees it. Asserting only the executor half leaves the
|
||||
* orchestrator free to auto-approve the checkpoint the executor just refused
|
||||
* to auto-approve.
|
||||
*/
|
||||
|
||||
const { describe, test, before } = require('node:test');
|
||||
@@ -17,6 +23,9 @@ const RESEARCHER = path.join(AGENTS, 'gsd-phase-researcher.md');
|
||||
const PLANNER = path.join(AGENTS, 'gsd-planner.md');
|
||||
const EXECUTOR = path.join(AGENTS, 'gsd-executor.md');
|
||||
|
||||
const WORKFLOWS = path.join(__dirname, '..', 'gsd-core', 'workflows');
|
||||
const EXECUTE_PHASE = path.join(WORKFLOWS, 'execute-phase.md');
|
||||
|
||||
function parseSections(md) {
|
||||
const lines = md.split(/\r?\n/);
|
||||
const sections = [];
|
||||
@@ -475,4 +484,134 @@ describe('gsd-executor.md — package installs excluded from RULE 3 auto-fix', (
|
||||
'executor auto mode must explicitly block auto-approval for package-legitimacy checkpoints'
|
||||
);
|
||||
});
|
||||
|
||||
// #2107 harm, one checkpoint type over: the executor auto-resolves a decision
|
||||
// checkpoint itself (auto-selects the first option and continues) without ever
|
||||
// returning it, so a blocking-human decision must be carved out HERE — the
|
||||
// orchestrator's carve-out never runs for a checkpoint the executor swallowed.
|
||||
test('auto mode does not auto-select a blocking-human decision checkpoint', () => {
|
||||
const autoModeLine = lineIndexes(model.lines, (line) => hasAllTokens(line, ['auto-mode', 'checkpoint', 'behavior']))[0];
|
||||
assert.notEqual(autoModeLine, undefined, 'executor must define auto-mode checkpoint behavior');
|
||||
|
||||
const window = model.lines.slice(autoModeLine, autoModeLine + 25);
|
||||
|
||||
const decisionLines = window.filter((line) => hasAllTokens(line, ['checkpoint:decision']));
|
||||
assert.ok(decisionLines.length > 0, 'executor auto-mode must document the checkpoint:decision branch');
|
||||
|
||||
const gatesDecision = decisionLines.some(
|
||||
(line) =>
|
||||
hasAllTokens(line, ['blocking-human']) &&
|
||||
(hasAllTokens(line, ['stop']) || hasAllTokens(line, ['not', 'auto-select']))
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
gatesDecision,
|
||||
'checkpoint:decision must carve out gate="blocking-human" (STOP + return) instead of auto-selecting the first option'
|
||||
);
|
||||
});
|
||||
|
||||
test('checkpoint_return_format transports the gate across the executor→orchestrator boundary', () => {
|
||||
const fmt = extractXmlElement(model.text, 'checkpoint_return_format');
|
||||
assert.ok(fmt.length > 0, 'executor must define checkpoint_return_format');
|
||||
|
||||
const fmtLines = fmt.split(/\r?\n/);
|
||||
const hasGateField = fmtLines.some((line) => hasAllTokens(line, ['gate:', 'blocking-human']));
|
||||
|
||||
assert.ok(
|
||||
hasGateField,
|
||||
'checkpoint_return_format must carry a **Gate:** field so blocking-human reaches the orchestrator carve-out'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('execute-phase.md — orchestrator honors the blocking-human gate', () => {
|
||||
let model;
|
||||
|
||||
before(() => {
|
||||
model = readModel(EXECUTE_PHASE);
|
||||
});
|
||||
|
||||
// The executor refuses to auto-approve a gate="blocking-human" checkpoint and
|
||||
// returns it via checkpoint_return_format. The orchestrator's auto-mode branch
|
||||
// is what runs next. If that branch dispatches purely on checkpoint *type*, it
|
||||
// auto-approves the checkpoint the executor just escalated — nullifying the
|
||||
// slopsquatting gate in exactly the unattended mode where it matters.
|
||||
test('auto-mode checkpoint handling excludes blocking-human checkpoints', () => {
|
||||
// NB: normalizeTokens keeps ':' as a word character, so the heading
|
||||
// "**Auto-mode checkpoint handling:**" yields the token `handling:`, not
|
||||
// `handling`. Anchor on the two tokens that survive intact.
|
||||
const autoModeLine = lineIndexes(model.lines, (line) =>
|
||||
hasAllTokens(line, ['auto-mode', 'checkpoint'])
|
||||
)[0];
|
||||
|
||||
assert.notEqual(
|
||||
autoModeLine,
|
||||
undefined,
|
||||
'execute-phase.md must define auto-mode checkpoint handling'
|
||||
);
|
||||
|
||||
const window = model.lines.slice(autoModeLine, autoModeLine + 20);
|
||||
|
||||
const honorsGate =
|
||||
anyLineHasAll(window, ['blocking-human']) ||
|
||||
anyLineHasAll(window, ['except', 'package-legitimacy']);
|
||||
|
||||
assert.ok(
|
||||
honorsGate,
|
||||
'execute-phase auto-mode must not auto-approve gate="blocking-human" checkpoints — ' +
|
||||
'the executor escalates them precisely so a human sees them'
|
||||
);
|
||||
});
|
||||
|
||||
test('auto-approve rule for human-verify is conditional, not unconditional', () => {
|
||||
const autoApproveLines = lineIndexes(model.lines, (line) =>
|
||||
hasAllTokens(line, ['human-verify', 'auto-spawn', 'approved'])
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
autoApproveLines.length > 0,
|
||||
'anchor drift: no human-verify auto-approve line matched — the conditional carve-out would pass vacuously'
|
||||
);
|
||||
|
||||
for (const idx of autoApproveLines) {
|
||||
const line = model.lines[idx];
|
||||
const isConditional =
|
||||
hasAllTokens(line, ['unless']) ||
|
||||
hasAllTokens(line, ['except']) ||
|
||||
hasAllTokens(line, ['blocking-human']) ||
|
||||
hasAllTokens(line, ['if', 'not']);
|
||||
|
||||
assert.ok(
|
||||
isConditional,
|
||||
`execute-phase.md:${idx + 1} auto-approves human-verify unconditionally; ` +
|
||||
'it must carve out gate="blocking-human"'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('auto-select rule for decision is conditional, not unconditional', () => {
|
||||
const autoSelectLines = lineIndexes(model.lines, (line) =>
|
||||
hasAllTokens(line, ['decision', 'auto-spawn', 'first', 'option'])
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
autoSelectLines.length > 0,
|
||||
'anchor drift: no decision auto-select line matched — the conditional carve-out would pass vacuously'
|
||||
);
|
||||
|
||||
for (const idx of autoSelectLines) {
|
||||
const line = model.lines[idx];
|
||||
const isConditional =
|
||||
hasAllTokens(line, ['unless']) ||
|
||||
hasAllTokens(line, ['except']) ||
|
||||
hasAllTokens(line, ['blocking-human']) ||
|
||||
hasAllTokens(line, ['if', 'not']);
|
||||
|
||||
assert.ok(
|
||||
isConditional,
|
||||
`execute-phase.md:${idx + 1} auto-selects a decision unconditionally; ` +
|
||||
'it must carve out gate="blocking-human"'
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
148
tests/shell-command-projection-path-sep.test.cjs
Normal file
148
tests/shell-command-projection-path-sep.test.cjs
Normal file
@@ -0,0 +1,148 @@
|
||||
'use strict';
|
||||
|
||||
// Focused unit tests for the path-separator helpers on shell-command-projection
|
||||
// (toPosixPath / toNativePath / posixNormalize). These are drop-in replacements
|
||||
// for open-coded `.replace(/\\/g, '/')` and
|
||||
// `process.platform === 'win32' ? x.replace(/\//g, '\\') : x`
|
||||
// call sites — see shell-command-projection.cts for the platform-relative contract.
|
||||
|
||||
const { describe, test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const path = require('node:path');
|
||||
const fc = require('fast-check');
|
||||
|
||||
const { toPosixPath, toNativePath, posixNormalize } = require('../gsd-core/bin/lib/shell-command-projection.cjs');
|
||||
|
||||
describe('toPosixPath', () => {
|
||||
test('plain relative path: already-POSIX segments pass through unchanged', () => {
|
||||
assert.equal(toPosixPath('a/b/c'), 'a/b/c');
|
||||
});
|
||||
|
||||
test('plain relative path built with the native separator normalizes to POSIX segments', () => {
|
||||
const native = path.join('alpha', 'beta', 'gamma');
|
||||
assert.equal(toPosixPath(native), ['alpha', 'beta', 'gamma'].join('/'));
|
||||
});
|
||||
|
||||
test('absolute path: segment list matches the native path split on path.sep', () => {
|
||||
const abs = path.resolve('alpha', 'beta');
|
||||
assert.equal(toPosixPath(abs), abs.split(path.sep).join('/'));
|
||||
});
|
||||
|
||||
test('idempotency: applying twice equals applying once', () => {
|
||||
const native = path.join('alpha', 'beta', 'gamma');
|
||||
const once = toPosixPath(native);
|
||||
const twice = toPosixPath(once);
|
||||
assert.equal(once, twice);
|
||||
});
|
||||
|
||||
test('empty string in, empty string out', () => {
|
||||
assert.equal(toPosixPath(''), '');
|
||||
});
|
||||
|
||||
test('does not corrupt a POSIX-style input containing a literal backslash-free path', () => {
|
||||
// A string that already uses only '/' as its separator and contains no
|
||||
// occurrence of path.sep-as-backslash must survive unchanged regardless of
|
||||
// the host platform's path.sep (no-op on POSIX; nothing to split on Windows
|
||||
// either, since there is no backslash present).
|
||||
const alreadyPosix = 'already/posix/style/path';
|
||||
assert.equal(toPosixPath(alreadyPosix), alreadyPosix);
|
||||
});
|
||||
|
||||
test('property: toPosixPath output never contains path.sep when path.sep differs from "/"', () => {
|
||||
fc.assert(
|
||||
fc.property(fc.array(fc.stringMatching(/^[a-zA-Z0-9_-]+$/), { minLength: 1, maxLength: 5 }), (segments) => {
|
||||
const native = segments.join(path.sep);
|
||||
const posix = toPosixPath(native);
|
||||
if (path.sep !== '/') {
|
||||
assert.ok(!posix.includes(path.sep));
|
||||
}
|
||||
assert.equal(posix, segments.join('/'));
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toNativePath', () => {
|
||||
test('plain relative path: already-native segments pass through unchanged', () => {
|
||||
const native = path.join('alpha', 'beta', 'gamma');
|
||||
assert.equal(toNativePath(native), native);
|
||||
});
|
||||
|
||||
test('POSIX-style relative path converts to native segments', () => {
|
||||
const posix = 'alpha/beta/gamma';
|
||||
assert.equal(toNativePath(posix), ['alpha', 'beta', 'gamma'].join(path.sep));
|
||||
});
|
||||
|
||||
test('absolute path: round-trips back to the original POSIX form via split/join', () => {
|
||||
const posixAbs = path.posix.resolve('/', 'alpha', 'beta');
|
||||
const native = toNativePath(posixAbs);
|
||||
assert.equal(native.split(path.sep).join('/'), posixAbs);
|
||||
});
|
||||
|
||||
test('idempotency: applying twice equals applying once', () => {
|
||||
const posix = 'alpha/beta/gamma';
|
||||
const once = toNativePath(posix);
|
||||
const twice = toNativePath(once);
|
||||
assert.equal(once, twice);
|
||||
});
|
||||
|
||||
test('empty string in, empty string out', () => {
|
||||
assert.equal(toNativePath(''), '');
|
||||
});
|
||||
|
||||
test('does not corrupt an already-native-style input containing no POSIX separator', () => {
|
||||
// A string that already uses only path.sep as its separator (and contains
|
||||
// no '/' occurrence) must survive unchanged: no-op on POSIX (path.sep is
|
||||
// '/', so it IS the separator being normalized to); on Windows there is no
|
||||
// '/' present to split on, so the single-element join reproduces the input.
|
||||
const alreadyNative = ['already', 'native', 'style', 'path'].join(path.sep);
|
||||
assert.equal(toNativePath(alreadyNative), alreadyNative);
|
||||
});
|
||||
|
||||
test('property: toPosixPath and toNativePath round-trip a POSIX-style path through both conversions', () => {
|
||||
fc.assert(
|
||||
fc.property(fc.array(fc.stringMatching(/^[a-zA-Z0-9_-]+$/), { minLength: 1, maxLength: 5 }), (segments) => {
|
||||
const posix = segments.join('/');
|
||||
assert.equal(toPosixPath(toNativePath(posix)), posix);
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('posixNormalize', () => {
|
||||
test('unconditional conversion: literal backslashes always become forward slashes, regardless of host platform', () => {
|
||||
// Unlike toPosixPath (which splits on path.sep — a no-op on POSIX hosts),
|
||||
// posixNormalize must convert backslashes even when running on a POSIX
|
||||
// host, because the input represents a TARGET platform's path, not this
|
||||
// machine's filesystem path.
|
||||
assert.equal(posixNormalize('alpha\\beta\\gamma'), 'alpha/beta/gamma');
|
||||
});
|
||||
|
||||
test('mixed separators: only backslashes are converted, forward slashes are left alone', () => {
|
||||
assert.equal(posixNormalize('alpha\\beta/gamma\\delta'), 'alpha/beta/gamma/delta');
|
||||
});
|
||||
|
||||
test('already-POSIX input with no backslash passes through unchanged', () => {
|
||||
const alreadyPosix = 'already/posix/style/path';
|
||||
assert.equal(posixNormalize(alreadyPosix), alreadyPosix);
|
||||
});
|
||||
|
||||
test('idempotency: applying twice equals applying once', () => {
|
||||
const once = posixNormalize('alpha\\beta\\gamma');
|
||||
const twice = posixNormalize(once);
|
||||
assert.equal(once, twice);
|
||||
});
|
||||
|
||||
test('empty string in, empty string out', () => {
|
||||
assert.equal(posixNormalize(''), '');
|
||||
});
|
||||
|
||||
test('property: posixNormalize output never contains a backslash', () => {
|
||||
fc.assert(
|
||||
fc.property(fc.string(), (input) => {
|
||||
const normalized = posixNormalize(input);
|
||||
assert.ok(!normalized.includes('\\'));
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -611,6 +611,37 @@ milestone: v1.0
|
||||
assert.ok(!content.includes('status: unknown'), 'should not contain unknown status');
|
||||
});
|
||||
|
||||
test('#2202: preserves unknown frontmatter keys the schema does not own', () => {
|
||||
// Regression: a mutating verb rewrites STATE.md via syncStateFrontmatter,
|
||||
// which rebuilds frontmatter from the body + schema. Before #2202 it dropped
|
||||
// any frontmatter key the schema does not own; custom/tooling keys must
|
||||
// survive every write.
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'STATE.md'),
|
||||
`---
|
||||
status: executing
|
||||
milestone: v1.0
|
||||
custom_tracking_id: ABC-123
|
||||
team: platform
|
||||
---
|
||||
|
||||
# Project State
|
||||
|
||||
**Current Phase:** 03
|
||||
**Current Plan:** 03-02
|
||||
`
|
||||
);
|
||||
|
||||
// Any writeStateMd triggers syncStateFrontmatter.
|
||||
runGsdTools('state update "Current Plan" "03-03"', tmpDir);
|
||||
|
||||
const content = fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8');
|
||||
assert.match(content, /custom_tracking_id: ABC-123/, 'unknown key custom_tracking_id must be preserved');
|
||||
assert.match(content, /team: platform/, 'unknown key team must be preserved');
|
||||
// Schema-owned keys still win / survive alongside the carried-forward keys.
|
||||
assert.ok(content.includes('status: executing'), 'schema-owned status still preserved');
|
||||
});
|
||||
|
||||
test('round-trip: write then read via state json', () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'STATE.md'),
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"docs-update.md": 55706,
|
||||
"edit-phase.md": 12927,
|
||||
"eval-review.md": 9967,
|
||||
"execute-phase.md": 93132,
|
||||
"execute-phase.md": 93583,
|
||||
"execute-plan.md": 32655,
|
||||
"explore.md": 10541,
|
||||
"extract-learnings.md": 12893,
|
||||
@@ -64,7 +64,7 @@
|
||||
"remove-phase.md": 8513,
|
||||
"remove-workspace.md": 7551,
|
||||
"resume-project.md": 17270,
|
||||
"review.md": 47168,
|
||||
"review.md": 50362,
|
||||
"scan.md": 7732,
|
||||
"secure-phase.md": 13622,
|
||||
"session-report.md": 4044,
|
||||
|
||||
Reference in New Issue
Block a user