@tekyzinc/gsd-t 5.2.10 → 5.2.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +2 -3
- package/bin/gsd-t-doc-marker.cjs +19 -1
- package/bin/gsd-t-env-registry.cjs +5 -7
- package/commands/gsd-t-quick.md +2 -2
- package/commands/gsd-t-test-sync.md +1 -1
- package/package.json +1 -1
- package/templates/CLAUDE-project.md +1 -1
- package/bin/model-selector.js +0 -234
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to GSD-T are documented here. Updated with each release.
|
|
4
4
|
|
|
5
|
+
## [5.2.11] - 2026-07-22
|
|
6
|
+
|
|
7
|
+
### Changed — architect-overview cleanup (dead tool, shared reader, archived contracts)
|
|
8
|
+
|
|
9
|
+
Applied the three safe findings from a `/gsd-t-architect` framework-overview pass, and recorded a correction to a wrong finding.
|
|
10
|
+
|
|
11
|
+
- Deleted `bin/model-selector.js` + `test/model-selector.test.js` — the M35 model picker with zero live callers, superseded by M85's `bin/gsd-t-model-tier-policy.cjs`. Scrubbed 3 stale `selectModel()` doc references (`gsd-t-quick.md`, `gsd-t-test-sync.md`, `templates/CLAUDE-project.md`, `README.md`).
|
|
12
|
+
- Extracted a shared marker-block *reader* (`extractMarkedDocBlock`) into `bin/gsd-t-doc-marker.cjs` and rewired `bin/gsd-t-env-registry.cjs::extractBlock` to it — finishing the M102 writer/reader extraction. The verify-gate's independent copy is left intact on purpose (defensive-independence invariant — a writer bug must not disable the gate).
|
|
13
|
+
- Archived 13 stale `mXX-integration-points.md` contracts to `.gsd-t/milestones/_integration-points-archive/`. `m94-integration-points.md` was RESTORED — 3 live M94 tests read it by path (a live fixture, not archaeology).
|
|
14
|
+
|
|
15
|
+
Recorded in `progress.md`: the overview's "graph subsystem is broken sprawl" finding was a **false negative** — it checked the pre-M99 path `.gsd-t/graph.db`; the real store moved to `.gsd-t/graphDB/graph.db` (M99 consolidation, already shipped, with a canonical resolver). The graph builds and works in every project. No code change — the correction is the deliverable.
|
|
16
|
+
|
|
17
|
+
Suite: 3020/3020 pass, 13 skipped.
|
|
18
|
+
|
|
5
19
|
## [5.2.10] - 2026-07-17
|
|
6
20
|
|
|
7
21
|
### Added — Environment Registry (M102): the committed, secret-free connection map
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# GSD-T: Contract-Driven Development for Claude Code
|
|
2
2
|
|
|
3
|
-
**v5.2.
|
|
3
|
+
**v5.2.11** - A methodology for reliable, parallelizable development using Claude Code with optional Agent Teams support.
|
|
4
4
|
|
|
5
5
|
**Eliminates context rot** — task-level fresh dispatch (one subagent per task, ~10-20% context each) means compaction never triggers.
|
|
6
6
|
**Compaction-proof debug loops** — `gsd-t headless --debug-loop` runs test-fix-retest cycles as separate `claude -p` sessions. A JSONL debug ledger persists all hypothesis/fix/learning history across fresh sessions. Anti-repetition preamble injection prevents retrying failed hypotheses. Escalation tiers (sonnet → opus → human) and a hard iteration ceiling enforced externally.
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
**Rigorous User-Journey Coverage + Anti-Drift Test Quality** — `bin/journey-coverage.cjs` regex listener detector + `gsd-t check-coverage` CLI + `scripts/hooks/pre-commit-journey-coverage` commit gate blocks viewer-source commits when uncovered listeners exist. Journey specs in `e2e/journeys/` use functional assertions (zero `toBeVisible`-only tests) per the E2E Test Quality Standard in CLAUDE.md.
|
|
19
19
|
**Universal Playwright Bootstrap + Deterministic UI Enforcement (M50)** — three executable enforcement layers: (1) `bin/playwright-bootstrap.cjs` + `bin/ui-detection.cjs` - idempotent installer detects package manager, installs `@playwright/test` + chromium, scaffolds `e2e/`; (2) Workflow runtime runs `playwright-bootstrap.cjs::installPlaywright()` before any E2E stage when `hasUI && !hasPlaywright`; install failure halts with `blocked-needs-human`; (3) `scripts/hooks/pre-commit-playwright-gate` (opt-in via `gsd-t doctor --install-hooks`) blocks viewer-source commits when staged files are newer than `.gsd-t/.last-playwright-pass`. The `gsd-t setup-playwright [path]` subcommand handles manual install.
|
|
20
20
|
**Visualizer (`/gsd-t-visualize`)** — launches a real-time browser dashboard with dual-pane view: top pane streams the main session, bottom pane streams whichever spawn the user clicks. Left rail shows Live Spawns and Completed (last 100 spawns, status-badged, collapsible). Right rail shows Spawn Plan / Parallelism / Tool Cost. Powered by `gsd-t-stream-feed-server.js` + `gsd-t-dashboard.html`.
|
|
21
|
-
**Surgical model selection** —
|
|
21
|
+
**Surgical model selection** — models are assigned haiku/sonnet/opus/fable per phase. **M85 single-source tier policy:** `bin/gsd-t-model-tier-policy.cjs` is the SINGLE source of truth for model-tier assignments; the 5 highest-leverage stages (solution-space probe, partition probe, competition judge, pre-mortem, Red Team) run on `fable` (Claude Fable 5, tier above Opus); competition producers stay `opus` (M82 blindness); debug escalates cycle-1→opus, cycle-2→fable. Drift is mechanically enforced by the M71-family lint (`test/m85-workflow-tier-policy-lint.test.js`). **M86 model profiles:** `bin/gsd-t-model-profile.cjs` adds a per-project SECOND dimension — three named profiles (`standard` / `pro` / `premium`) that control which stages run on Fable vs. Opus/Sonnet (see [Model Profiles](#model-profiles) below).
|
|
22
22
|
**Token Telemetry** — `gsd-t-calibration-hook.js` records token usage per spawn to `.gsd-t/token-metrics.jsonl` (18-field rows). `gsd-t-token-aggregator.js` aggregates across tasks for the `/gsd-t-metrics` view. Use the native Claude Code `/context` command for live in-session context percentage.
|
|
23
23
|
**Quality North Star** — projects define a `## Quality North Star` section in CLAUDE.md (1–3 sentences, e.g., "This is a published npm library. Every public API must be intuitive and backward-compatible."). `gsd-t-init` auto-detects preset (library/web-app/cli) from package.json signals; `gsd-t-setup` configures it for existing projects. Subagents read it as a quality lens; absent = silent skip (backward compatible).
|
|
24
24
|
**Design Brief Artifact** — during partition, UI/frontend projects (React, Vue, Svelte, Flutter, Tailwind) automatically get `.gsd-t/contracts/design-brief.md` with color palette, typography, spacing system, component patterns, and tone/voice. Non-UI projects skip silently. User-customized briefs are preserved. Referenced in plan phase for visual consistency.
|
|
@@ -466,7 +466,6 @@ get-stuff-done-teams/
|
|
|
466
466
|
│ ├── graph-*.js # Code graph engine (CGC/Neo4j integration)
|
|
467
467
|
│ ├── journey-coverage.cjs # Listener detector + coverage gap reporting
|
|
468
468
|
│ ├── playwright-bootstrap.cjs # Idempotent Playwright installer
|
|
469
|
-
│ ├── model-selector.js # Phase-to-model assignment (haiku/sonnet/opus/fable)
|
|
470
469
|
│ ├── gsd-t-model-tier-policy.cjs # M85: single-source tier policy (haiku/sonnet/opus/fable), resolver CLI
|
|
471
470
|
│ ├── rule-engine.js # Declarative failure-pattern rules
|
|
472
471
|
│ ├── patch-lifecycle.js # 5-stage patch candidate→graduated lifecycle
|
package/bin/gsd-t-doc-marker.cjs
CHANGED
|
@@ -62,4 +62,22 @@ function upsertMarkedDocBlock(targetPath, startMarker, endMarker, block) {
|
|
|
62
62
|
return targetPath;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
// Read the marked block back OUT of `content`. Returns the slice from the START
|
|
66
|
+
// marker up to (but NOT including) the END marker — i.e. the block body a parser
|
|
67
|
+
// walks line-by-line — or null if either marker is absent. The complement of
|
|
68
|
+
// upsertMarkedDocBlock's write. (Shared so a reader isn't re-implemented per
|
|
69
|
+
// caller — the env-registry writer/reader used to copy this inline.)
|
|
70
|
+
//
|
|
71
|
+
// NOTE: the verify GATE (gsd-t-env-registry-check.cjs) deliberately does NOT use
|
|
72
|
+
// this — it re-implements the extract independently so a bug here cannot disable
|
|
73
|
+
// the gate (M102 defensive-independence invariant). That duplication is
|
|
74
|
+
// load-bearing; do not "consolidate" it.
|
|
75
|
+
function extractMarkedDocBlock(content, startMarker, endMarker) {
|
|
76
|
+
if (typeof content !== "string" || !startMarker || !endMarker) return null;
|
|
77
|
+
if (!content.includes(startMarker) || !content.includes(endMarker)) return null;
|
|
78
|
+
const start = content.indexOf(startMarker);
|
|
79
|
+
const end = content.indexOf(endMarker);
|
|
80
|
+
return content.slice(start, end);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = { upsertMarkedDocBlock, extractMarkedDocBlock };
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
const fs = require("fs");
|
|
26
26
|
const path = require("path");
|
|
27
|
-
const { upsertMarkedDocBlock } = require("./gsd-t-doc-marker.cjs");
|
|
27
|
+
const { upsertMarkedDocBlock, extractMarkedDocBlock } = require("./gsd-t-doc-marker.cjs");
|
|
28
28
|
|
|
29
29
|
// ─── Markers + schema ────────────────────────────────────────────────────────
|
|
30
30
|
|
|
@@ -851,13 +851,11 @@ function infraDocPath(projectDir) {
|
|
|
851
851
|
return path.join(projectDir, "docs", "infrastructure.md");
|
|
852
852
|
}
|
|
853
853
|
|
|
854
|
+
// Uses the ONE shared reader from gsd-t-doc-marker.cjs (complement of the shared
|
|
855
|
+
// writer this file already calls) — no inline re-implementation. The verify gate
|
|
856
|
+
// keeps its OWN independent copy on purpose (defensive independence).
|
|
854
857
|
function extractBlock(content) {
|
|
855
|
-
|
|
856
|
-
return null;
|
|
857
|
-
}
|
|
858
|
-
const start = content.indexOf(ENV_MARKER_START);
|
|
859
|
-
const end = content.indexOf(ENV_MARKER_END);
|
|
860
|
-
return content.slice(start, end);
|
|
858
|
+
return extractMarkedDocBlock(content, ENV_MARKER_START, ENV_MARKER_END);
|
|
861
859
|
}
|
|
862
860
|
|
|
863
861
|
function splitRow(line) {
|
package/commands/gsd-t-quick.md
CHANGED
|
@@ -25,8 +25,8 @@ Spawn path is `autoSpawnHeadless({command, spawnType, projectDir, sessionContext
|
|
|
25
25
|
|
|
26
26
|
Per `.gsd-t/contracts/model-selection-contract.md` v1.0.0.
|
|
27
27
|
|
|
28
|
-
- **Default**: `sonnet` (`
|
|
29
|
-
- **Mechanical subroutines** (demote to `haiku`): test runners
|
|
28
|
+
- **Default**: `sonnet` (per `bin/gsd-t-model-tier-policy.cjs`, phase `quick`) — routine one-off task.
|
|
29
|
+
- **Mechanical subroutines** (demote to `haiku`): test runners.
|
|
30
30
|
- **Red Team (Step 5.5)**: `opus` — adversarial reasoning always runs at top tier.
|
|
31
31
|
- **Escalation**: `/advisor` convention-based fallback from `bin/advisor-integration.js` at declared high-stakes sub-decisions (see `.gsd-t/M35-advisor-findings.md`). Never silently downgrade the model or skip Red Team / doc-ripple under context pressure — M35 removed that behavior.
|
|
32
32
|
|
|
@@ -6,7 +6,7 @@ You are maintaining test coverage as code changes. Your job is to identify stale
|
|
|
6
6
|
|
|
7
7
|
Per `.gsd-t/contracts/model-selection-contract.md` v1.0.0.
|
|
8
8
|
|
|
9
|
-
- **Default**: `sonnet` (`
|
|
9
|
+
- **Default**: `sonnet` (per `bin/gsd-t-model-tier-policy.cjs`, phase `test-sync`) — test alignment is routine refactoring work.
|
|
10
10
|
- **Escalation**: `/advisor` convention-based fallback from `bin/advisor-integration.js` when a test touches a contract boundary or requires judgment about what "missing coverage" means. Never silently skip test-sync under context pressure — M35 removed that behavior.
|
|
11
11
|
|
|
12
12
|
This command is:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tekyzinc/gsd-t",
|
|
3
|
-
"version": "5.2.
|
|
3
|
+
"version": "5.2.11",
|
|
4
4
|
"description": "GSD-T: Contract-Driven Development for Claude Code — 54 slash commands with headless-by-default workflow spawning, unattended supervisor relay with event stream, graph-powered code analysis, real-time agent dashboard, task telemetry, doc-ripple enforcement, backlog management, impact analysis, test sync, milestone archival, and PRD generation",
|
|
5
5
|
"author": "Tekyz, Inc.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,7 +23,7 @@ Contract-driven development methodology for Claude Code. npm package providing s
|
|
|
23
23
|
|
|
24
24
|
```
|
|
25
25
|
bin/ — CLI entry (gsd-t.js) + orchestrators (orchestrator.js, design-orchestrator.js)
|
|
26
|
-
+ support modules (gsd-t-context-brief.cjs, cli-preflight.cjs, gsd-t-verify-gate.cjs, model-
|
|
26
|
+
+ support modules (gsd-t-context-brief.cjs, cli-preflight.cjs, gsd-t-verify-gate.cjs, gsd-t-model-tier-policy.cjs, …)
|
|
27
27
|
commands/ — slash commands for Claude Code (GSD-T workflow + utility)
|
|
28
28
|
templates/ — document + prompt + stack templates
|
|
29
29
|
CLAUDE-{global,project}.md, requirements.md, architecture.md, workflows.md,
|
package/bin/model-selector.js
DELETED
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* GSD-T Model Selector — surgical per-phase model tier assignment
|
|
5
|
-
*
|
|
6
|
-
* Replaces the v2.x "silent downgrade under context pressure" behavior with
|
|
7
|
-
* declarative per-phase tier assignments. Callers ask `selectModel({phase, ...})`
|
|
8
|
-
* and get back `{model, reason, escalation_hook}` — the tier decision is
|
|
9
|
-
* deterministic, driven by the rules table below, and does NOT depend on
|
|
10
|
-
* session context percentage.
|
|
11
|
-
*
|
|
12
|
-
* Contract: .gsd-t/contracts/model-selection-contract.md v1.0.0 (M35 T4)
|
|
13
|
-
* Findings: .gsd-t/M35-advisor-findings.md (convention-based /advisor fallback)
|
|
14
|
-
*
|
|
15
|
-
* Zero external dependencies.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
// ── Tiers ───────────────────────────────────────────────────────────────────
|
|
19
|
-
// M85: FABLE tier added alongside HAIKU/SONNET/OPUS.
|
|
20
|
-
// Contract: .gsd-t/contracts/model-tier-policy-contract.md v1.0.0 § "Stage Policy"
|
|
21
|
-
|
|
22
|
-
const TIERS = Object.freeze({
|
|
23
|
-
HAIKU: "haiku",
|
|
24
|
-
SONNET: "sonnet",
|
|
25
|
-
OPUS: "opus",
|
|
26
|
-
FABLE: "fable",
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
const DEFAULT_TIER = TIERS.SONNET;
|
|
30
|
-
|
|
31
|
-
// ── Escalation hook block (convention-based /advisor fallback) ──────────────
|
|
32
|
-
//
|
|
33
|
-
// Per `.gsd-t/M35-advisor-findings.md`, Claude Code's native /advisor has no
|
|
34
|
-
// programmable API at subagent scope. This block is injected into the subagent
|
|
35
|
-
// prompt at declared escalation points on sonnet-tier phases where the
|
|
36
|
-
// orchestrator has flagged a high-stakes sub-decision.
|
|
37
|
-
//
|
|
38
|
-
// Kept as a constant so all consumers (command files, advisor-integration.js,
|
|
39
|
-
// M35-advisor-findings.md) reference the same canonical text.
|
|
40
|
-
|
|
41
|
-
const ESCALATION_HOOK = [
|
|
42
|
-
"## Escalation Hook — /advisor convention-based fallback",
|
|
43
|
-
"",
|
|
44
|
-
"Before finalizing your answer for this phase, stop and consider:",
|
|
45
|
-
"1. Is this decision high-stakes? (architecture, contract design, security boundary,",
|
|
46
|
-
" data-loss risk, cross-module refactor, adversarial QA verdict)",
|
|
47
|
-
"2. Would a more capable model produce a materially better answer?",
|
|
48
|
-
"3. Are you confident in the assumptions you're making?",
|
|
49
|
-
"",
|
|
50
|
-
"If YES to any of the above, do ONE of the following:",
|
|
51
|
-
"- Escalate internally: spend an extra reasoning pass re-examining the decision",
|
|
52
|
-
" from first principles. Document the re-examination in your output.",
|
|
53
|
-
"- Spawn a nested opus subagent: use the Task tool with",
|
|
54
|
-
" `subagent_type: \"general-purpose\"` and include `model: opus` in the spawn.",
|
|
55
|
-
"",
|
|
56
|
-
"Record in your output whether you escalated: set `ESCALATED_VIA_ADVISOR=true` or",
|
|
57
|
-
"`ESCALATED_VIA_ADVISOR=false` on a line by itself near the end of your report.",
|
|
58
|
-
].join("\n");
|
|
59
|
-
|
|
60
|
-
// ── Declarative phase rules table ───────────────────────────────────────────
|
|
61
|
-
//
|
|
62
|
-
// Each rule maps (phase, task_type) → tier. The first matching rule wins.
|
|
63
|
-
// `task_type` is optional — when absent, the rule matches any task within
|
|
64
|
-
// that phase. Order the rules from most-specific to least-specific.
|
|
65
|
-
//
|
|
66
|
-
// Tier assignments mirror `.gsd-t/M35-definition.md` Part B and the Model
|
|
67
|
-
// Assignments section of the GSD-T global CLAUDE template:
|
|
68
|
-
// - haiku: strictly mechanical — test runners, branch guards, file checks,
|
|
69
|
-
// JSON validation, no judgment
|
|
70
|
-
// - sonnet: routine code work — execute step 2, test-sync, doc-ripple wiring,
|
|
71
|
-
// quick fixes, integration wiring, debug fix-apply
|
|
72
|
-
// - opus: high-stakes reasoning — partition, discuss, Red Team, verify
|
|
73
|
-
// judgment, debug root-cause, contract/architecture design
|
|
74
|
-
|
|
75
|
-
const PHASE_RULES = Object.freeze([
|
|
76
|
-
// Phase: execute
|
|
77
|
-
{ phase: "execute", task_type: "test_runner", model: TIERS.HAIKU, reason: "Mechanical test-suite runner — zero judgment" },
|
|
78
|
-
{ phase: "execute", task_type: "branch_guard", model: TIERS.HAIKU, reason: "Mechanical branch-name check — zero judgment" },
|
|
79
|
-
{ phase: "execute", task_type: "file_check", model: TIERS.HAIKU, reason: "Mechanical file-existence check — zero judgment" },
|
|
80
|
-
{ phase: "execute", task_type: "qa", model: TIERS.SONNET, reason: "QA evaluation needs judgment per M31 tier refinement" },
|
|
81
|
-
{ phase: "execute", task_type: "red_team", model: TIERS.OPUS, reason: "Adversarial QA benefits most from top tier" },
|
|
82
|
-
{ phase: "execute", model: TIERS.SONNET, reason: "Routine task execution — sonnet is the M35 default for routine work", hasEscalation: true },
|
|
83
|
-
|
|
84
|
-
// Phase: wave (the wave orchestrator itself)
|
|
85
|
-
{ phase: "wave", model: TIERS.SONNET, reason: "Wave orchestration dispatches per-phase subagents; the orchestrator itself is routine coordination", hasEscalation: true },
|
|
86
|
-
|
|
87
|
-
// Phase: quick
|
|
88
|
-
{ phase: "quick", task_type: "test_runner", model: TIERS.HAIKU, reason: "Mechanical test-suite runner — zero judgment" },
|
|
89
|
-
{ phase: "quick", model: TIERS.SONNET, reason: "Routine one-off task — sonnet default" },
|
|
90
|
-
|
|
91
|
-
// Phase: integrate
|
|
92
|
-
{ phase: "integrate", task_type: "test_runner", model: TIERS.HAIKU, reason: "Mechanical integration test runner — zero judgment" },
|
|
93
|
-
{ phase: "integrate", model: TIERS.SONNET, reason: "Integration wiring is routine coordination work" },
|
|
94
|
-
|
|
95
|
-
// Phase: debug
|
|
96
|
-
{ phase: "debug", task_type: "fix_apply", model: TIERS.SONNET, reason: "Applying a known fix is routine code work" },
|
|
97
|
-
{ phase: "debug", task_type: "root_cause", model: TIERS.OPUS, reason: "Root-cause analysis is high-stakes reasoning" },
|
|
98
|
-
// M85: cycle-2 escalation — when debug cycle-1 (opus) has not resolved the issue,
|
|
99
|
-
// cycle-2 escalates to Fable. The debug DEFAULT (cycle-1/general) remains opus —
|
|
100
|
-
// no existing rule is altered (AC f, no silent degradation). This is a DOCUMENTED
|
|
101
|
-
// MIRROR for Task-based/bin/ callers; the live enforcement is in the debug workflow
|
|
102
|
-
// ternary (D3-T3); the D4 lint guards that ternary.
|
|
103
|
-
// API shape: selectModel({ phase: "debug", task_type: "cycle_2_escalation" }) → fable
|
|
104
|
-
{ phase: "debug", task_type: "cycle_2_escalation", model: TIERS.FABLE, reason: "Cycle-2 debug escalation — Fable after opus cycle-1 has not resolved; no existing rule altered (AC f)" },
|
|
105
|
-
{ phase: "debug", model: TIERS.OPUS, reason: "Debug default is high-stakes — prefer opus unless the task_type says otherwise" },
|
|
106
|
-
|
|
107
|
-
// Phase: partition — high-stakes architectural decomposition
|
|
108
|
-
{ phase: "partition", model: TIERS.OPUS, reason: "Domain partitioning is architectural reasoning — high stakes" },
|
|
109
|
-
|
|
110
|
-
// Phase: discuss — multi-perspective design exploration
|
|
111
|
-
{ phase: "discuss", model: TIERS.OPUS, reason: "Design exploration benefits from top-tier reasoning" },
|
|
112
|
-
|
|
113
|
-
// Phase: plan — task-list authoring
|
|
114
|
-
{ phase: "plan", model: TIERS.SONNET, reason: "Task decomposition is structured work — sonnet with escalation hook", hasEscalation: true },
|
|
115
|
-
|
|
116
|
-
// Phase: verify — final quality judgment before milestone complete
|
|
117
|
-
{ phase: "verify", model: TIERS.OPUS, reason: "Milestone verification is the final quality gate — high stakes" },
|
|
118
|
-
|
|
119
|
-
// Phase: test-sync — keeping tests aligned with code
|
|
120
|
-
{ phase: "test-sync", model: TIERS.SONNET, reason: "Test alignment is routine refactoring work" },
|
|
121
|
-
|
|
122
|
-
// Phase: doc-ripple — downstream document updates
|
|
123
|
-
{ phase: "doc-ripple", model: TIERS.SONNET, reason: "Documentation updates are routine prose editing" },
|
|
124
|
-
|
|
125
|
-
// Phase: red_team — explicit adversarial QA phase (separate from execute task_type)
|
|
126
|
-
{ phase: "red_team", model: TIERS.OPUS, reason: "Adversarial QA — always opus, the incentive is to find bugs" },
|
|
127
|
-
|
|
128
|
-
// Phase: qa — explicit standalone QA phase
|
|
129
|
-
{ phase: "qa", model: TIERS.SONNET, reason: "QA per M31 refinement — sonnet produces fewer false negatives than haiku" },
|
|
130
|
-
]);
|
|
131
|
-
|
|
132
|
-
// Complexity-signal overrides. If the caller provides `complexity_signals`,
|
|
133
|
-
// these can bump a sonnet decision to opus regardless of phase rule.
|
|
134
|
-
const COMPLEXITY_OVERRIDES = Object.freeze({
|
|
135
|
-
cross_module_refactor: TIERS.OPUS,
|
|
136
|
-
security_boundary: TIERS.OPUS,
|
|
137
|
-
data_loss_risk: TIERS.OPUS,
|
|
138
|
-
contract_design: TIERS.OPUS,
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
// ── Public API ──────────────────────────────────────────────────────────────
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Select the model tier for a subagent spawn.
|
|
145
|
-
*
|
|
146
|
-
* @param {object} args
|
|
147
|
-
* @param {string} args.phase — required; one of the phase names above
|
|
148
|
-
* @param {string} [args.task_type] — optional task_type for finer-grained rules
|
|
149
|
-
* @param {string} [args.domain_type] — optional, currently unused (reserved for future per-domain rules)
|
|
150
|
-
* @param {object} [args.complexity_signals] — optional object, keys matching COMPLEXITY_OVERRIDES escalate sonnet→opus
|
|
151
|
-
* @returns {{model: string, reason: string, escalation_hook: string|null}}
|
|
152
|
-
*/
|
|
153
|
-
function selectModel(args) {
|
|
154
|
-
if (!args || typeof args !== "object") {
|
|
155
|
-
return {
|
|
156
|
-
model: DEFAULT_TIER,
|
|
157
|
-
reason: "No args provided — default to routine tier (sonnet)",
|
|
158
|
-
escalation_hook: null,
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const { phase, task_type, complexity_signals } = args;
|
|
163
|
-
|
|
164
|
-
if (!phase || typeof phase !== "string") {
|
|
165
|
-
return {
|
|
166
|
-
model: DEFAULT_TIER,
|
|
167
|
-
reason: "No phase provided — default to routine tier (sonnet)",
|
|
168
|
-
escalation_hook: null,
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// First pass: find the most-specific matching rule.
|
|
173
|
-
let matched = null;
|
|
174
|
-
for (const rule of PHASE_RULES) {
|
|
175
|
-
if (rule.phase !== phase) continue;
|
|
176
|
-
if (rule.task_type && rule.task_type !== task_type) continue;
|
|
177
|
-
matched = rule;
|
|
178
|
-
break;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (!matched) {
|
|
182
|
-
return {
|
|
183
|
-
model: DEFAULT_TIER,
|
|
184
|
-
reason: `Unknown phase "${phase}" — fallback to routine tier (sonnet)`,
|
|
185
|
-
escalation_hook: null,
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
let model = matched.model;
|
|
190
|
-
let reason = matched.reason;
|
|
191
|
-
|
|
192
|
-
// Complexity-signal overrides: bump sonnet → opus if any flagged signal is truthy.
|
|
193
|
-
if (model === TIERS.SONNET && complexity_signals && typeof complexity_signals === "object") {
|
|
194
|
-
for (const key of Object.keys(complexity_signals)) {
|
|
195
|
-
if (!complexity_signals[key]) continue;
|
|
196
|
-
const override = COMPLEXITY_OVERRIDES[key];
|
|
197
|
-
if (override && override !== model) {
|
|
198
|
-
model = override;
|
|
199
|
-
reason = `${reason} (escalated to ${override} by complexity signal: ${key})`;
|
|
200
|
-
break;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Escalation hook is only injected on sonnet phases flagged as high-stakes-adjacent.
|
|
206
|
-
// Haiku phases have no hook (mechanical, no judgment). Opus phases have no hook
|
|
207
|
-
// (already at top tier — nowhere to escalate).
|
|
208
|
-
let escalation_hook = null;
|
|
209
|
-
if (model === TIERS.SONNET && matched.hasEscalation) {
|
|
210
|
-
escalation_hook = ESCALATION_HOOK;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return { model, reason, escalation_hook };
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Return the canonical list of phase names the selector knows about.
|
|
218
|
-
* Used by tests and documentation tooling to assert coverage.
|
|
219
|
-
*/
|
|
220
|
-
function listPhases() {
|
|
221
|
-
const seen = new Set();
|
|
222
|
-
for (const rule of PHASE_RULES) seen.add(rule.phase);
|
|
223
|
-
return [...seen].sort();
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
module.exports = {
|
|
227
|
-
selectModel,
|
|
228
|
-
listPhases,
|
|
229
|
-
TIERS,
|
|
230
|
-
DEFAULT_TIER,
|
|
231
|
-
ESCALATION_HOOK,
|
|
232
|
-
PHASE_RULES,
|
|
233
|
-
COMPLEXITY_OVERRIDES,
|
|
234
|
-
};
|