mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-02 23:14:33 +01:00
Merge remote-tracking branch 'origin/garrytan/opus55-wip-A' into garrytan/opus-5-5-prompt-audit
This commit is contained in:
commit
52064e6883
13 files changed
+126
-74
No files matched your search
@@ -92,9 +92,10 @@ shard processes, serial within each, packed by recorded per-file durations
|
||||
when `scripts/free-test-durations.json` exists — refresh occasionally with
|
||||
`bun run test:free --record-durations`; strict-output classification per
|
||||
shard: a shard without bun's terminal summary line FAILS — silent truncation
|
||||
cannot report green). The former trailing serial tree-mutating shard is
|
||||
gone: `TREE_MUTATING` is empty (gen-skill-docs has a main() guard and
|
||||
`--out-dir` renders every host, so tests render into mkdtemps — see
|
||||
cannot report green). `TREE_MUTATING` lists the files that still run in their
|
||||
own trailing serial shard (today only `test/bootstrap-retention.test.ts`, for
|
||||
host-wide procfs visibility); gen-skill-docs tests no longer need it (main()
|
||||
guard, and `--out-dir` renders every host into mkdtemps — see
|
||||
docs/TESTING_INTERNALS.md). Never type bare `bun test` for the suite: it
|
||||
walks the whole repo, loading paid eval files and missing the strict
|
||||
classifier.
|
||||
@@ -121,7 +122,18 @@ engine's own tests run everywhere.
|
||||
|
||||
New or changed tests follow the [test value bar](docs/test-value-bar.md): each one
|
||||
protects behavior a real regression would break, and contract tests (SKILL.md
|
||||
goldens, prompt bytes) stay. The bar's source is `scripts/resolvers/test-value.ts`.
|
||||
goldens, and prompt bytes that are a contract as defined below) stay. The bar's
|
||||
source is `scripts/resolvers/test-value.ts`.
|
||||
|
||||
In this repo, prompt bytes are a contract only when software reads them (a
|
||||
parser, hook, grader or another skill consumes the exact text) or a recorded
|
||||
eval shows the wording matters. Tests on skill templates and generated SKILL.md
|
||||
pin structure, step order, routing tables, machine-read markers and
|
||||
safety-critical lines; check safety lines case-insensitively, on meaning rather
|
||||
than capitals. Leave behavior to E2E cases and judges. Don't pin emphasis,
|
||||
capitalization, issue numbers, or a sentence a behavioral check already covers,
|
||||
and when a rewrite changes a pinned sentence, replace the pin with a structural
|
||||
or meaning-level check instead of pinning the new sentence.
|
||||
Projects tune `/ship`'s coverage gate with optional CLAUDE.md `## Test Coverage`
|
||||
keys, all absent by default: `Minimum:`, `Target:`, `Generation cap:` (default 5),
|
||||
`Base control:` (`auto` or `off`), `Base control budget:` (seconds, default 90) and
|
||||
@@ -188,7 +200,8 @@ or as a reference doc, (3) only compress carefully-tuned prose as a last resort
|
||||
cuts to the coverage audit, review army, or voice directive have real quality cost.
|
||||
|
||||
A second, harder ceiling guards the DISCOVERY surface: `test/catalog-budget.test.ts`
|
||||
caps the aggregate frontmatter `name` + `description` across all skills at 1,171
|
||||
caps the aggregate frontmatter `name` + `description` across all skills at
|
||||
`CATALOG_BUDGET_TOKEN_EQUIVALENTS` (1,194 today; each new skill ratchets it)
|
||||
token-equivalents (260-byte per-skill sub-cap), counted through the shared census
|
||||
in `test/helpers/skill-census.ts`. This one is enforced, not a warning — every
|
||||
host loads the full catalog every session, so growth here taxes every
|
||||
@@ -246,8 +259,9 @@ Rules:
|
||||
## Writing style (V1)
|
||||
|
||||
Default output from every tier-≥2 skill follows the Writing Style section in
|
||||
`scripts/resolvers/preamble.ts`: jargon glossed on first use (curated list in
|
||||
`scripts/jargon-list.json`, baked at gen-skill-docs time), questions framed in
|
||||
`scripts/resolvers/preamble/generate-writing-style.ts`: jargon glossed on first
|
||||
use (curated list in `scripts/jargon-list.json`, which the skill Reads at runtime
|
||||
on the first jargon term), questions framed in
|
||||
outcome terms ("what breaks for your users if...") not implementation terms,
|
||||
short sentences, decisions close with user impact. Power users who want the
|
||||
tighter V0 prose set `gstack-config set explain_level terse` (binary switch,
|
||||
@@ -702,19 +716,6 @@ regenerated SKILL.md shifts prompt context.
|
||||
|
||||
"Pre-existing" without receipts is a lazy claim. Prove it or don't say it.
|
||||
|
||||
## Long-running tasks: don't give up
|
||||
|
||||
When running evals, E2E tests, or any long-running background task, **poll until
|
||||
completion**. Use `sleep 180 && echo "ready"` + `TaskOutput` in a loop every 3
|
||||
minutes. Never switch to blocking mode and give up when the poll times out. Never
|
||||
say "I'll be notified when it completes" and stop checking — keep the loop going
|
||||
until the task finishes or the user tells you to stop.
|
||||
|
||||
The full E2E suite can take 30-45 minutes. That's 10-15 polling cycles. Do all of
|
||||
them. Report progress at each check (which tests passed, which are running, any
|
||||
failures so far). The user wants to see the run complete, not a promise that
|
||||
you'll check later.
|
||||
|
||||
## Running evals as an agent: always detach (SIGTERM-proof)
|
||||
|
||||
When **you (an agent/harness)** launch a long eval/benchmark run, run it through
|
||||
@@ -737,7 +738,8 @@ the run can also die to idle-sleep. `gstack-detach` fixes both: a fresh session
|
||||
(stray `claude`/`codex` grandchildren included), a per-shard
|
||||
`GSTACK_EVAL_DIR=<evalDir>/shards/<slug>/` honored by the `EvalCollector`
|
||||
constructor, and an aggregate that separates failed vs timed-out vs
|
||||
never-started shards — the detach timeouts (47340s gate / 67380s periodic;
|
||||
never-started shards — the detach timeouts (the `--timeout` values on
|
||||
package.json's `eval:bg:gate` / `eval:bg:periodic`;
|
||||
floor enforced against the live shard census by
|
||||
test/eval-detach-timeout-floor.test.ts)
|
||||
are sized against worst-case shard wall clock. `EVALS_JOBS` sets the shard
|
||||
@@ -750,7 +752,10 @@ the run can also die to idle-sleep. `gstack-detach` fixes both: a fresh session
|
||||
- Then **poll the printed logfile** with a death-aware watcher: break on the
|
||||
guaranteed `### gstack-detach EXIT=<code> ###` sentinel (success AND failure are
|
||||
both marked, so silence is never mistaken for success). The detached run survives
|
||||
even if your watcher gets reaped, so re-checking the log always works.
|
||||
even if your watcher gets reaped, so re-checking the log always works. Keep
|
||||
checking until the sentinel appears or the user tells you to stop; a long run is
|
||||
expected, and a promise to check later is not a result. At each check, report
|
||||
which tests passed, which are still running, and any failures so far.
|
||||
- Why the lock: a shared dev box with several Conductor worktrees will rate-limit
|
||||
the model API if two eval suites run at once (15-way concurrency each), which
|
||||
mass-times-out E2E tests. The lock makes the second run WAIT, not collide.
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# gstack Builder Ethos
|
||||
|
||||
These are the principles that shape how gstack thinks, recommends, and builds.
|
||||
They are injected into every workflow skill's preamble automatically. They
|
||||
reflect what we believe about building software in 2026.
|
||||
Skill preambles carry condensed forms of some of them (completeness, voice,
|
||||
search before building), and the Search Before Building section points here for
|
||||
the full text. They reflect what we believe about building software in 2026.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+7
-5
@@ -40,14 +40,16 @@ This is the gstack router. Its one job is to send the request to the right skill
|
||||
|
||||
Best-effort, record which way you routed (never block on it). Set `ROUTE_OUTCOME` to
|
||||
`browse` (sent to /browse), `routed` (sent to another skill), or `direct` (answered
|
||||
directly, no skill matched):
|
||||
directly, no skill matched), and replace `SESSION_ID` with the value the skill-start
|
||||
output echoed:
|
||||
```bash
|
||||
~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type route --skill gstack --outcome ROUTE_OUTCOME --session-id "$_SESSION_ID" 2>/dev/null || true
|
||||
~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type route --skill gstack --outcome ROUTE_OUTCOME --session-id "SESSION_ID" 2>/dev/null || true
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `false`: do NOT proactively invoke or suggest other gstack skills during
|
||||
this session. Only run skills the user explicitly invokes. This preference persists across
|
||||
sessions via `gstack-config`.
|
||||
If `PROACTIVE` is `false`: do not proactively invoke or suggest other gstack skills during
|
||||
this session, including by asking whether to run one ("want me to run /X?"). Only run
|
||||
skills the user explicitly invokes. This preference persists across sessions via
|
||||
`gstack-config`.
|
||||
|
||||
If `PROACTIVE` is `true` (default): **invoke the Skill tool** when the user's request
|
||||
matches a skill's purpose. Do NOT answer directly when a skill exists for the task.
|
||||
|
||||
@@ -6,5 +6,6 @@ turns out to be unnecessary, mark it skipped with a one-line reason.
|
||||
non-trivial new features), briefly state your approach before executing. This lets
|
||||
the user course-correct cheaply instead of mid-flight.
|
||||
|
||||
**Dedicated tools over Bash.** Prefer Read, Edit, Write, Glob, Grep over shell
|
||||
equivalents (cat, sed, find, grep). The dedicated tools are cheaper and clearer.
|
||||
**Dedicated tools over Bash.** Prefer the host's dedicated file tools (Read, Edit,
|
||||
Write, and its search tools when it has them) over shell equivalents (cat, sed,
|
||||
find, grep). The dedicated tools are cheaper and clearer.
|
||||
+20
-5
@@ -34,14 +34,30 @@ export type Model = (typeof ALL_MODEL_NAMES)[number];
|
||||
/**
|
||||
* Resolve a model argument from CLI input to a known Model family.
|
||||
*
|
||||
* Input is trimmed and lowercased, and one trailing `[...]` context marker
|
||||
* (for example `claude-sonnet-5[1m]`) is stripped before matching.
|
||||
*
|
||||
* Precedence rules:
|
||||
* 1. Exact match against ALL_MODEL_NAMES → return as-is. This is the ONLY
|
||||
* path that selects `gpt-5.6-sol` — Sol is intentionally exact-only.
|
||||
* It is also how to force a model-pinned profile: `--model opus-4-7`,
|
||||
* `--model sonnet-5`, and so on.
|
||||
* 2. Family heuristics for common variants:
|
||||
* - `gpt-5.4-mini`, `gpt-5.4-turbo`, `gpt-5.4-*` → `gpt-5.4`
|
||||
* - `gpt-*` (anything else GPT, including other 5.6 variants) → `gpt`
|
||||
* - `o3`, `o4`, `o4-mini`, `o1`, `o1-mini`, `o1-pro` → `o-series`
|
||||
* - `claude-*` (sonnet, opus, haiku, any version) → `claude`
|
||||
* - Model-pinned Claude families (Opus 4.7, Opus 4.8, Sonnet 5) use an
|
||||
* allowlist: the bare ID (`claude-<family>-<major>`), the bare ID plus
|
||||
* an 8-digit date snapshot (`-YYYYMMDD`), and the bare ID plus `-latest`
|
||||
* keep the pinned profile. Any other suffix, including a point release
|
||||
* with or without a date (`claude-opus-4-7-1`, `claude-sonnet-5-5`,
|
||||
* `claude-sonnet-5-5-20261001`), falls to `claude`: a newer model should
|
||||
* not read nudges written for an older one.
|
||||
* - Fable is the exception: any `claude-fable-5-*` ID keeps `fable-5`,
|
||||
* because `claude-fable-5-1` is CLAUDE_FRONTIER_EVAL_MODEL
|
||||
* (lib/eval-model.ts) and moving it would shift every eval baseline.
|
||||
* The exception ends when CLAUDE_FRONTIER_EVAL_MODEL changes.
|
||||
* - Any other `claude-*` (including `claude-opus-5-5`) → `claude`
|
||||
* - `gemini-*` (2.5-pro, flash, etc.) → `gemini`
|
||||
* 3. Unknown input → returns null (caller decides: error, or fall back).
|
||||
*
|
||||
@@ -50,7 +66,7 @@ export type Model = (typeof ALL_MODEL_NAMES)[number];
|
||||
* normalizes CLI input to a family name.
|
||||
*/
|
||||
export function resolveModel(input: string): Model | null {
|
||||
const s = input.trim();
|
||||
const s = input.trim().toLowerCase().replace(/\[[^\]]*\]$/, '').trim();
|
||||
if (!s) return null;
|
||||
|
||||
// Exact match first
|
||||
@@ -67,10 +83,9 @@ export function resolveModel(input: string): Model | null {
|
||||
if (/^gpt-5\.4(-|$)/.test(s)) return 'gpt-5.4';
|
||||
if (/^gpt(-|$)/.test(s)) return 'gpt';
|
||||
if (/^o[0-9]+(-|$)/.test(s)) return 'o-series';
|
||||
if (/^claude-opus-4-7(-|$)/.test(s)) return 'opus-4-7';
|
||||
const pinned = /^claude-(opus-4-7|opus-4-8|sonnet-5)(-\d{8}|-latest)?$/.exec(s);
|
||||
if (pinned) return pinned[1] as Model;
|
||||
if (/^claude-fable-5(-|$)/.test(s)) return 'fable-5';
|
||||
if (/^claude-opus-4-8(-|$)/.test(s)) return 'opus-4-8';
|
||||
if (/^claude-sonnet-5(-|$)/.test(s)) return 'sonnet-5';
|
||||
if (/^claude(-|$)/.test(s)) return 'claude';
|
||||
if (/^gemini(-|$)/.test(s)) return 'gemini';
|
||||
|
||||
|
||||
@@ -48,13 +48,12 @@ Escalate after 3 failed attempts, uncertain security-sensitive changes, or scope
|
||||
|
||||
## Operational Self-Improvement
|
||||
|
||||
Before completing, review the session for durable learnings and log each one —
|
||||
this step ALWAYS runs, it is not conditional on something feeling noteworthy
|
||||
(#2402: 43 of 44 learnings came from explicit /learn because "if you
|
||||
discovered" read as optional). A durable learning is a project quirk, command
|
||||
fix, pitfall, or pattern that would save 5+ minutes in a future session. If
|
||||
the review genuinely surfaces none, state "No durable learnings this session"
|
||||
in your completion summary — an explicit empty result, not a skipped step.
|
||||
Before completing, review the session for durable learnings and log each one.
|
||||
The review runs every time, not only when something felt noteworthy. A durable
|
||||
learning is a project quirk, command fix, pitfall, or pattern that would save
|
||||
5+ minutes in a future session. If the review genuinely surfaces none, state
|
||||
"No durable learnings this session" in your completion summary — an explicit
|
||||
empty result, not a skipped step.
|
||||
|
||||
\`\`\`bash
|
||||
${ctx.paths.binDir}/gstack-learnings-log '{"skill":"SKILL_NAME","type":"operational","key":"SHORT_KEY","insight":"DESCRIPTION","confidence":N,"source":"observed"}'
|
||||
|
||||
@@ -4,7 +4,7 @@ export function generateContextHealth(ctx?: TemplateContext): string {
|
||||
if (ctx?.explainLevel === 'terse') return '';
|
||||
return `## Context Health (soft directive)
|
||||
|
||||
During long-running skill sessions, periodically write a brief \`[PROGRESS]\` summary: done, next, surprises.
|
||||
During long-running skill sessions, when you finish a phase or change direction, tell the user in a sentence or two what is done, what is next, and anything surprising.
|
||||
|
||||
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.`;
|
||||
}
|
||||
|
||||
@@ -31,34 +31,18 @@ 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
|
||||
Ask with AskUserQuestion in the AskUserQuestion Format. List each failure with file:line and a brief error description, say the failures appear pre-existing (not caused by this branch), and say that in a solo repo nobody else will fix them. Options, with A recommended because the context is fresh:
|
||||
- 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
|
||||
Ask with AskUserQuestion in the AskUserQuestion Format. List each failure the same way, and say that in a collaborative repo these may be someone else's responsibility. Options, with B recommended so the person who broke it fixes it:
|
||||
- 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
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import type { TemplateContext } from '../types';
|
||||
* the fence prose scopes and the model follows.
|
||||
*/
|
||||
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?"
|
||||
return `If \`PROACTIVE\` is \`false\`, do not auto-invoke or suggest skills, including by asking whether to run one. Only run skills the user explicitly invokes.
|
||||
|
||||
If \`SKILL_PREFIX\` is \`"true"\`, suggest/invoke \`/gstack-*\` names. Disk paths stay \`${ctx.paths.skillRoot}/[skill-name]/SKILL.md\`.`;
|
||||
}
|
||||
@@ -31,6 +31,6 @@ Applies to AskUserQuestion, user replies, and findings. AskUserQuestion Format i
|
||||
- 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.
|
||||
|
||||
Curated jargon list lives at \`${jargonPath}\` (80+ terms). On the first jargon term you encounter this session, Read that file once; treat the \`terms\` array as the canonical list. The list is repo-owned and may grow between releases.
|
||||
Curated jargon list lives at \`${jargonPath}\`. On the first jargon term you encounter this session, Read that file once; treat the \`terms\` array as the canonical list. The list is repo-owned and may grow between releases.
|
||||
`;
|
||||
}
|
||||
@@ -640,7 +640,7 @@ describe('gen-skill-docs', () => {
|
||||
// Should not have Edit, Glob, or Grep in allowed-tools.
|
||||
// Scope to frontmatter (between the first two --- lines) — the body can
|
||||
// legitimately mention these tool names in prose (e.g., Claude model
|
||||
// overlay says "prefer Read, Edit, Write, Glob, Grep over Bash").
|
||||
// overlay names the host's dedicated file and search tools).
|
||||
const fmMatch = qaOnlyContent.match(/^---\n([\s\S]*?)\n---/);
|
||||
expect(fmMatch).not.toBeNull();
|
||||
const frontmatter = fmMatch![1];
|
||||
@@ -4485,7 +4485,7 @@ describe('plan-mode-info resolver (handshake-replacement)', () => {
|
||||
'utf-8',
|
||||
);
|
||||
const planModeIdx = content.indexOf(PLAN_MODE_INFO_MARKER);
|
||||
const upgradeIdx = content.indexOf('If `PROACTIVE` is `"false"`');
|
||||
const upgradeIdx = content.search(/If `PROACTIVE` is `"?false"?`/i);
|
||||
expect(planModeIdx).toBeGreaterThan(0);
|
||||
expect(upgradeIdx).toBeGreaterThan(0);
|
||||
expect(planModeIdx).toBeLessThan(upgradeIdx);
|
||||
|
||||
@@ -10,6 +10,7 @@ import type { TemplateContext } from '../scripts/resolvers/types';
|
||||
import { HOST_PATHS } from '../scripts/resolvers/types';
|
||||
import { generateModelOverlay } from '../scripts/resolvers/model-overlay';
|
||||
import { resolveModel } from '../scripts/models';
|
||||
import { CLAUDE_FRONTIER_EVAL_MODEL } from '../lib/eval-model';
|
||||
import { readOverlay } from '../scripts/resolvers/model-overlay';
|
||||
import { generateCompletenessSection } from '../scripts/resolvers/preamble/generate-completeness-section';
|
||||
import { generateSetupCommand } from '../scripts/resolvers/utility';
|
||||
@@ -368,3 +369,47 @@ describe('Sonnet 5 overlay — family nudges', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('model-routing-claude', () => {
|
||||
test.each([
|
||||
['claude-opus-4-7', 'opus-4-7'],
|
||||
['claude-opus-4-7-20260416', 'opus-4-7'],
|
||||
['claude-opus-4-7-latest', 'opus-4-7'],
|
||||
['claude-opus-4-7-1', 'claude'],
|
||||
['claude-opus-4-7-1-20260601', 'claude'],
|
||||
['claude-opus-4-8', 'opus-4-8'],
|
||||
['claude-opus-4-8-20260701', 'opus-4-8'],
|
||||
['claude-opus-4-8-latest', 'opus-4-8'],
|
||||
['claude-opus-4-8-1', 'claude'],
|
||||
['claude-sonnet-5', 'sonnet-5'],
|
||||
['claude-sonnet-5-20260115', 'sonnet-5'],
|
||||
['claude-sonnet-5-latest', 'sonnet-5'],
|
||||
['claude-sonnet-5-5', 'claude'],
|
||||
['claude-sonnet-5-5-20261001', 'claude'],
|
||||
['claude-sonnet-5-10', 'claude'],
|
||||
['claude-sonnet-5-latest-20260101', 'claude'],
|
||||
['claude-opus-5-5', 'claude'],
|
||||
['claude-fable-5', 'fable-5'],
|
||||
['claude-fable-5-1', 'fable-5'],
|
||||
])('%s routes to %s', (input, expected) => {
|
||||
expect(resolveModel(input)).toBe(expected);
|
||||
});
|
||||
|
||||
test('input is lowercased and one trailing [...] marker is stripped', () => {
|
||||
expect(resolveModel('CLAUDE-SONNET-5')).toBe('sonnet-5');
|
||||
expect(resolveModel('claude-sonnet-5[1m]')).toBe('sonnet-5');
|
||||
expect(resolveModel('claude-opus-5-5[1m]')).toBe('claude');
|
||||
});
|
||||
|
||||
test('family names force the pinned profile', () => {
|
||||
expect(resolveModel('opus-4-7')).toBe('opus-4-7');
|
||||
expect(resolveModel('opus-4-8')).toBe('opus-4-8');
|
||||
expect(resolveModel('sonnet-5')).toBe('sonnet-5');
|
||||
});
|
||||
|
||||
test('Fable prefix exception holds only while the frontier eval model is a Fable 5 point release', () => {
|
||||
if (resolveModel('claude-fable-5-99') === 'fable-5') {
|
||||
expect(CLAUDE_FRONTIER_EVAL_MODEL.startsWith('claude-fable-5-')).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -100,7 +100,7 @@ describe('terse build — per-resolver behavior', () => {
|
||||
test('default: emits full section', () => {
|
||||
const out = generateContextHealth(makeCtx('default'));
|
||||
expect(out).toContain('## Context Health');
|
||||
expect(out).toContain('PROGRESS');
|
||||
expect(out.replace(/^## Context Health.*$/m, '').trim().length).toBeGreaterThan(80);
|
||||
});
|
||||
|
||||
test('terse: returns empty string', () => {
|
||||
|
||||
Reference in new issue
Block a user