Initial import from garrytan/gstack@026751e (main snapshot via local relay)
Some checks failed
Workflow Lint / actionlint (push) Has been cancelled
Build CI Image / build (push) Has been cancelled
Skill Docs Freshness / check-freshness (push) Has been cancelled
Periodic Evals / build-image (push) Has been cancelled
Periodic Evals / evals (map[file:test/codex-e2e.test.ts name:e2e-codex]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/gemini-e2e.test.ts name:e2e-gemini]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-design.test.ts name:e2e-design]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-plan.test.ts name:e2e-plan]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-qa-bugs.test.ts name:e2e-qa-bugs]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-qa-workflow.test.ts name:e2e-qa-workflow]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-review.test.ts name:e2e-review]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-workflow.test.ts name:e2e-workflow]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-routing-e2e.test.ts name:e2e-routing]) (push) Has been cancelled
Some checks failed
Workflow Lint / actionlint (push) Has been cancelled
Build CI Image / build (push) Has been cancelled
Skill Docs Freshness / check-freshness (push) Has been cancelled
Periodic Evals / build-image (push) Has been cancelled
Periodic Evals / evals (map[file:test/codex-e2e.test.ts name:e2e-codex]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/gemini-e2e.test.ts name:e2e-gemini]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-design.test.ts name:e2e-design]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-plan.test.ts name:e2e-plan]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-qa-bugs.test.ts name:e2e-qa-bugs]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-qa-workflow.test.ts name:e2e-qa-workflow]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-review.test.ts name:e2e-review]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-workflow.test.ts name:e2e-workflow]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-routing-e2e.test.ts name:e2e-routing]) (push) Has been cancelled
Source: https://github.com/garrytan/gstack/commit/026751e
This commit is contained in:
83
scripts/resolvers/preamble/generate-ask-user-format.ts
Normal file
83
scripts/resolvers/preamble/generate-ask-user-format.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import type { TemplateContext } from '../types';
|
||||
|
||||
export function generateAskUserFormat(_ctx: TemplateContext): string {
|
||||
return `## AskUserQuestion Format
|
||||
|
||||
### Tool resolution (read first)
|
||||
|
||||
"AskUserQuestion" can resolve to two tools at runtime: the **host MCP variant** (e.g. \`mcp__conductor__AskUserQuestion\` — appears in your tool list when the host registers it) or the **native** Claude Code tool.
|
||||
|
||||
**Rule:** if any \`mcp__*__AskUserQuestion\` variant is in your tool list, prefer it. Hosts may disable native AUQ via \`--disallowedTools AskUserQuestion\` (Conductor does, by default) and route through their MCP variant; calling native there silently fails. Same questions/options shape; same decision-brief format applies.
|
||||
|
||||
**If no AskUserQuestion variant appears in your tool list, this skill is BLOCKED.** Stop, report \`BLOCKED — AskUserQuestion unavailable\`, and wait for the user. Do not write decisions to the plan file as a substitute, do not emit them as prose and stop, and do not silently auto-decide (only \`/plan-tune\` AUTO_DECIDE opt-ins authorize auto-picking).
|
||||
|
||||
### Format
|
||||
|
||||
Every AskUserQuestion is a decision brief and must be sent as tool_use, not prose.
|
||||
|
||||
\`\`\`
|
||||
D<N> — <one-line question title>
|
||||
Project/branch/task: <1 short grounding sentence using _BRANCH>
|
||||
ELI10: <plain English a 16-year-old could follow, 2-4 sentences, name the stakes>
|
||||
Stakes if we pick wrong: <one sentence on what breaks, what user sees, what's lost>
|
||||
Recommendation: <choice> because <one-line reason>
|
||||
Completeness: A=X/10, B=Y/10 (or: Note: options differ in kind, not coverage — no completeness score)
|
||||
Pros / cons:
|
||||
A) <option label> (recommended)
|
||||
✅ <pro — concrete, observable, ≥40 chars>
|
||||
❌ <con — honest, ≥40 chars>
|
||||
B) <option label>
|
||||
✅ <pro>
|
||||
❌ <con>
|
||||
Net: <one-line synthesis of what you're actually trading off>
|
||||
\`\`\`
|
||||
|
||||
D-numbering: first question in a skill invocation is \`D1\`; increment yourself. This is a model-level instruction, not a runtime counter.
|
||||
|
||||
ELI10 is always present, in plain English, not function names. Recommendation is ALWAYS present. Keep the \`(recommended)\` label; AUTO_DECIDE depends on it.
|
||||
|
||||
Completeness: use \`Completeness: N/10\` only when options differ in coverage. 10 = complete, 7 = happy path, 3 = shortcut. If options differ in kind, write: \`Note: options differ in kind, not coverage — no completeness score.\`
|
||||
|
||||
Pros / cons: use ✅ and ❌. Minimum 2 pros and 1 con per option when the choice is real; Minimum 40 characters per bullet. Hard-stop escape for one-way/destructive confirmations: \`✅ No cons — this is a hard-stop choice\`.
|
||||
|
||||
Neutral posture: \`Recommendation: <default> — this is a taste call, no strong preference either way\`; \`(recommended)\` STAYS on the default option for AUTO_DECIDE.
|
||||
|
||||
Effort both-scales: when an option involves effort, label both human-team and CC+gstack time, e.g. \`(human: ~2 days / CC: ~15 min)\`. Makes AI compression visible at decision time.
|
||||
|
||||
Net line closes the tradeoff. Per-skill instructions may add stricter rules.
|
||||
|
||||
12. **Non-ASCII characters — write directly, never \\u-escape.** When any
|
||||
string field (question, option label, option description) contains
|
||||
Chinese (繁體/簡體), Japanese, Korean, or other non-ASCII text, emit
|
||||
the literal UTF-8 characters in the JSON string. **Never escape them
|
||||
as \`\\uXXXX\`.** Claude Code's tool parameter pipe is UTF-8 native
|
||||
and passes characters through unchanged. Manually escaping requires
|
||||
recalling each codepoint from training, which is unreliable for long
|
||||
CJK strings — the model regularly emits the wrong codepoint (e.g.
|
||||
writes \`\\u3103\` thinking it is 管 U+7BA1, but \`\\u3103\` is
|
||||
actually , so the user sees \`管理工具\` rendered as \`3用箱\`).
|
||||
The trigger is long, multi-line questions with hundreds of CJK
|
||||
characters: that is exactly when reflexive escaping kicks in and
|
||||
exactly when miscoding is most damaging. Long ≠ escape. Keep
|
||||
characters literal.
|
||||
|
||||
Wrong: \`"question": "請選擇\\uXXXX\\uXXXX\\uXXXX\\uXXXX"\`
|
||||
Right: \`"question": "請選擇管理工具"\`
|
||||
|
||||
Only JSON-mandatory escapes remain allowed: \`\\n\`, \`\\t\`, \`\\"\`, \`\\\\\`.
|
||||
|
||||
### Self-check before emitting
|
||||
|
||||
Before calling AskUserQuestion, verify:
|
||||
- [ ] D<N> header present
|
||||
- [ ] ELI10 paragraph present (stakes line too)
|
||||
- [ ] Recommendation line present with concrete reason
|
||||
- [ ] Completeness scored (coverage) OR kind-note present (kind)
|
||||
- [ ] Every option has ≥2 ✅ and ≥1 ❌, each ≥40 chars (or hard-stop escape)
|
||||
- [ ] (recommended) label on one option (even for neutral-posture)
|
||||
- [ ] Dual-scale effort labels on effort-bearing options (human / CC)
|
||||
- [ ] Net line closes the decision
|
||||
- [ ] You are calling the tool, not writing prose
|
||||
- [ ] Non-ASCII characters (CJK / accents) written directly, NOT \\u-escaped
|
||||
`;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { TemplateContext } from '../types';
|
||||
|
||||
export function generateBrainHealthInstruction(ctx: TemplateContext): string {
|
||||
if (ctx.host !== 'gbrain' && ctx.host !== 'hermes') return '';
|
||||
return `If \`BRAIN_HEALTH\` is shown and the score is below 50, tell the user which checks
|
||||
failed (shown in the output) and suggest: "Run \\\`gbrain doctor\\\` for full diagnostics."
|
||||
If the output is not valid JSON or health_score is missing, treat GBrain as unavailable
|
||||
and proceed without brain features this session.`;
|
||||
}
|
||||
159
scripts/resolvers/preamble/generate-brain-sync-block.ts
Normal file
159
scripts/resolvers/preamble/generate-brain-sync-block.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
/**
|
||||
* artifacts-sync preamble block (renamed from gbrain-sync in v1.27.0.0).
|
||||
*
|
||||
* Emits bash that runs at every skill invocation:
|
||||
* 0. Live gbrain-availability hint (per /plan-eng-review): when gbrain is
|
||||
* configured, emit one of two variants (steady-state vs empty-corpus
|
||||
* emergency). Zero context cost when gbrain is not configured.
|
||||
* 1. If ~/.gstack-artifacts-remote.txt (or legacy ~/.gstack-brain-remote.txt
|
||||
* during the v1.27.0.0 migration window) exists AND ~/.gstack/.git is
|
||||
* missing, surface a restore-available hint (does NOT auto-run restore).
|
||||
* 2. If sync is on, run `gstack-brain-sync --once` (drain + push). The
|
||||
* script keeps its old name; only the config-key + state-file names flip.
|
||||
* 3. On first skill of the day (24h cache via .brain-last-pull):
|
||||
* `git fetch` + ff-only merge (JSONL merge driver handles conflicts).
|
||||
* 4. Emit an `ARTIFACTS_SYNC:` status line so every skill surfaces health.
|
||||
* In remote-MCP mode, the line reads `ARTIFACTS_SYNC: remote-mode
|
||||
* (managed by brain server <host>)` since this machine doesn't sync
|
||||
* anything locally — the brain admin's server pulls from GitHub/GitLab.
|
||||
*
|
||||
* Also emits prose instructions for the host LLM to fire a one-time privacy
|
||||
* stop-gate via AskUserQuestion when artifacts_sync_mode is unset and gbrain
|
||||
* is available on the host.
|
||||
*
|
||||
* Block emitted across all tiers. Internal bash short-circuits when feature
|
||||
* is disabled; cost is <5ms.
|
||||
*
|
||||
* Skill-end sync is handled by the completion-status generator via a call
|
||||
* to `gstack-brain-sync --discover-new` + `--once`.
|
||||
*/
|
||||
import type { TemplateContext } from '../types';
|
||||
|
||||
export function generateBrainSyncBlock(ctx: TemplateContext): string {
|
||||
const isBrainHost = ctx.host === 'gbrain' || ctx.host === 'hermes';
|
||||
return `## Artifacts Sync (skill start)
|
||||
|
||||
\`\`\`bash
|
||||
_GSTACK_HOME="\${GSTACK_HOME:-$HOME/.gstack}"
|
||||
# Prefer the v1.27.0.0 artifacts file; fall back to brain file for users
|
||||
# upgrading mid-stream before the migration script runs.
|
||||
if [ -f "$HOME/.gstack-artifacts-remote.txt" ]; then
|
||||
_BRAIN_REMOTE_FILE="$HOME/.gstack-artifacts-remote.txt"
|
||||
else
|
||||
_BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt"
|
||||
fi
|
||||
_BRAIN_SYNC_BIN="${ctx.paths.binDir}/gstack-brain-sync"
|
||||
_BRAIN_CONFIG_BIN="${ctx.paths.binDir}/gstack-config"
|
||||
|
||||
# /sync-gbrain context-load: teach the agent to use gbrain when it's available.
|
||||
# Per-worktree pin: post-spike redesign uses kubectl-style \`.gbrain-source\` in the
|
||||
# git toplevel to scope queries. Look for the pin in the worktree (not a global
|
||||
# state file) so that opening worktree B without a pin doesn't claim "indexed"
|
||||
# just because worktree A was synced. Empty string when gbrain is not
|
||||
# configured (zero context cost for non-gbrain users).
|
||||
_GBRAIN_CONFIG="$HOME/.gbrain/config.json"
|
||||
if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then
|
||||
_GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0)
|
||||
if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then
|
||||
_GBRAIN_PIN_PATH=""
|
||||
_REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "")
|
||||
if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then
|
||||
_GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source"
|
||||
fi
|
||||
if [ -n "$_GBRAIN_PIN_PATH" ]; then
|
||||
echo "GBrain configured. Prefer \\\`gbrain search\\\`/\\\`gbrain query\\\` over Grep for"
|
||||
echo "semantic questions; use \\\`gbrain code-def\\\`/\\\`code-refs\\\`/\\\`code-callers\\\` for"
|
||||
echo "symbol-aware code lookup. See \\"## GBrain Search Guidance\\" in CLAUDE.md."
|
||||
echo "Run /sync-gbrain to refresh."
|
||||
else
|
||||
echo "GBrain configured but this worktree isn't pinned yet. Run \\\`/sync-gbrain --full\\\`"
|
||||
echo "before relying on \\\`gbrain search\\\` for code questions in this worktree."
|
||||
echo "Falls back to Grep until pinned."
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
_BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || echo off)
|
||||
|
||||
# Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is
|
||||
# a no-op in remote mode; the brain server pulls from GitHub/GitLab on its
|
||||
# own cadence. Read claude.json directly to keep this preamble fast (no
|
||||
# subprocess to claude CLI on every skill start).
|
||||
_GBRAIN_MCP_MODE="none"
|
||||
if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then
|
||||
_GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null)
|
||||
case "$_GBRAIN_MCP_TYPE" in
|
||||
url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;;
|
||||
stdio) _GBRAIN_MCP_MODE="local-stdio" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then
|
||||
_BRAIN_NEW_URL=$(head -1 "$_BRAIN_REMOTE_FILE" 2>/dev/null | tr -d '[:space:]')
|
||||
if [ -n "$_BRAIN_NEW_URL" ]; then
|
||||
echo "ARTIFACTS_SYNC: artifacts repo detected: $_BRAIN_NEW_URL"
|
||||
echo "ARTIFACTS_SYNC: run 'gstack-brain-restore' to pull your cross-machine artifacts (or 'gstack-config set artifacts_sync_mode off' to dismiss forever)"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then
|
||||
_BRAIN_LAST_PULL_FILE="$_GSTACK_HOME/.brain-last-pull"
|
||||
_BRAIN_NOW=$(date +%s)
|
||||
_BRAIN_DO_PULL=1
|
||||
if [ -f "$_BRAIN_LAST_PULL_FILE" ]; then
|
||||
_BRAIN_LAST=$(cat "$_BRAIN_LAST_PULL_FILE" 2>/dev/null || echo 0)
|
||||
_BRAIN_AGE=$(( _BRAIN_NOW - _BRAIN_LAST ))
|
||||
[ "$_BRAIN_AGE" -lt 86400 ] && _BRAIN_DO_PULL=0
|
||||
fi
|
||||
if [ "$_BRAIN_DO_PULL" = "1" ]; then
|
||||
( cd "$_GSTACK_HOME" && git fetch origin >/dev/null 2>&1 && git merge --ff-only "origin/$(git rev-parse --abbrev-ref HEAD)" >/dev/null 2>&1 ) || true
|
||||
echo "$_BRAIN_NOW" > "$_BRAIN_LAST_PULL_FILE"
|
||||
fi
|
||||
"$_BRAIN_SYNC_BIN" --once 2>/dev/null || true
|
||||
fi
|
||||
|
||||
if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then
|
||||
# Remote-MCP mode: local artifacts sync is a no-op (brain admin's server
|
||||
# pulls from GitHub/GitLab). Show the user this is by design, not broken.
|
||||
_GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\\1|')
|
||||
echo "ARTIFACTS_SYNC: remote-mode (managed by brain server \${_GBRAIN_HOST:-remote})"
|
||||
elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then
|
||||
_BRAIN_QUEUE_DEPTH=0
|
||||
[ -f "$_GSTACK_HOME/.brain-queue.jsonl" ] && _BRAIN_QUEUE_DEPTH=$(wc -l < "$_GSTACK_HOME/.brain-queue.jsonl" | tr -d ' ')
|
||||
_BRAIN_LAST_PUSH="never"
|
||||
[ -f "$_GSTACK_HOME/.brain-last-push" ] && _BRAIN_LAST_PUSH=$(cat "$_GSTACK_HOME/.brain-last-push" 2>/dev/null || echo never)
|
||||
echo "ARTIFACTS_SYNC: mode=$_BRAIN_SYNC_MODE | last_push=$_BRAIN_LAST_PUSH | queue=$_BRAIN_QUEUE_DEPTH"
|
||||
else
|
||||
echo "ARTIFACTS_SYNC: off"
|
||||
fi
|
||||
\`\`\`
|
||||
|
||||
${isBrainHost ? `If output shows \`ARTIFACTS_SYNC: artifacts repo detected\`, offer \`gstack-brain-restore\` via AskUserQuestion; otherwise continue.` : ''}
|
||||
|
||||
Privacy stop-gate: if output shows \`ARTIFACTS_SYNC: off\`, \`artifacts_sync_mode_prompted\` is \`false\`, and gbrain is on PATH or \`gbrain doctor --fast --json\` works, ask once:
|
||||
|
||||
> gstack can publish your artifacts (CEO plans, designs, reports) to a private GitHub repo that GBrain indexes across machines. How much should sync?
|
||||
|
||||
Options:
|
||||
- A) Everything allowlisted (recommended)
|
||||
- B) Only artifacts
|
||||
- C) Decline, keep everything local
|
||||
|
||||
After answer:
|
||||
|
||||
\`\`\`bash
|
||||
# Chosen mode: full | artifacts-only | off
|
||||
"$_BRAIN_CONFIG_BIN" set artifacts_sync_mode <choice>
|
||||
"$_BRAIN_CONFIG_BIN" set artifacts_sync_mode_prompted true
|
||||
\`\`\`
|
||||
|
||||
If A/B and \`~/.gstack/.git\` is missing, ask whether to run \`gstack-artifacts-init\`. Do not block the skill.
|
||||
|
||||
At skill END before telemetry:
|
||||
|
||||
\`\`\`bash
|
||||
"${ctx.paths.binDir}/gstack-brain-sync" --discover-new 2>/dev/null || true
|
||||
"${ctx.paths.binDir}/gstack-brain-sync" --once 2>/dev/null || true
|
||||
\`\`\`
|
||||
`;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
|
||||
export function generateCompletenessSection(): string {
|
||||
return `## Completeness Principle — Boil the Lake
|
||||
|
||||
AI makes completeness cheap. Recommend complete lakes (tests, edge cases, error paths); flag oceans (rewrites, multi-quarter migrations).
|
||||
|
||||
When options differ in coverage, include \`Completeness: X/10\` (10 = all edge cases, 7 = happy path, 3 = shortcut). When options differ in kind, write: \`Note: options differ in kind, not coverage — no completeness score.\` Do not fabricate scores.`;
|
||||
}
|
||||
85
scripts/resolvers/preamble/generate-completion-status.ts
Normal file
85
scripts/resolvers/preamble/generate-completion-status.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import type { TemplateContext } from '../types';
|
||||
|
||||
/**
|
||||
* Plan-mode-skill semantics block.
|
||||
*
|
||||
* Lives at the TOP of the preamble (position 1) so models read the authoritative
|
||||
* plan-mode rule before any other instructions. Replaces the vestigial
|
||||
* generate-plan-mode-handshake.ts that used to sit at this position and told
|
||||
* interactive review skills to emit an exit-and-rerun handshake instead of
|
||||
* running their interactive STOP-Ask workflow.
|
||||
*
|
||||
* Text is the same "Plan Mode Safe Operations" + "Skill Invocation During Plan
|
||||
* Mode" blocks that previously lived at the tail of generateCompletionStatus().
|
||||
* Only the position changes. All skills (not just interactive: true) see this.
|
||||
*
|
||||
* Composition position: index 1 in scripts/resolvers/preamble.ts — after
|
||||
* generatePreambleBash (so _SESSION_ID / _BRANCH / _TEL env vars exist before
|
||||
* any plan-mode-aware telemetry) and before generateUpgradeCheck + onboarding
|
||||
* gates. See ceo-plan 2026-04-24 "remove vestigial plan-mode handshake" for
|
||||
* the full rationale.
|
||||
*/
|
||||
export function generatePlanModeInfo(_ctx: TemplateContext): string {
|
||||
return `## Plan Mode Safe Operations
|
||||
|
||||
In plan mode, allowed because they inform the plan: \`$B\`, \`$D\`, \`codex exec\`/\`codex review\`, writes to \`~/.gstack/\`, writes to the plan file, and \`open\` for generated artifacts.
|
||||
|
||||
## Skill Invocation During Plan Mode
|
||||
|
||||
If the user invokes a skill in plan mode, the skill takes precedence over generic plan mode behavior. **Treat the skill file as executable instructions, not reference.** Follow it step by step starting from Step 0; the first AskUserQuestion is the workflow entering plan mode, not a violation of it. AskUserQuestion (any variant — \`mcp__*__AskUserQuestion\` or native; see "AskUserQuestion Format → Tool resolution") satisfies plan mode's end-of-turn requirement. If no variant is callable, the skill is BLOCKED — stop and report \`BLOCKED — AskUserQuestion unavailable\` per the AskUserQuestion Format rule. At a STOP point, stop immediately. Do not continue the workflow or call ExitPlanMode there. Commands marked "PLAN MODE EXCEPTION — ALWAYS RUN" execute. Call ExitPlanMode only after the skill workflow completes, or if the user tells you to cancel the skill or leave plan mode.`;
|
||||
}
|
||||
|
||||
export function generateCompletionStatus(ctx: TemplateContext): string {
|
||||
return `## Completion Status Protocol
|
||||
|
||||
When completing a skill workflow, report status using one of:
|
||||
- **DONE** — completed with evidence.
|
||||
- **DONE_WITH_CONCERNS** — completed, but list concerns.
|
||||
- **BLOCKED** — cannot proceed; state blocker and what was tried.
|
||||
- **NEEDS_CONTEXT** — missing info; state exactly what is needed.
|
||||
|
||||
Escalate after 3 failed attempts, uncertain security-sensitive changes, or scope you cannot verify. Format: \`STATUS\`, \`REASON\`, \`ATTEMPTED\`, \`RECOMMENDATION\`.
|
||||
|
||||
## Operational Self-Improvement
|
||||
|
||||
Before completing, if you discovered a durable project quirk or command fix that would save 5+ minutes next time, log it:
|
||||
|
||||
\`\`\`bash
|
||||
${ctx.paths.binDir}/gstack-learnings-log '{"skill":"SKILL_NAME","type":"operational","key":"SHORT_KEY","insight":"DESCRIPTION","confidence":N,"source":"observed"}'
|
||||
\`\`\`
|
||||
|
||||
Do not log obvious facts or one-time transient errors.
|
||||
|
||||
## Telemetry (run last)
|
||||
|
||||
After workflow completion, log telemetry. Use skill \`name:\` from frontmatter. OUTCOME is success/error/abort/unknown.
|
||||
|
||||
**PLAN MODE EXCEPTION — ALWAYS RUN:** This command writes telemetry to
|
||||
\`~/.gstack/analytics/\`, matching preamble analytics writes.
|
||||
|
||||
Run this bash:
|
||||
|
||||
\`\`\`bash
|
||||
_TEL_END=$(date +%s)
|
||||
_TEL_DUR=$(( _TEL_END - _TEL_START ))
|
||||
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
|
||||
# Session timeline: record skill completion (local-only, never sent anywhere)
|
||||
~/.claude/skills/gstack/bin/gstack-timeline-log '{"skill":"SKILL_NAME","event":"completed","branch":"'$(git branch --show-current 2>/dev/null || echo unknown)'","outcome":"OUTCOME","duration_s":"'"$_TEL_DUR"'","session":"'"$_SESSION_ID"'"}' 2>/dev/null || true
|
||||
# Local analytics (gated on telemetry setting)
|
||||
if [ "$_TEL" != "off" ]; then
|
||||
echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||
fi
|
||||
# Remote telemetry (opt-in, requires binary)
|
||||
if [ "$_TEL" != "off" ] && [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then
|
||||
~/.claude/skills/gstack/bin/gstack-telemetry-log \\
|
||||
--skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \\
|
||||
--used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null &
|
||||
fi
|
||||
\`\`\`
|
||||
|
||||
Replace \`SKILL_NAME\`, \`OUTCOME\`, and \`USED_BROWSE\` before running.
|
||||
|
||||
## Plan Status Footer
|
||||
|
||||
Skills that run plan reviews (\`/plan-*-review\`, \`/codex review\`) include the EXIT PLAN MODE GATE blocking checklist at the end of the skill, which verifies the plan file ends with \`## GSTACK REVIEW REPORT\` before ExitPlanMode is called. Skills that don't run plan reviews (operational skills like \`/ship\`, \`/qa\`, \`/review\`) typically don't operate in plan mode and have no review report to verify; this footer is a no-op for them. Writing the plan file is the one edit allowed in plan mode.`;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export function generateConfusionProtocol(): string {
|
||||
return `## Confusion Protocol
|
||||
|
||||
For high-stakes ambiguity (architecture, data model, destructive scope, missing context), STOP. Name it in one sentence, present 2-3 options with tradeoffs, and ask. Do not use for routine coding or obvious changes.`;
|
||||
}
|
||||
22
scripts/resolvers/preamble/generate-context-health.ts
Normal file
22
scripts/resolvers/preamble/generate-context-health.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
|
||||
|
||||
export function generateContextHealth(): string {
|
||||
return `## Context Health (soft directive)
|
||||
|
||||
During long-running skill sessions, periodically write a brief \`[PROGRESS]\` summary: done, next, surprises.
|
||||
|
||||
If you are looping on the same diagnostic, same file, or failed fix variants, STOP and reassess. Consider escalation or /context-save. Progress summaries must NEVER mutate git state.`;
|
||||
}
|
||||
|
||||
// Preamble Composition (tier → sections)
|
||||
// ─────────────────────────────────────────────
|
||||
// T1: core + upgrade + lake + telemetry + voice(trimmed) + completion
|
||||
// T2: T1 + voice(full) + ask + completeness + context-recovery
|
||||
// T3: T2 + repo-mode + search
|
||||
// T4: (same as T3 — TEST_FAILURE_TRIAGE is a separate {{}} placeholder, not preamble)
|
||||
//
|
||||
// Skills by tier:
|
||||
// T1: browse, setup-cookies, benchmark
|
||||
// T2: investigate, cso, retro, doc-release, setup-deploy, canary, checkpoint, health
|
||||
// T3: autoplan, codex, design-consult, office-hours, ceo/design/eng-review
|
||||
// T4: ship, review, qa, qa-only, design-review, land-deploy
|
||||
31
scripts/resolvers/preamble/generate-context-recovery.ts
Normal file
31
scripts/resolvers/preamble/generate-context-recovery.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import type { TemplateContext } from '../types';
|
||||
|
||||
export function generateContextRecovery(ctx: TemplateContext): string {
|
||||
const binDir = ctx.host === 'codex' ? '$GSTACK_BIN' : ctx.paths.binDir;
|
||||
|
||||
return `## Context Recovery
|
||||
|
||||
At session start or after compaction, recover recent project context.
|
||||
|
||||
\`\`\`bash
|
||||
eval "$(${binDir}/gstack-slug 2>/dev/null)"
|
||||
_PROJ="\${GSTACK_HOME:-$HOME/.gstack}/projects/\${SLUG:-unknown}"
|
||||
if [ -d "$_PROJ" ]; then
|
||||
echo "--- RECENT ARTIFACTS ---"
|
||||
find "$_PROJ/ceo-plans" "$_PROJ/checkpoints" -type f -name "*.md" 2>/dev/null | xargs ls -t 2>/dev/null | head -3
|
||||
[ -f "$_PROJ/\${_BRANCH}-reviews.jsonl" ] && echo "REVIEWS: $(wc -l < "$_PROJ/\${_BRANCH}-reviews.jsonl" | tr -d ' ') entries"
|
||||
[ -f "$_PROJ/timeline.jsonl" ] && tail -5 "$_PROJ/timeline.jsonl"
|
||||
if [ -f "$_PROJ/timeline.jsonl" ]; then
|
||||
_LAST=$(grep "\\"branch\\":\\"\${_BRANCH}\\"" "$_PROJ/timeline.jsonl" 2>/dev/null | grep '"event":"completed"' | tail -1)
|
||||
[ -n "$_LAST" ] && echo "LAST_SESSION: $_LAST"
|
||||
_RECENT_SKILLS=$(grep "\\"branch\\":\\"\${_BRANCH}\\"" "$_PROJ/timeline.jsonl" 2>/dev/null | grep '"event":"completed"' | tail -3 | grep -o '"skill":"[^"]*"' | sed 's/"skill":"//;s/"//' | tr '\\n' ',')
|
||||
[ -n "$_RECENT_SKILLS" ] && echo "RECENT_PATTERN: $_RECENT_SKILLS"
|
||||
fi
|
||||
_LATEST_CP=$(find "$_PROJ/checkpoints" -name "*.md" -type f 2>/dev/null | xargs ls -t 2>/dev/null | head -1)
|
||||
[ -n "$_LATEST_CP" ] && echo "LATEST_CHECKPOINT: $_LATEST_CP"
|
||||
echo "--- END ARTIFACTS ---"
|
||||
fi
|
||||
\`\`\`
|
||||
|
||||
If artifacts are listed, read the newest useful one. If \`LAST_SESSION\` or \`LATEST_CHECKPOINT\` appears, give a 2-sentence welcome back summary. If \`RECENT_PATTERN\` clearly implies a next skill, suggest it once.`;
|
||||
}
|
||||
28
scripts/resolvers/preamble/generate-continuous-checkpoint.ts
Normal file
28
scripts/resolvers/preamble/generate-continuous-checkpoint.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
|
||||
|
||||
export function generateContinuousCheckpoint(): string {
|
||||
return `## Continuous Checkpoint Mode
|
||||
|
||||
If \`CHECKPOINT_MODE\` is \`"continuous"\`: auto-commit completed logical units with \`WIP:\` prefix.
|
||||
|
||||
Commit after new intentional files, completed functions/modules, verified bug fixes, and before long-running install/build/test commands.
|
||||
|
||||
Commit format:
|
||||
|
||||
\`\`\`
|
||||
WIP: <concise description of what changed>
|
||||
|
||||
[gstack-context]
|
||||
Decisions: <key choices made this step>
|
||||
Remaining: <what's left in the logical unit>
|
||||
Tried: <failed approaches worth recording> (omit if none)
|
||||
Skill: </skill-name-if-running>
|
||||
[/gstack-context]
|
||||
\`\`\`
|
||||
|
||||
Rules: stage only intentional files, NEVER \`git add -A\`, do not commit broken tests or mid-edit state, and push only if \`CHECKPOINT_PUSH\` is \`"true"\`. Do not announce each WIP commit.
|
||||
|
||||
\`/context-restore\` reads \`[gstack-context]\`; \`/ship\` squashes WIP commits into clean commits.
|
||||
|
||||
If \`CHECKPOINT_MODE\` is \`"explicit"\`: ignore this section unless a skill or user asks to commit.`;
|
||||
}
|
||||
12
scripts/resolvers/preamble/generate-lake-intro.ts
Normal file
12
scripts/resolvers/preamble/generate-lake-intro.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
|
||||
export function generateLakeIntro(): string {
|
||||
return `If \`LAKE_INTRO\` is \`no\`: say "gstack follows the **Boil the Lake** principle — do the complete thing when AI makes marginal cost near-zero. Read more: https://garryslist.org/posts/boil-the-ocean" Offer to open:
|
||||
|
||||
\`\`\`bash
|
||||
open https://garryslist.org/posts/boil-the-ocean
|
||||
touch ~/.gstack/.completeness-intro-seen
|
||||
\`\`\`
|
||||
|
||||
Only run \`open\` if yes. Always run \`touch\`.`;
|
||||
}
|
||||
105
scripts/resolvers/preamble/generate-preamble-bash.ts
Normal file
105
scripts/resolvers/preamble/generate-preamble-bash.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import type { TemplateContext } from '../types';
|
||||
import { getHostConfig } from '../../../hosts/index';
|
||||
|
||||
export function generatePreambleBash(ctx: TemplateContext): string {
|
||||
const hostConfig = getHostConfig(ctx.host);
|
||||
const runtimeRoot = hostConfig.usesEnvVars
|
||||
? `_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
|
||||
GSTACK_ROOT="$HOME/${hostConfig.globalRoot}"
|
||||
[ -n "$_ROOT" ] && [ -d "$_ROOT/${ctx.paths.localSkillRoot}" ] && GSTACK_ROOT="$_ROOT/${ctx.paths.localSkillRoot}"
|
||||
GSTACK_BIN="$GSTACK_ROOT/bin"
|
||||
GSTACK_BROWSE="$GSTACK_ROOT/browse/dist"
|
||||
GSTACK_DESIGN="$GSTACK_ROOT/design/dist"
|
||||
`
|
||||
: '';
|
||||
|
||||
return `## Preamble (run first)
|
||||
|
||||
\`\`\`bash
|
||||
${runtimeRoot}_UPD=$(${ctx.paths.binDir}/gstack-update-check 2>/dev/null || ${ctx.paths.localSkillRoot}/bin/gstack-update-check 2>/dev/null || true)
|
||||
[ -n "$_UPD" ] && echo "$_UPD" || true
|
||||
mkdir -p ~/.gstack/sessions
|
||||
touch ~/.gstack/sessions/"$PPID"
|
||||
_SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ')
|
||||
find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true
|
||||
_PROACTIVE=$(${ctx.paths.binDir}/gstack-config get proactive 2>/dev/null || echo "true")
|
||||
_PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no")
|
||||
_BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown")
|
||||
echo "BRANCH: $_BRANCH"
|
||||
_SKILL_PREFIX=$(${ctx.paths.binDir}/gstack-config get skill_prefix 2>/dev/null || echo "false")
|
||||
echo "PROACTIVE: $_PROACTIVE"
|
||||
echo "PROACTIVE_PROMPTED: $_PROACTIVE_PROMPTED"
|
||||
echo "SKILL_PREFIX: $_SKILL_PREFIX"
|
||||
source <(${ctx.paths.binDir}/gstack-repo-mode 2>/dev/null) || true
|
||||
REPO_MODE=\${REPO_MODE:-unknown}
|
||||
echo "REPO_MODE: $REPO_MODE"
|
||||
_LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no")
|
||||
echo "LAKE_INTRO: $_LAKE_SEEN"
|
||||
_TEL=$(${ctx.paths.binDir}/gstack-config get telemetry 2>/dev/null || true)
|
||||
_TEL_PROMPTED=$([ -f ~/.gstack/.telemetry-prompted ] && echo "yes" || echo "no")
|
||||
_TEL_START=$(date +%s)
|
||||
_SESSION_ID="$$-$(date +%s)"
|
||||
echo "TELEMETRY: \${_TEL:-off}"
|
||||
echo "TEL_PROMPTED: $_TEL_PROMPTED"
|
||||
_EXPLAIN_LEVEL=$(${ctx.paths.binDir}/gstack-config get explain_level 2>/dev/null || echo "default")
|
||||
if [ "$_EXPLAIN_LEVEL" != "default" ] && [ "$_EXPLAIN_LEVEL" != "terse" ]; then _EXPLAIN_LEVEL="default"; fi
|
||||
echo "EXPLAIN_LEVEL: $_EXPLAIN_LEVEL"
|
||||
_QUESTION_TUNING=$(${ctx.paths.binDir}/gstack-config get question_tuning 2>/dev/null || echo "false")
|
||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||
mkdir -p ~/.gstack/analytics
|
||||
if [ "$_TEL" != "off" ]; then
|
||||
echo '{"skill":"${ctx.skillName}","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||
fi
|
||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||
if [ -f "$_PF" ]; then
|
||||
if [ "$_TEL" != "off" ] && [ -x "${ctx.paths.binDir}/gstack-telemetry-log" ]; then
|
||||
${ctx.paths.binDir}/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true
|
||||
fi
|
||||
rm -f "$_PF" 2>/dev/null || true
|
||||
fi
|
||||
break
|
||||
done
|
||||
eval "$(${ctx.paths.binDir}/gstack-slug 2>/dev/null)" 2>/dev/null || true
|
||||
_LEARN_FILE="\${GSTACK_HOME:-$HOME/.gstack}/projects/\${SLUG:-unknown}/learnings.jsonl"
|
||||
if [ -f "$_LEARN_FILE" ]; then
|
||||
_LEARN_COUNT=$(wc -l < "$_LEARN_FILE" 2>/dev/null | tr -d ' ')
|
||||
echo "LEARNINGS: $_LEARN_COUNT entries loaded"
|
||||
if [ "$_LEARN_COUNT" -gt 5 ] 2>/dev/null; then
|
||||
${ctx.paths.binDir}/gstack-learnings-search --limit 3 2>/dev/null || true
|
||||
fi
|
||||
else
|
||||
echo "LEARNINGS: 0"
|
||||
fi
|
||||
${ctx.paths.binDir}/gstack-timeline-log '{"skill":"${ctx.skillName}","event":"started","branch":"'"$_BRANCH"'","session":"'"$_SESSION_ID"'"}' 2>/dev/null &
|
||||
_HAS_ROUTING="no"
|
||||
if [ -f CLAUDE.md ] && grep -q "## Skill routing" CLAUDE.md 2>/dev/null; then
|
||||
_HAS_ROUTING="yes"
|
||||
fi
|
||||
_ROUTING_DECLINED=$(${ctx.paths.binDir}/gstack-config get routing_declined 2>/dev/null || echo "false")
|
||||
echo "HAS_ROUTING: $_HAS_ROUTING"
|
||||
echo "ROUTING_DECLINED: $_ROUTING_DECLINED"
|
||||
_VENDORED="no"
|
||||
if [ -d ".claude/skills/gstack" ] && [ ! -L ".claude/skills/gstack" ]; then
|
||||
if [ -f ".claude/skills/gstack/VERSION" ] || [ -d ".claude/skills/gstack/.git" ]; then
|
||||
_VENDORED="yes"
|
||||
fi
|
||||
fi
|
||||
echo "VENDORED_GSTACK: $_VENDORED"
|
||||
echo "MODEL_OVERLAY: ${ctx.model ?? 'none'}"
|
||||
_CHECKPOINT_MODE=$(${ctx.paths.binDir}/gstack-config get checkpoint_mode 2>/dev/null || echo "explicit")
|
||||
_CHECKPOINT_PUSH=$(${ctx.paths.binDir}/gstack-config get checkpoint_push 2>/dev/null || echo "false")
|
||||
echo "CHECKPOINT_MODE: $_CHECKPOINT_MODE"
|
||||
echo "CHECKPOINT_PUSH: $_CHECKPOINT_PUSH"
|
||||
[ -n "$OPENCLAW_SESSION" ] && echo "SPAWNED_SESSION: true" || true${ctx.host === 'gbrain' || ctx.host === 'hermes' ? `
|
||||
if command -v gbrain &>/dev/null; then
|
||||
_BRAIN_JSON=$(gbrain doctor --fast --json 2>/dev/null || echo '{}')
|
||||
_BRAIN_SCORE=$(echo "$_BRAIN_JSON" | grep -o '"health_score":[0-9]*' | cut -d: -f2)
|
||||
_BRAIN_FAILS=$(echo "$_BRAIN_JSON" | grep -o '"status":"fail"' | wc -l | tr -d ' ')
|
||||
_BRAIN_WARNS=$(echo "$_BRAIN_JSON" | grep -o '"status":"warn"' | wc -l | tr -d ' ')
|
||||
echo "BRAIN_HEALTH: \${_BRAIN_SCORE:-unknown} (\${_BRAIN_FAILS:-0} failures, \${_BRAIN_WARNS:-0} warnings)"
|
||||
if [ "\${_BRAIN_SCORE:-100}" -lt 50 ] 2>/dev/null; then
|
||||
echo "$_BRAIN_JSON" | grep -o '"name":"[^"]*","status":"[^"]*","message":"[^"]*"' || true
|
||||
fi
|
||||
fi` : ''}
|
||||
\`\`\``;
|
||||
}
|
||||
21
scripts/resolvers/preamble/generate-proactive-prompt.ts
Normal file
21
scripts/resolvers/preamble/generate-proactive-prompt.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { TemplateContext } from '../types';
|
||||
|
||||
export function generateProactivePrompt(ctx: TemplateContext): string {
|
||||
return `If \`PROACTIVE_PROMPTED\` is \`no\` AND \`TEL_PROMPTED\` is \`yes\`: ask once:
|
||||
|
||||
> Let gstack proactively suggest skills, like /qa for "does this work?" or /investigate for bugs?
|
||||
|
||||
Options:
|
||||
- A) Keep it on (recommended)
|
||||
- B) Turn it off — I'll type /commands myself
|
||||
|
||||
If A: run \`${ctx.paths.binDir}/gstack-config set proactive true\`
|
||||
If B: run \`${ctx.paths.binDir}/gstack-config set proactive false\`
|
||||
|
||||
Always run:
|
||||
\`\`\`bash
|
||||
touch ~/.gstack/.proactive-prompted
|
||||
\`\`\`
|
||||
|
||||
Skip if \`PROACTIVE_PROMPTED\` is \`yes\`.`;
|
||||
}
|
||||
12
scripts/resolvers/preamble/generate-repo-mode-section.ts
Normal file
12
scripts/resolvers/preamble/generate-repo-mode-section.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
|
||||
export function generateRepoModeSection(): string {
|
||||
return `## Repo Ownership — See Something, Say Something
|
||||
|
||||
\`REPO_MODE\` controls how to handle issues outside your branch:
|
||||
- **\`solo\`** — You own everything. Investigate and offer to fix proactively.
|
||||
- **\`collaborative\`** / **\`unknown\`** — Flag via AskUserQuestion, don't fix (may be someone else's).
|
||||
|
||||
Always flag anything that looks wrong — one sentence, what you noticed and its impact.`;
|
||||
}
|
||||
|
||||
43
scripts/resolvers/preamble/generate-routing-injection.ts
Normal file
43
scripts/resolvers/preamble/generate-routing-injection.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import type { TemplateContext } from '../types';
|
||||
|
||||
export function generateRoutingInjection(ctx: TemplateContext): string {
|
||||
return `If \`HAS_ROUTING\` is \`no\` AND \`ROUTING_DECLINED\` is \`false\` AND \`PROACTIVE_PROMPTED\` is \`yes\`:
|
||||
Check if a CLAUDE.md file exists in the project root. If it does not exist, create it.
|
||||
|
||||
Use AskUserQuestion:
|
||||
|
||||
> gstack works best when your project's CLAUDE.md includes skill routing rules.
|
||||
|
||||
Options:
|
||||
- A) Add routing rules to CLAUDE.md (recommended)
|
||||
- B) No thanks, I'll invoke skills manually
|
||||
|
||||
If A: Append this section to the end of CLAUDE.md:
|
||||
|
||||
\`\`\`markdown
|
||||
|
||||
## Skill routing
|
||||
|
||||
When the user's request matches an available skill, invoke it via the Skill tool. When in doubt, invoke the skill.
|
||||
|
||||
Key routing rules:
|
||||
- Product ideas/brainstorming → invoke /office-hours
|
||||
- Strategy/scope → invoke /plan-ceo-review
|
||||
- Architecture → invoke /plan-eng-review
|
||||
- Design system/plan review → invoke /design-consultation or /plan-design-review
|
||||
- Full review pipeline → invoke /autoplan
|
||||
- Bugs/errors → invoke /investigate
|
||||
- QA/testing site behavior → invoke /qa or /qa-only
|
||||
- Code review/diff check → invoke /review
|
||||
- Visual polish → invoke /design-review
|
||||
- Ship/deploy/PR → invoke /ship or /land-and-deploy
|
||||
- Save progress → invoke /context-save
|
||||
- Resume context → invoke /context-restore
|
||||
\`\`\`
|
||||
|
||||
Then commit the change: \`git add CLAUDE.md && git commit -m "chore: add gstack skill routing rules to CLAUDE.md"\`
|
||||
|
||||
If B: run \`${ctx.paths.binDir}/gstack-config set routing_declined true\` and say they can re-enable with \`gstack-config set routing_declined false\`.
|
||||
|
||||
This only happens once per project. Skip if \`HAS_ROUTING\` is \`yes\` or \`ROUTING_DECLINED\` is \`true\`.`;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import type { TemplateContext } from '../types';
|
||||
|
||||
export function generateSearchBeforeBuildingSection(ctx: TemplateContext): string {
|
||||
return `## Search Before Building
|
||||
|
||||
Before building anything unfamiliar, **search first.** See \`${ctx.paths.skillRoot}/ETHOS.md\`.
|
||||
- **Layer 1** (tried and true) — don't reinvent. **Layer 2** (new and popular) — scrutinize. **Layer 3** (first principles) — prize above all.
|
||||
|
||||
**Eureka:** When first-principles reasoning contradicts conventional wisdom, name it and log:
|
||||
\`\`\`bash
|
||||
jq -n --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" --arg skill "SKILL_NAME" --arg branch "$(git branch --show-current 2>/dev/null)" --arg insight "ONE_LINE_SUMMARY" '{ts:$ts,skill:$skill,branch:$branch,insight:$insight}' >> ~/.gstack/analytics/eureka.jsonl 2>/dev/null || true
|
||||
\`\`\``;
|
||||
}
|
||||
|
||||
11
scripts/resolvers/preamble/generate-spawned-session-check.ts
Normal file
11
scripts/resolvers/preamble/generate-spawned-session-check.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
|
||||
export function generateSpawnedSessionCheck(): string {
|
||||
return `If \`SPAWNED_SESSION\` is \`"true"\`, you are running inside a session spawned by an
|
||||
AI orchestrator (e.g., OpenClaw). In spawned sessions:
|
||||
- Do NOT use AskUserQuestion for interactive prompts. Auto-choose the recommended option.
|
||||
- Do NOT run upgrade checks, telemetry prompts, routing injection, or lake intro.
|
||||
- Focus on completing the task and reporting results via prose output.
|
||||
- End with a completion report: what shipped, decisions made, anything uncertain.`;
|
||||
}
|
||||
|
||||
31
scripts/resolvers/preamble/generate-telemetry-prompt.ts
Normal file
31
scripts/resolvers/preamble/generate-telemetry-prompt.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import type { TemplateContext } from '../types';
|
||||
|
||||
export function generateTelemetryPrompt(ctx: TemplateContext): string {
|
||||
return `If \`TEL_PROMPTED\` is \`no\` AND \`LAKE_INTRO\` is \`yes\`: ask telemetry once via AskUserQuestion:
|
||||
|
||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
||||
|
||||
Options:
|
||||
- A) Help gstack get better! (recommended)
|
||||
- B) No thanks
|
||||
|
||||
If A: run \`${ctx.paths.binDir}/gstack-config set telemetry community\`
|
||||
|
||||
If B: ask follow-up:
|
||||
|
||||
> Anonymous mode sends only aggregate usage, no unique ID.
|
||||
|
||||
Options:
|
||||
- A) Sure, anonymous is fine
|
||||
- B) No thanks, fully off
|
||||
|
||||
If B→A: run \`${ctx.paths.binDir}/gstack-config set telemetry anonymous\`
|
||||
If B→B: run \`${ctx.paths.binDir}/gstack-config set telemetry off\`
|
||||
|
||||
Always run:
|
||||
\`\`\`bash
|
||||
touch ~/.gstack/.telemetry-prompted
|
||||
\`\`\`
|
||||
|
||||
Skip if \`TEL_PROMPTED\` is \`yes\`.`;
|
||||
}
|
||||
108
scripts/resolvers/preamble/generate-test-failure-triage.ts
Normal file
108
scripts/resolvers/preamble/generate-test-failure-triage.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
|
||||
|
||||
export function generateTestFailureTriage(): string {
|
||||
return `## Test Failure Ownership Triage
|
||||
|
||||
When tests fail, do NOT immediately stop. First, determine ownership:
|
||||
|
||||
### Step T1: Classify each failure
|
||||
|
||||
For each failing test:
|
||||
|
||||
1. **Get the files changed on this branch:**
|
||||
\`\`\`bash
|
||||
git diff origin/<base>...HEAD --name-only
|
||||
\`\`\`
|
||||
|
||||
2. **Classify the failure:**
|
||||
- **In-branch** if: the failing test file itself was modified on this branch, OR the test output references code that was changed on this branch, OR you can trace the failure to a change in the branch diff.
|
||||
- **Likely pre-existing** if: neither the test file nor the code it tests was modified on this branch, AND the failure is unrelated to any branch change you can identify.
|
||||
- **When ambiguous, default to in-branch.** It is safer to stop the developer than to let a broken test ship. Only classify as pre-existing when you are confident.
|
||||
|
||||
This classification is heuristic — use your judgment reading the diff and the test output. You do not have a programmatic dependency graph.
|
||||
|
||||
### Step T2: Handle in-branch failures
|
||||
|
||||
**STOP.** These are your failures. Show them and do not proceed. The developer must fix their own broken tests before shipping.
|
||||
|
||||
### Step T3: Handle pre-existing failures
|
||||
|
||||
Check \`REPO_MODE\` from the preamble output.
|
||||
|
||||
**If REPO_MODE is \`solo\`:**
|
||||
|
||||
Use AskUserQuestion:
|
||||
|
||||
> These test failures appear pre-existing (not caused by your branch changes):
|
||||
>
|
||||
> [list each failure with file:line and brief error description]
|
||||
>
|
||||
> Since this is a solo repo, you're the only one who will fix these.
|
||||
>
|
||||
> RECOMMENDATION: Choose A — fix now while the context is fresh. Completeness: 9/10.
|
||||
> A) Investigate and fix now (human: ~2-4h / CC: ~15min) — Completeness: 10/10
|
||||
> B) Add as P0 TODO — fix after this branch lands — Completeness: 7/10
|
||||
> C) Skip — I know about this, ship anyway — Completeness: 3/10
|
||||
|
||||
**If REPO_MODE is \`collaborative\` or \`unknown\`:**
|
||||
|
||||
Use AskUserQuestion:
|
||||
|
||||
> These test failures appear pre-existing (not caused by your branch changes):
|
||||
>
|
||||
> [list each failure with file:line and brief error description]
|
||||
>
|
||||
> This is a collaborative repo — these may be someone else's responsibility.
|
||||
>
|
||||
> RECOMMENDATION: Choose B — assign it to whoever broke it so the right person fixes it. Completeness: 9/10.
|
||||
> A) Investigate and fix now anyway — Completeness: 10/10
|
||||
> B) Blame + assign GitHub issue to the author — Completeness: 9/10
|
||||
> C) Add as P0 TODO — Completeness: 7/10
|
||||
> D) Skip — ship anyway — Completeness: 3/10
|
||||
|
||||
### Step T4: Execute the chosen action
|
||||
|
||||
**If "Investigate and fix now":**
|
||||
- Switch to /investigate mindset: root cause first, then minimal fix.
|
||||
- Fix the pre-existing failure.
|
||||
- Commit the fix separately from the branch's changes: \`git commit -m "fix: pre-existing test failure in <test-file>"\`
|
||||
- Continue with the workflow.
|
||||
|
||||
**If "Add as P0 TODO":**
|
||||
- If \`TODOS.md\` exists, add the entry following the format in \`review/TODOS-format.md\` (or \`.claude/skills/review/TODOS-format.md\`).
|
||||
- If \`TODOS.md\` does not exist, create it with the standard header and add the entry.
|
||||
- Entry should include: title, the error output, which branch it was noticed on, and priority P0.
|
||||
- Continue with the workflow — treat the pre-existing failure as non-blocking.
|
||||
|
||||
**If "Blame + assign GitHub issue" (collaborative only):**
|
||||
- Find who likely broke it. Check BOTH the test file AND the production code it tests:
|
||||
\`\`\`bash
|
||||
# Who last touched the failing test?
|
||||
git log --format="%an (%ae)" -1 -- <failing-test-file>
|
||||
# Who last touched the production code the test covers? (often the actual breaker)
|
||||
git log --format="%an (%ae)" -1 -- <source-file-under-test>
|
||||
\`\`\`
|
||||
If these are different people, prefer the production code author — they likely introduced the regression.
|
||||
- Create an issue assigned to that person (use the platform detected in Step 0):
|
||||
- **If GitHub:**
|
||||
\`\`\`bash
|
||||
gh issue create \\
|
||||
--title "Pre-existing test failure: <test-name>" \\
|
||||
--body "Found failing on branch <current-branch>. Failure is pre-existing.\\n\\n**Error:**\\n\`\`\`\\n<first 10 lines>\\n\`\`\`\\n\\n**Last modified by:** <author>\\n**Noticed by:** gstack /ship on <date>" \\
|
||||
--assignee "<github-username>"
|
||||
\`\`\`
|
||||
- **If GitLab:**
|
||||
\`\`\`bash
|
||||
glab issue create \\
|
||||
-t "Pre-existing test failure: <test-name>" \\
|
||||
-d "Found failing on branch <current-branch>. Failure is pre-existing.\\n\\n**Error:**\\n\`\`\`\\n<first 10 lines>\\n\`\`\`\\n\\n**Last modified by:** <author>\\n**Noticed by:** gstack /ship on <date>" \\
|
||||
-a "<gitlab-username>"
|
||||
\`\`\`
|
||||
- If neither CLI is available or \`--assignee\`/\`-a\` fails (user not in org, etc.), create the issue without assignee and note who should look at it in the body.
|
||||
- Continue with the workflow.
|
||||
|
||||
**If "Skip":**
|
||||
- Continue with the workflow.
|
||||
- Note in output: "Pre-existing test failure skipped: <test-name>"`;
|
||||
}
|
||||
|
||||
17
scripts/resolvers/preamble/generate-upgrade-check.ts
Normal file
17
scripts/resolvers/preamble/generate-upgrade-check.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { TemplateContext } from '../types';
|
||||
|
||||
export function generateUpgradeCheck(ctx: TemplateContext): string {
|
||||
return `If \`PROACTIVE\` is \`"false"\`, do not auto-invoke or proactively suggest skills. If a skill seems useful, ask: "I think /skillname might help here — want me to run it?"
|
||||
|
||||
If \`SKILL_PREFIX\` is \`"true"\`, suggest/invoke \`/gstack-*\` names. Disk paths stay \`${ctx.paths.skillRoot}/[skill-name]/SKILL.md\`.
|
||||
|
||||
If output shows \`UPGRADE_AVAILABLE <old> <new>\`: read \`${ctx.paths.skillRoot}/gstack-upgrade/SKILL.md\` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined).
|
||||
|
||||
If output shows \`JUST_UPGRADED <from> <to>\`: print "Running gstack v{to} (just updated!)". If \`SPAWNED_SESSION\` is true, skip feature discovery.
|
||||
|
||||
Feature discovery, max one prompt per session:
|
||||
- Missing \`${ctx.paths.skillRoot}/.feature-prompted-continuous-checkpoint\`: AskUserQuestion for Continuous checkpoint auto-commits. If accepted, run \`${ctx.paths.binDir}/gstack-config set checkpoint_mode continuous\`. Always touch marker.
|
||||
- Missing \`${ctx.paths.skillRoot}/.feature-prompted-model-overlay\`: inform "Model overlays are active. MODEL_OVERLAY shows the patch." Always touch marker.
|
||||
|
||||
After upgrade prompts, continue workflow.`;
|
||||
}
|
||||
29
scripts/resolvers/preamble/generate-vendoring-deprecation.ts
Normal file
29
scripts/resolvers/preamble/generate-vendoring-deprecation.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import type { TemplateContext } from '../types';
|
||||
|
||||
export function generateVendoringDeprecation(ctx: TemplateContext): string {
|
||||
return `If \`VENDORED_GSTACK\` is \`yes\`, warn once via AskUserQuestion unless \`~/.gstack/.vendoring-warned-$SLUG\` exists:
|
||||
|
||||
> This project has gstack vendored in \`.claude/skills/gstack/\`. Vendoring is deprecated.
|
||||
> Migrate to team mode?
|
||||
|
||||
Options:
|
||||
- A) Yes, migrate to team mode now
|
||||
- B) No, I'll handle it myself
|
||||
|
||||
If A:
|
||||
1. Run \`git rm -r .claude/skills/gstack/\`
|
||||
2. Run \`echo '.claude/skills/gstack/' >> .gitignore\`
|
||||
3. Run \`${ctx.paths.binDir}/gstack-team-init required\` (or \`optional\`)
|
||||
4. Run \`git add .claude/ .gitignore CLAUDE.md && git commit -m "chore: migrate gstack from vendored to team mode"\`
|
||||
5. Tell the user: "Done. Each developer now runs: \`cd ~/.claude/skills/gstack && ./setup --team\`"
|
||||
|
||||
If B: say "OK, you're on your own to keep the vendored copy up to date."
|
||||
|
||||
Always run (regardless of choice):
|
||||
\`\`\`bash
|
||||
eval "$(${ctx.paths.binDir}/gstack-slug 2>/dev/null)" 2>/dev/null || true
|
||||
touch ~/.gstack/.vendoring-warned-\${SLUG:-unknown}
|
||||
\`\`\`
|
||||
|
||||
If marker exists, skip.`;
|
||||
}
|
||||
29
scripts/resolvers/preamble/generate-voice-directive.ts
Normal file
29
scripts/resolvers/preamble/generate-voice-directive.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
|
||||
|
||||
export function generateVoiceDirective(tier: number): string {
|
||||
if (tier <= 1) {
|
||||
return `## Voice
|
||||
|
||||
Direct, concrete, builder-to-builder. Name the file, function, command, and user-visible impact. No filler.
|
||||
|
||||
No em dashes. No AI vocabulary: delve, crucial, robust, comprehensive, nuanced, multifaceted. Never corporate or academic. Short paragraphs. End with what to do.
|
||||
|
||||
The user has context you do not. Cross-model agreement is a recommendation, not a decision. The user decides.`;
|
||||
}
|
||||
|
||||
return `## Voice
|
||||
|
||||
GStack voice: Garry-shaped product and engineering judgment, compressed for runtime.
|
||||
|
||||
- Lead with the point. Say what it does, why it matters, and what changes for the builder.
|
||||
- Be concrete. Name files, functions, line numbers, commands, outputs, evals, and real numbers.
|
||||
- Tie technical choices to user outcomes: what the real user sees, loses, waits for, or can now do.
|
||||
- Be direct about quality. Bugs matter. Edge cases matter. Fix the whole thing, not the demo path.
|
||||
- Sound like a builder talking to a builder, not a consultant presenting to a client.
|
||||
- Never corporate, academic, PR, or hype. Avoid filler, throat-clearing, generic optimism, and founder cosplay.
|
||||
- No em dashes. No AI vocabulary: delve, crucial, robust, comprehensive, nuanced, multifaceted, furthermore, moreover, additionally, pivotal, landscape, tapestry, underscore, foster, showcase, intricate, vibrant, fundamental, significant.
|
||||
- The user has context you do not: domain knowledge, timing, relationships, taste. Cross-model agreement is a recommendation, not a decision. The user decides.
|
||||
|
||||
Good: "auth.ts:47 returns undefined when the session cookie expires. Users hit a white screen. Fix: add a null check and redirect to /login. Two lines."
|
||||
Bad: "I've identified a potential issue in the authentication flow that may cause problems under certain conditions."`;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { TemplateContext } from '../types';
|
||||
|
||||
export function generateWritingStyleMigration(ctx: TemplateContext): string {
|
||||
return `If \`WRITING_STYLE_PENDING\` is \`yes\`: ask once about writing style:
|
||||
|
||||
> v1 prompts are simpler: first-use jargon glosses, outcome-framed questions, shorter prose. Keep default or restore terse?
|
||||
|
||||
Options:
|
||||
- A) Keep the new default (recommended — good writing helps everyone)
|
||||
- B) Restore V0 prose — set \`explain_level: terse\`
|
||||
|
||||
If A: leave \`explain_level\` unset (defaults to \`default\`).
|
||||
If B: run \`${ctx.paths.binDir}/gstack-config set explain_level terse\`.
|
||||
|
||||
Always run (regardless of choice):
|
||||
\`\`\`bash
|
||||
rm -f ~/.gstack/.writing-style-prompt-pending
|
||||
touch ~/.gstack/.writing-style-prompted
|
||||
\`\`\`
|
||||
|
||||
Skip if \`WRITING_STYLE_PENDING\` is \`no\`.`;
|
||||
}
|
||||
37
scripts/resolvers/preamble/generate-writing-style.ts
Normal file
37
scripts/resolvers/preamble/generate-writing-style.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import type { TemplateContext } from '../types';
|
||||
|
||||
function loadJargonList(): string[] {
|
||||
const jargonPath = path.join(__dirname, '..', '..', 'jargon-list.json');
|
||||
try {
|
||||
const raw = fs.readFileSync(jargonPath, 'utf-8');
|
||||
const data = JSON.parse(raw);
|
||||
if (Array.isArray(data?.terms)) return data.terms.filter((t: unknown): t is string => typeof t === 'string');
|
||||
} catch {
|
||||
// Missing or malformed: fall back to empty list. Writing Style block still fires,
|
||||
// but with no terms to gloss — graceful degradation.
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export function generateWritingStyle(_ctx: TemplateContext): string {
|
||||
const terms = loadJargonList();
|
||||
const jargonBlock = terms.length > 0
|
||||
? `Jargon list, gloss on first use if the term appears:\n${terms.map(t => `- ${t}`).join('\n')}`
|
||||
: `Jargon list unavailable. Skip jargon glossing until \`scripts/jargon-list.json\` is restored.`;
|
||||
|
||||
return `## Writing Style (skip entirely if \`EXPLAIN_LEVEL: terse\` appears in the preamble echo OR the user's current message explicitly requests terse / no-explanations output)
|
||||
|
||||
Applies to AskUserQuestion, user replies, and findings. AskUserQuestion Format is structure; this is prose quality.
|
||||
|
||||
- Gloss curated jargon on first use per skill invocation, even if the user pasted the term.
|
||||
- Frame questions in outcome terms: what pain is avoided, what capability unlocks, what user experience changes.
|
||||
- Use short sentences, concrete nouns, active voice.
|
||||
- Close decisions with user impact: what the user sees, waits for, loses, or gains.
|
||||
- User-turn override wins: if the current message asks for terse / no explanations / just the answer, skip this section.
|
||||
- Terse mode (EXPLAIN_LEVEL: terse): no glosses, no outcome-framing layer, shorter responses.
|
||||
|
||||
${jargonBlock}
|
||||
`;
|
||||
}
|
||||
Reference in New Issue
Block a user