chore: import upstream snapshot with attribution
This commit is contained in:
+76
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Shared fixture for test coverage audit E2E tests.
|
||||
*
|
||||
* Creates a Node.js project with billing source code that has intentional
|
||||
* test coverage gaps: processPayment has happy-path-only tests,
|
||||
* refundPayment has no tests at all.
|
||||
*
|
||||
* Used by: ship-coverage-audit E2E, review-coverage-audit E2E
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { spawnSync } from 'child_process';
|
||||
|
||||
export function createCoverageAuditFixture(dir: string): void {
|
||||
// Create a Node.js project WITH test framework but coverage gaps
|
||||
fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({
|
||||
name: 'test-coverage-app',
|
||||
version: '1.0.0',
|
||||
type: 'module',
|
||||
scripts: { test: 'echo "no tests yet"' },
|
||||
devDependencies: { vitest: '^1.0.0' },
|
||||
}, null, 2));
|
||||
|
||||
// Create vitest config
|
||||
fs.writeFileSync(path.join(dir, 'vitest.config.ts'),
|
||||
`import { defineConfig } from 'vitest/config';\nexport default defineConfig({ test: {} });\n`);
|
||||
|
||||
fs.writeFileSync(path.join(dir, 'VERSION'), '0.1.0.0\n');
|
||||
fs.writeFileSync(path.join(dir, 'CHANGELOG.md'), '# Changelog\n');
|
||||
|
||||
// Create source file with multiple code paths
|
||||
fs.mkdirSync(path.join(dir, 'src'), { recursive: true });
|
||||
fs.writeFileSync(path.join(dir, 'src', 'billing.ts'), `
|
||||
export function processPayment(amount: number, currency: string) {
|
||||
if (amount <= 0) throw new Error('Invalid amount');
|
||||
if (currency !== 'USD' && currency !== 'EUR') throw new Error('Unsupported currency');
|
||||
return { status: 'success', amount, currency };
|
||||
}
|
||||
|
||||
export function refundPayment(paymentId: string, reason: string) {
|
||||
if (!paymentId) throw new Error('Payment ID required');
|
||||
if (!reason) throw new Error('Reason required');
|
||||
return { status: 'refunded', paymentId, reason };
|
||||
}
|
||||
`);
|
||||
|
||||
// Create a test directory with ONE test (partial coverage)
|
||||
fs.mkdirSync(path.join(dir, 'test'), { recursive: true });
|
||||
fs.writeFileSync(path.join(dir, 'test', 'billing.test.ts'), `
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import { processPayment } from '../src/billing';
|
||||
|
||||
describe('processPayment', () => {
|
||||
test('processes valid payment', () => {
|
||||
const result = processPayment(100, 'USD');
|
||||
expect(result.status).toBe('success');
|
||||
});
|
||||
// GAP: no test for invalid amount
|
||||
// GAP: no test for unsupported currency
|
||||
// GAP: refundPayment not tested at all
|
||||
});
|
||||
`);
|
||||
|
||||
// Init git repo with main branch
|
||||
const run = (cmd: string, args: string[]) =>
|
||||
spawnSync(cmd, args, { cwd: dir, stdio: 'pipe', timeout: 5000 });
|
||||
run('git', ['init', '-b', 'main']);
|
||||
run('git', ['config', 'user.email', 'test@test.com']);
|
||||
run('git', ['config', 'user.name', 'Test']);
|
||||
run('git', ['add', '.']);
|
||||
run('git', ['commit', '-m', 'initial commit']);
|
||||
|
||||
// Create feature branch
|
||||
run('git', ['checkout', '-b', 'feature/billing']);
|
||||
}
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"command_reference": { "clarity": 4, "completeness": 3, "actionability": 4 },
|
||||
"snapshot_flags": { "clarity": 4, "completeness": 4, "actionability": 4 },
|
||||
"browse_skill": { "clarity": 4, "completeness": 4, "actionability": 4 },
|
||||
"qa_workflow": { "clarity": 4, "completeness": 4, "actionability": 4 },
|
||||
"qa_health_rubric": { "clarity": 4, "completeness": 3, "actionability": 4 }
|
||||
}
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
/**
|
||||
* Per-skill draft-plan seeds engineered to surface at least one
|
||||
* review-phase finding in the corresponding plan-* review skill.
|
||||
*
|
||||
* Used by gate-tier finding-floor tests
|
||||
* (test/skill-e2e-plan-{eng,ceo,design,devex}-finding-floor.test.ts) as
|
||||
* the minimum-cost regression for the May 2026 transcript bug:
|
||||
* "/plan-eng-review reviewed a real PR diff, wrote a multi-section
|
||||
* review plan to ~/.claude/plans/ and called ExitPlanMode without
|
||||
* ever firing AskUserQuestion."
|
||||
*
|
||||
* Each seed is small and pre-loaded with one obvious finding the
|
||||
* matching skill cannot honestly miss. Floor tests assert
|
||||
* `reviewCount >= 1` — i.e., the model fired at least one review-phase
|
||||
* AUQ before reaching plan_ready / completion_summary / ceiling.
|
||||
*
|
||||
* Each seed includes the standard "write your plan-mode plan to /tmp/…"
|
||||
* preamble that the existing periodic finding-count fixtures use, so
|
||||
* the agent has a concrete plan-file target. The /tmp path is unique
|
||||
* per skill to avoid collisions if floor tests run in parallel.
|
||||
*
|
||||
* For a deeper [N-1, N+2] count band assertion, see the periodic
|
||||
* test/skill-e2e-plan-{X}-finding-count.test.ts fixtures.
|
||||
*/
|
||||
|
||||
export const FORCING_FLOOR_ENG = [
|
||||
'Please review this plan thoroughly. As you go, write your plan-mode plan to /tmp/gstack-test-plan-eng-floor.md (use Edit/Write to that exact path).',
|
||||
'',
|
||||
'# Plan: Add request-id propagation across services',
|
||||
'',
|
||||
'## Architecture',
|
||||
"We'll roll a custom UUIDv7 generator inline in each service rather than",
|
||||
"use Node's crypto.randomUUID() built-in. Same shape, but we want full",
|
||||
'control over the entropy source for "future flexibility" — no concrete',
|
||||
'reason yet.',
|
||||
].join('\n');
|
||||
|
||||
export const FORCING_FLOOR_CEO = [
|
||||
'Please review this plan thoroughly. As you go, write your plan-mode plan to /tmp/gstack-test-plan-ceo-floor.md (use Edit/Write to that exact path).',
|
||||
'',
|
||||
'# Plan: Launch a "developer-friendly" pricing tier',
|
||||
'',
|
||||
'## Goal',
|
||||
'Increase developer adoption.',
|
||||
'',
|
||||
'## Success metric',
|
||||
'More signups.',
|
||||
'',
|
||||
'## Premise',
|
||||
"We haven't talked to any developers about whether the current pricing",
|
||||
'is actually a barrier. The team agreed it "feels like" it should be cheaper.',
|
||||
].join('\n');
|
||||
|
||||
export const FORCING_FLOOR_DESIGN = [
|
||||
'Please review this plan thoroughly. As you go, write your plan-mode plan to /tmp/gstack-test-plan-design-floor.md (use Edit/Write to that exact path).',
|
||||
'',
|
||||
'# Plan: Marketing landing page',
|
||||
'',
|
||||
'## Layout',
|
||||
'All headings, taglines, and body copy will be center-aligned for a',
|
||||
'"clean modern look." The hero h1 sits 8px above the subhead with no',
|
||||
'breathing room; the CTA button is the same visual weight as a',
|
||||
'secondary "Learn more" link directly beside it.',
|
||||
].join('\n');
|
||||
|
||||
export const FORCING_FLOOR_DEVEX = [
|
||||
'Please review this plan thoroughly. As you go, write your plan-mode plan to /tmp/gstack-test-plan-devex-floor.md (use Edit/Write to that exact path).',
|
||||
'',
|
||||
'# Plan: SDK quickstart docs',
|
||||
'',
|
||||
'## Onboarding flow',
|
||||
'Step 1: clone the repo.',
|
||||
'Step 2: install bun manually if not present.',
|
||||
'Step 3: copy .env.example to .env and fill in 8 environment variables.',
|
||||
'Step 4: run database migrations against your local Postgres.',
|
||||
'Step 5: start the dev server.',
|
||||
'Step 6: open the docs in a separate tab.',
|
||||
'Step 7: register an API key by emailing the team.',
|
||||
'Step 8: paste the key into your .env, restart the server, then make',
|
||||
'your first SDK call.',
|
||||
'',
|
||||
'No quickstart command, no hosted sandbox, no copy-pasteable curl example.',
|
||||
].join('\n');
|
||||
|
||||
/**
|
||||
* Multi-finding batching regression seed (periodic tier).
|
||||
*
|
||||
* Mirrors the May 2026 transcript bug shape: 4 distinct non-trivial findings
|
||||
* spread across plan-eng-review's standard sections (Architecture, Code
|
||||
* Quality, Tests, Performance). Each finding is independent — there is no
|
||||
* legitimate reason to batch them into a single AskUserQuestion.
|
||||
*
|
||||
* Used by test/skill-e2e-plan-eng-multi-finding-batching.test.ts to assert
|
||||
* the agent fires >= 3 review-phase AUQs (i.e., does NOT batch them into a
|
||||
* "## Decisions to confirm" section + ExitPlanMode). Floor of 3 (not 4) is
|
||||
* the [N-1] tolerance from the existing finding-count band convention.
|
||||
*/
|
||||
export const FORCING_BATCHING_ENG = [
|
||||
'Please review this plan thoroughly. As you go, write your plan-mode plan to /tmp/gstack-test-plan-eng-batching.md (use Edit/Write to that exact path).',
|
||||
'',
|
||||
'# Plan: Add background job retry framework',
|
||||
'',
|
||||
'## Architecture',
|
||||
"We'll roll a custom exponential-backoff scheduler inline in each worker",
|
||||
"rather than use the existing job library's built-in retry hooks. Same",
|
||||
'shape as the library version, but we want full control over the curve.',
|
||||
'',
|
||||
'## Code quality',
|
||||
'The retry envelope (compute delay, log attempt, dispatch) is duplicated',
|
||||
'across 5 worker files with copy-pasted bodies. We will leave the',
|
||||
'duplication for now and refactor "later."',
|
||||
'',
|
||||
'## Tests',
|
||||
'The existing `processWebhookJob()` flow gets rewritten as part of this',
|
||||
'change. No regression test for the prior at-most-once delivery guarantee',
|
||||
'is planned.',
|
||||
'',
|
||||
'## Performance',
|
||||
'On every retry we re-fetch the full job payload from the database, then',
|
||||
'iterate the payload to recompute the dependency graph. Could cache the',
|
||||
'graph on the first attempt; not planned.',
|
||||
].join('\n');
|
||||
|
||||
/**
|
||||
* Split-overflow regression seed (periodic tier).
|
||||
*
|
||||
* Catches the original failure mode the user complained about: when the
|
||||
* agent has 5+ options for ONE conceptual decision, it must split into N
|
||||
* sequential AskUserQuestion calls (or batch into compatible ≤4-groups),
|
||||
* NOT drop an option arbitrarily to fit Conductor's 4-option cap.
|
||||
*
|
||||
* Fixture shape: 5 independent platform-integration candidates for ONE
|
||||
* scope decision. Each is independent (no dependencies between them) so
|
||||
* the natural compliant shape is a per-option split chain at parent D<N>.
|
||||
*
|
||||
* Used by test/skill-e2e-plan-ceo-split-overflow.test.ts to assert the
|
||||
* agent fires >= 4 review-phase AUQs (floor uses the standard [N-1]
|
||||
* tolerance band, accounting for one expected scope-reduction-or-merge
|
||||
* call before the per-option chain begins).
|
||||
*
|
||||
* Pre-fix behavior: agent fires 1 AUQ with 4 options, "trims" the 5th
|
||||
* via prose ("E5 is the largest lift and a natural follow-up; moving to
|
||||
* TODOs without asking"). That's the bug. Floor of 4 detects it.
|
||||
*/
|
||||
export const FORCING_SPLIT_OVERFLOW_CEO = [
|
||||
'Please review this plan and help me decide scope. Write your plan-mode plan to /tmp/gstack-test-plan-ceo-split-overflow.md (use Edit/Write to that exact path).',
|
||||
'',
|
||||
'# Plan: Pick which chat-platform integrations to ship this quarter',
|
||||
'',
|
||||
'We have engineering bandwidth for at most 2-3 integrations this quarter.',
|
||||
'I need your help deciding which to prioritize. Below are 5 candidates,',
|
||||
'each fully independent of the others (no shared infrastructure, no',
|
||||
'dependencies between them). For each, the user can independently decide:',
|
||||
'include in this scope, defer to next quarter, or cut entirely.',
|
||||
'',
|
||||
'## E1) Slack — DM bot for incident alerts',
|
||||
'Build cost: ~2 weeks. Existing Slack auth flow we can reuse. High user',
|
||||
'demand (top customer request in Q2 survey, ~40% of asks).',
|
||||
'',
|
||||
'## E2) Discord — guild bot for community channels',
|
||||
'Build cost: ~3 weeks. Greenfield integration, no existing auth. Medium',
|
||||
'demand (~15% of asks, but loud community).',
|
||||
'',
|
||||
'## E3) Microsoft Teams — webhook + bot framework',
|
||||
'Build cost: ~4 weeks. Enterprise customers specifically asked for this.',
|
||||
'Highest revenue impact per user but smallest user count (~5% of asks).',
|
||||
'',
|
||||
'## E4) Telegram — bot API integration',
|
||||
'Build cost: ~1 week. Simplest API surface. Low strategic value but',
|
||||
'cheap win (~8% of asks, mostly from international users).',
|
||||
'',
|
||||
'## E5) Mattermost — REST plugin',
|
||||
'Build cost: ~2 weeks. Self-hosted enterprise users. Niche but locked-in',
|
||||
'segment (~3% of asks but all from high-ARR accounts).',
|
||||
'',
|
||||
'Please walk me through each candidate and help me decide include/defer/cut',
|
||||
'per option. I want individual decisions per candidate, not a bundled pick.',
|
||||
].join('\n');
|
||||
Vendored
+2503
File diff suppressed because it is too large
Load Diff
+1417
File diff suppressed because it is too large
Load Diff
+2803
File diff suppressed because it is too large
Load Diff
+3209
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
.build/
|
||||
.swiftpm/
|
||||
DerivedData/
|
||||
*.xcodeproj/
|
||||
*.xcworkspace/
|
||||
Package.resolved
|
||||
*.xcodeproj/xcuserdata/
|
||||
*.xcodeproj/project.xcworkspace/xcuserdata/
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
// swift-tools-version:5.9
|
||||
// Test fixture: minimal SwiftUI app + DebugBridge SPM package.
|
||||
// DebugBridgeCore (Foundation+Network) builds cross-platform.
|
||||
// DebugBridgeUI (UIKit/SwiftUI) is iOS-only.
|
||||
// DebugBridgeTouch (Objective-C) is iOS-only — in-process tap synthesis
|
||||
// derived from KIF (MIT). DEBUG-only.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "FixtureApp",
|
||||
platforms: [
|
||||
.iOS(.v16),
|
||||
.macOS(.v13),
|
||||
],
|
||||
products: [
|
||||
.library(name: "DebugBridgeCore", targets: ["DebugBridgeCore"]),
|
||||
.library(name: "DebugBridgeUI", targets: ["DebugBridgeUI"]),
|
||||
.library(name: "DebugBridgeTouch", targets: ["DebugBridgeTouch"]),
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "DebugBridgeCore",
|
||||
dependencies: [],
|
||||
path: "Sources/DebugBridgeCore",
|
||||
swiftSettings: [
|
||||
.define("DEBUG", .when(configuration: .debug)),
|
||||
]
|
||||
),
|
||||
.target(
|
||||
name: "DebugBridgeTouch",
|
||||
dependencies: [],
|
||||
path: "Sources/DebugBridgeTouch",
|
||||
publicHeadersPath: "include",
|
||||
linkerSettings: [
|
||||
.linkedFramework("UIKit", .when(platforms: [.iOS])),
|
||||
]
|
||||
),
|
||||
.target(
|
||||
name: "DebugBridgeUI",
|
||||
dependencies: ["DebugBridgeCore", "DebugBridgeTouch"],
|
||||
path: "Sources/DebugBridgeUI",
|
||||
swiftSettings: [
|
||||
.define("DEBUG", .when(configuration: .debug)),
|
||||
]
|
||||
),
|
||||
.testTarget(
|
||||
name: "DebugBridgeCoreTests",
|
||||
dependencies: ["DebugBridgeCore"],
|
||||
path: "Tests/DebugBridgeCoreTests"
|
||||
),
|
||||
]
|
||||
)
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
// AUTO-GENERATED from gstack/ios-qa/templates/DebugBridgeManager.swift.template
|
||||
//
|
||||
// Bootstraps StateServer on app launch. Lives in DebugBridgeCore (no UIKit
|
||||
// dependency). The DebugOverlay install is wired separately by the consuming
|
||||
// app — it lives in DebugBridgeUI which depends on DebugBridgeCore (not the
|
||||
// other way around). Everything is #if DEBUG-gated; this file does not exist
|
||||
// in Release builds.
|
||||
|
||||
#if DEBUG
|
||||
|
||||
import Foundation
|
||||
|
||||
@MainActor
|
||||
public final class DebugBridgeManager {
|
||||
public static let shared = DebugBridgeManager()
|
||||
|
||||
public func start(appState: AppState) {
|
||||
// 1. Register the canonical AppState struct + accessor wiring.
|
||||
// AppStateAccessor.register(_:) is generated by gen-accessors-tool.
|
||||
AppStateAccessor.register(appState)
|
||||
|
||||
// 2. Boot the StateServer.
|
||||
StateServer.shared.start()
|
||||
|
||||
// 3. The consuming app installs DebugOverlayWindow separately. See
|
||||
// the example in DebugBridgeWiring.swift.template:
|
||||
//
|
||||
// #if canImport(UIKit)
|
||||
// DebugOverlayWindow.shared.install(recording: recording)
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
|
||||
// Placeholder. gen-accessors-tool emits the real `AppStateAccessor` enum next
|
||||
// to the app's canonical state struct. Apps that haven't run codegen get a
|
||||
// stub that registers no accessors (snapshot is empty, restore returns
|
||||
// missing-key for every key).
|
||||
@MainActor
|
||||
public enum AppStateAccessor {
|
||||
public static var register: (Any) -> Void = { _ in }
|
||||
}
|
||||
|
||||
// Apps declare their canonical state struct; codegen reads it and emits
|
||||
// AppStateAccessor.register. The app's struct must be `@Observable` and
|
||||
// must hold all snapshot-eligible state in `@Snapshotable`-marked fields.
|
||||
@MainActor
|
||||
public protocol AppState: AnyObject {}
|
||||
|
||||
#endif // DEBUG
|
||||
@@ -0,0 +1,569 @@
|
||||
// AUTO-GENERATED from gstack/ios-qa/templates/StateServer.swift.template
|
||||
// Regenerate with: /ios-sync
|
||||
//
|
||||
// StateServer — HTTP server embedded in the iOS app under test. Loopback-only.
|
||||
// All tailnet ingress is the responsibility of the Mac-side daemon.
|
||||
//
|
||||
// Threat model: this surface is reachable from the local Mac via the CoreDevice
|
||||
// IPv6 tunnel. It MUST refuse any caller without a current bearer token. The
|
||||
// boot token is rotated within ~5 seconds of daemon spawn so anything scraping
|
||||
// os_log past that window sees a dead credential.
|
||||
|
||||
import Foundation
|
||||
import Network
|
||||
import os.log
|
||||
|
||||
#if DEBUG
|
||||
|
||||
public typealias JSONDict = [String: Any]
|
||||
|
||||
@MainActor
|
||||
public final class StateServer {
|
||||
// MARK: Public surface
|
||||
|
||||
public static let shared = StateServer()
|
||||
|
||||
// MARK: Configuration
|
||||
|
||||
private let logger = Logger(subsystem: "gstack.ios-qa", category: "StateServer")
|
||||
private let port: UInt16
|
||||
private let bootTokenPath: String
|
||||
|
||||
// Two listeners for dual-stack loopback. The fork's single-listener IPv6-only
|
||||
// binding was caught in eng + outside-voice review as incomplete.
|
||||
private var ipv6Listener: NWListener?
|
||||
private var ipv4Listener: NWListener?
|
||||
|
||||
// Auth state. The boot token is what we wrote to os_log on first launch.
|
||||
// It exists ONLY long enough for the daemon to call /auth/rotate.
|
||||
private var bootToken: String
|
||||
private var rotatedToken: String? // set after first /auth/rotate
|
||||
private var bootTokenValid: Bool = true
|
||||
|
||||
// MARK: Session lock (per-device, sliding window on mutations only)
|
||||
|
||||
private struct Session {
|
||||
let id: String
|
||||
var lastMutationAt: Date
|
||||
}
|
||||
private var activeSession: Session?
|
||||
private let sessionTtlSeconds: TimeInterval = 300 // 5 min orphan timeout
|
||||
|
||||
// MARK: Accessor registry (populated by codegen)
|
||||
|
||||
public typealias ReadHandler = () -> Any?
|
||||
public typealias WriteHandler = (Any) -> Bool
|
||||
public typealias TypeName = String
|
||||
|
||||
private var readHandlers: [String: ReadHandler] = [:]
|
||||
private var writeHandlers: [String: WriteHandler] = [:]
|
||||
private var typeNames: [String: TypeName] = [:]
|
||||
|
||||
// Atomic-restore hook. Codegen wires this to the canonical AppState struct.
|
||||
// Restore replaces the entire struct in one assignment so SwiftUI's Combine
|
||||
// pipeline observes exactly one change notification — true observable
|
||||
// atomicity. @MainActor alone doesn't guarantee that.
|
||||
public typealias AtomicRestoreFn = (JSONDict) -> RestoreResult
|
||||
public enum RestoreResult {
|
||||
case ok
|
||||
case missingKey(String)
|
||||
case typeMismatch(String)
|
||||
case schemaMismatch(expected: String, got: String)
|
||||
}
|
||||
private var atomicRestore: AtomicRestoreFn?
|
||||
|
||||
// Snapshot schema hash — written by codegen, stable across builds with
|
||||
// identical accessor signatures.
|
||||
private var accessorHash: String = "uninitialized"
|
||||
private var appBuildId: String = "uninitialized"
|
||||
|
||||
// Agent identity for the DebugOverlay attribution chip. Display-only,
|
||||
// never used for auth.
|
||||
public private(set) var lastAgentIdentity: String = "Claude Code (local)"
|
||||
|
||||
// MARK: Lifecycle
|
||||
|
||||
private init(port: UInt16 = 9999) {
|
||||
self.port = port
|
||||
self.bootToken = UUID().uuidString
|
||||
self.bootTokenPath = NSTemporaryDirectory() + "gstack-ios-qa.token"
|
||||
}
|
||||
|
||||
public func start() {
|
||||
// 1. Persist boot token to a 0600 file (best-effort fallback for the
|
||||
// daemon if os_log scrape misses).
|
||||
try? bootToken.write(toFile: bootTokenPath, atomically: true, encoding: .utf8)
|
||||
try? FileManager.default.setAttributes([.posixPermissions: 0o600], ofItemAtPath: bootTokenPath)
|
||||
|
||||
// 2. Log the boot token EXACTLY ONCE so the daemon can scrape it.
|
||||
// The daemon will rotate immediately; this log line is dead within
|
||||
// seconds.
|
||||
logger.notice("gstack-ios-qa-bootstrap token=\(self.bootToken, privacy: .public) port=\(self.port, privacy: .public) build=\(self.appBuildId, privacy: .public)")
|
||||
|
||||
// 3. Bind both IPv6 and IPv4 loopback. CoreDevice tunnel uses IPv6;
|
||||
// local tooling may use IPv4. Never bind 0.0.0.0 or ::.
|
||||
startListener(family: .ipv6)
|
||||
startListener(family: .ipv4)
|
||||
}
|
||||
|
||||
public func register(buildId: String, accessorHash: String, atomicRestore: @escaping AtomicRestoreFn) {
|
||||
self.appBuildId = buildId
|
||||
self.accessorHash = accessorHash
|
||||
self.atomicRestore = atomicRestore
|
||||
}
|
||||
|
||||
public func registerAccessor(key: String, type: String, read: @escaping ReadHandler, write: @escaping WriteHandler) {
|
||||
readHandlers[key] = read
|
||||
writeHandlers[key] = write
|
||||
typeNames[key] = type
|
||||
}
|
||||
|
||||
// MARK: Listener setup
|
||||
|
||||
private enum AddressFamily {
|
||||
case ipv4
|
||||
case ipv6
|
||||
|
||||
var host: NWEndpoint.Host {
|
||||
switch self {
|
||||
case .ipv4: return NWEndpoint.Host("127.0.0.1")
|
||||
case .ipv6: return NWEndpoint.Host("::1")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func startListener(family: AddressFamily) {
|
||||
do {
|
||||
// Binding strategy: accept connections from the device's loopback
|
||||
// AND from the CoreDevice tunnel (the USB-mounted tunnel the Mac
|
||||
// daemon uses to reach this app — appears as a non-loopback
|
||||
// utun-style interface on the device with the peer's source
|
||||
// address in the fd*/fc* ULA range). We can't use
|
||||
// params.acceptLocalOnly — Network.framework's definition of
|
||||
// "local" is strictly loopback and silently drops CoreDevice
|
||||
// tunnel peers. Instead we accept on the wildcard interface and
|
||||
// do a per-connection peer-address check below: loopback OR
|
||||
// RFC 4193 ULA (fc00::/7) → accept, everything else → cancel.
|
||||
let params = NWParameters.tcp
|
||||
params.allowLocalEndpointReuse = true
|
||||
|
||||
let listener = try NWListener(using: params, on: NWEndpoint.Port(rawValue: port)!)
|
||||
listener.stateUpdateHandler = { [weak self] state in
|
||||
Task { @MainActor in
|
||||
if case .ready = state {
|
||||
self?.logger.notice("StateServer listening on \(String(describing: family))")
|
||||
} else if case .failed(let err) = state {
|
||||
self?.logger.error("StateServer listener failed: \(err.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
listener.newConnectionHandler = { [weak self] connection in
|
||||
Task { @MainActor in
|
||||
// Defense-in-depth: even with .loopback interface gate, double-check
|
||||
// the peer is loopback. Reject otherwise.
|
||||
if let self, self.isLoopbackPeer(connection) {
|
||||
self.handle(connection)
|
||||
} else {
|
||||
connection.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
listener.start(queue: .global(qos: .userInitiated))
|
||||
|
||||
switch family {
|
||||
case .ipv6: ipv6Listener = listener
|
||||
case .ipv4: ipv4Listener = listener
|
||||
}
|
||||
} catch {
|
||||
logger.error("Listener bind failed (\(String(describing: family))): \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
private func isLoopbackPeer(_ connection: NWConnection) -> Bool {
|
||||
switch connection.endpoint {
|
||||
case .hostPort(let host, _):
|
||||
switch host {
|
||||
case .ipv4(let addr):
|
||||
return addr == .loopback
|
||||
case .ipv6(let addr):
|
||||
// Loopback (::1) — local same-device traffic
|
||||
if addr.isLoopback { return true }
|
||||
// CoreDevice ULA range (fd00::/8 unique-local addresses) —
|
||||
// the USB tunnel that the Mac daemon uses to reach this app.
|
||||
// Apple's CoreDevice tunnel uses fd-prefixed ULAs like
|
||||
// fd72:8347:2ead::1 (Mac-facing) and fd72:8347:2ead::2
|
||||
// (device-facing). We accept the entire ULA range since
|
||||
// the prefix is regenerated per session.
|
||||
let bytes = addr.rawValue
|
||||
if bytes.count >= 1 && (bytes[0] & 0xFE) == 0xFC {
|
||||
// RFC 4193 ULA range (fc00::/7) — fc* or fd* prefix.
|
||||
return true
|
||||
}
|
||||
return false
|
||||
case .name(let name, _):
|
||||
return name == "localhost"
|
||||
@unknown default: return false
|
||||
}
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Request handling
|
||||
|
||||
private func handle(_ connection: NWConnection) {
|
||||
connection.start(queue: .global(qos: .userInitiated))
|
||||
receive(connection: connection, buffer: Data())
|
||||
}
|
||||
|
||||
private static let maxBodyBytes = 1_048_576 // 1MB hard cap
|
||||
|
||||
private func receive(connection: NWConnection, buffer: Data) {
|
||||
connection.receive(minimumIncompleteLength: 1, maximumLength: 65_536) { [weak self] data, _, isComplete, error in
|
||||
guard let self else { return }
|
||||
Task { @MainActor in
|
||||
var current = buffer
|
||||
if let data = data { current.append(data) }
|
||||
if current.count > Self.maxBodyBytes {
|
||||
self.send(connection: connection, status: 413, body: ["error": "body_too_large"])
|
||||
return
|
||||
}
|
||||
if let req = self.tryParseRequest(current) {
|
||||
self.route(connection: connection, request: req)
|
||||
} else if isComplete || error != nil {
|
||||
self.send(connection: connection, status: 400, body: ["error": "bad_request"])
|
||||
} else {
|
||||
self.receive(connection: connection, buffer: current)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ParsedRequest {
|
||||
let method: String
|
||||
let path: String
|
||||
let headers: [String: String]
|
||||
let body: Data
|
||||
}
|
||||
|
||||
private func tryParseRequest(_ data: Data) -> ParsedRequest? {
|
||||
guard let headerEnd = data.range(of: Data("\r\n\r\n".utf8)) else { return nil }
|
||||
let headerData = data.subdata(in: 0..<headerEnd.lowerBound)
|
||||
let body = data.subdata(in: headerEnd.upperBound..<data.count)
|
||||
guard let headerStr = String(data: headerData, encoding: .utf8) else { return nil }
|
||||
let lines = headerStr.components(separatedBy: "\r\n")
|
||||
guard let requestLine = lines.first else { return nil }
|
||||
let parts = requestLine.components(separatedBy: " ")
|
||||
guard parts.count >= 2 else { return nil }
|
||||
|
||||
var headers: [String: String] = [:]
|
||||
for line in lines.dropFirst() {
|
||||
guard let colon = line.firstIndex(of: ":") else { continue }
|
||||
let key = String(line[..<colon]).lowercased()
|
||||
let value = line[line.index(after: colon)...].trimmingCharacters(in: .whitespaces)
|
||||
headers[key] = value
|
||||
}
|
||||
|
||||
if let lenStr = headers["content-length"], let len = Int(lenStr), body.count < len {
|
||||
return nil // need more bytes
|
||||
}
|
||||
return ParsedRequest(method: parts[0], path: parts[1], headers: headers, body: body)
|
||||
}
|
||||
|
||||
private func route(connection: NWConnection, request: ParsedRequest) {
|
||||
// Update display attribution from header (display only — never trusted
|
||||
// for auth).
|
||||
if let agent = request.headers["x-agent-identity"], !agent.isEmpty, agent.count < 200 {
|
||||
lastAgentIdentity = agent
|
||||
}
|
||||
|
||||
let path = request.path
|
||||
|
||||
// 1. Public on loopback: /healthz.
|
||||
if request.method == "GET" && path == "/healthz" {
|
||||
send(connection: connection, status: 200, body: [
|
||||
"version": "1.0.0",
|
||||
"build": appBuildId,
|
||||
"accessor_hash": accessorHash,
|
||||
])
|
||||
return
|
||||
}
|
||||
|
||||
// 2. Auth bootstrap: /auth/rotate is the ONLY endpoint that accepts the
|
||||
// boot token. Everything else requires the rotated token.
|
||||
if request.method == "POST" && path == "/auth/rotate" {
|
||||
handleAuthRotate(connection: connection, request: request)
|
||||
return
|
||||
}
|
||||
|
||||
// 3. All other endpoints require Bearer auth with the rotated token.
|
||||
guard authorize(request: request) else {
|
||||
send(connection: connection, status: 401, body: ["error": "unauthorized"])
|
||||
return
|
||||
}
|
||||
|
||||
switch (request.method, path) {
|
||||
case ("POST", "/session/acquire"): handleSessionAcquire(connection: connection)
|
||||
case ("POST", "/session/release"): handleSessionRelease(connection: connection)
|
||||
case ("POST", "/session/heartbeat"): handleSessionHeartbeat(connection: connection, request: request)
|
||||
case ("GET", "/state/snapshot"): handleSnapshotGet(connection: connection)
|
||||
case ("POST", "/state/restore"): handleSnapshotRestore(connection: connection, request: request)
|
||||
case ("GET", "/elements"): handleElements(connection: connection)
|
||||
case ("GET", "/screenshot"): handleScreenshot(connection: connection)
|
||||
case ("POST", "/tap"): handleMutation(connection: connection, request: request, op: "tap")
|
||||
case ("POST", "/swipe"): handleMutation(connection: connection, request: request, op: "swipe")
|
||||
case ("POST", "/type"): handleMutation(connection: connection, request: request, op: "type")
|
||||
case ("GET", let p) where p.hasPrefix("/state/"):
|
||||
let key = String(p.dropFirst("/state/".count))
|
||||
handleStateGet(connection: connection, key: key)
|
||||
case ("POST", let p) where p.hasPrefix("/state/"):
|
||||
let key = String(p.dropFirst("/state/".count))
|
||||
handleStateWrite(connection: connection, request: request, key: key)
|
||||
default:
|
||||
send(connection: connection, status: 404, body: ["error": "not_found", "path": path])
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Auth
|
||||
|
||||
private func authorize(request: ParsedRequest) -> Bool {
|
||||
guard let auth = request.headers["authorization"], auth.hasPrefix("Bearer ") else { return false }
|
||||
let token = String(auth.dropFirst("Bearer ".count))
|
||||
return token == rotatedToken
|
||||
}
|
||||
|
||||
private func handleAuthRotate(connection: NWConnection, request: ParsedRequest) {
|
||||
// Validate boot token (still alive AND used only once).
|
||||
guard bootTokenValid,
|
||||
let auth = request.headers["authorization"],
|
||||
auth.hasPrefix("Bearer "),
|
||||
String(auth.dropFirst("Bearer ".count)) == bootToken else {
|
||||
send(connection: connection, status: 401, body: ["error": "boot_token_invalid"])
|
||||
return
|
||||
}
|
||||
|
||||
guard let dict = try? JSONSerialization.jsonObject(with: request.body) as? JSONDict,
|
||||
let newToken = dict["new_token"] as? String,
|
||||
newToken.count >= 16 else {
|
||||
send(connection: connection, status: 400, body: ["error": "invalid_rotate_payload"])
|
||||
return
|
||||
}
|
||||
|
||||
rotatedToken = newToken
|
||||
bootTokenValid = false
|
||||
// Best-effort scrub of on-disk boot token file.
|
||||
try? FileManager.default.removeItem(atPath: bootTokenPath)
|
||||
|
||||
logger.notice("Boot token rotated; original now invalid")
|
||||
send(connection: connection, status: 200, body: ["ok": true])
|
||||
}
|
||||
|
||||
// MARK: Session lock
|
||||
|
||||
private static let mutatingPaths: Set<String> = ["/tap", "/swipe", "/type", "/state/restore"]
|
||||
|
||||
private func mutatingPathRequiresSession(_ path: String, method: String) -> Bool {
|
||||
if method != "POST" { return false }
|
||||
if path.hasPrefix("/state/") && path != "/state/restore" { return true } // /state/<key> writes
|
||||
return Self.mutatingPaths.contains(path)
|
||||
}
|
||||
|
||||
private func requireSession(in request: ParsedRequest, connection: NWConnection) -> Bool {
|
||||
guard let id = request.headers["x-session-id"] else {
|
||||
send(connection: connection, status: 409, body: ["error": "session_required"])
|
||||
return false
|
||||
}
|
||||
guard let current = activeSession, current.id == id else {
|
||||
send(connection: connection, status: 409, body: ["error": "session_invalid_or_expired"])
|
||||
return false
|
||||
}
|
||||
// Mutation slides the lock; reads do not.
|
||||
activeSession?.lastMutationAt = Date()
|
||||
return true
|
||||
}
|
||||
|
||||
private func handleSessionAcquire(connection: NWConnection) {
|
||||
// Reap orphaned session.
|
||||
if let s = activeSession, Date().timeIntervalSince(s.lastMutationAt) > sessionTtlSeconds {
|
||||
activeSession = nil
|
||||
}
|
||||
if activeSession != nil {
|
||||
send(connection: connection, status: 423, body: ["error": "device_locked"])
|
||||
return
|
||||
}
|
||||
let id = UUID().uuidString
|
||||
activeSession = Session(id: id, lastMutationAt: Date())
|
||||
send(connection: connection, status: 200, body: [
|
||||
"session_id": id,
|
||||
"ttl_seconds": Int(sessionTtlSeconds),
|
||||
])
|
||||
}
|
||||
|
||||
private func handleSessionRelease(connection: NWConnection) {
|
||||
activeSession = nil
|
||||
send(connection: connection, status: 200, body: ["ok": true])
|
||||
}
|
||||
|
||||
private func handleSessionHeartbeat(connection: NWConnection, request: ParsedRequest) {
|
||||
guard let id = request.headers["x-session-id"],
|
||||
activeSession?.id == id else {
|
||||
send(connection: connection, status: 409, body: ["error": "session_invalid_or_expired"])
|
||||
return
|
||||
}
|
||||
activeSession?.lastMutationAt = Date()
|
||||
send(connection: connection, status: 200, body: ["ok": true, "ttl_seconds": Int(sessionTtlSeconds)])
|
||||
}
|
||||
|
||||
// MARK: State handlers
|
||||
|
||||
private func handleStateGet(connection: NWConnection, key: String) {
|
||||
guard let handler = readHandlers[key] else {
|
||||
send(connection: connection, status: 404, body: ["error": "unknown_key", "key": key])
|
||||
return
|
||||
}
|
||||
let value = handler() ?? NSNull()
|
||||
send(connection: connection, status: 200, body: ["key": key, "value": value])
|
||||
}
|
||||
|
||||
private func handleStateWrite(connection: NWConnection, request: ParsedRequest, key: String) {
|
||||
guard requireSession(in: request, connection: connection) else { return }
|
||||
guard let handler = writeHandlers[key] else {
|
||||
send(connection: connection, status: 404, body: ["error": "unknown_key", "key": key])
|
||||
return
|
||||
}
|
||||
guard let payload = try? JSONSerialization.jsonObject(with: request.body) as? JSONDict,
|
||||
let value = payload["value"] else {
|
||||
send(connection: connection, status: 400, body: ["error": "missing_value"])
|
||||
return
|
||||
}
|
||||
let ok = handler(value)
|
||||
if ok {
|
||||
send(connection: connection, status: 200, body: ["ok": true])
|
||||
} else {
|
||||
send(connection: connection, status: 400, body: ["error": "type_mismatch", "expected": typeNames[key] ?? "?"])
|
||||
}
|
||||
}
|
||||
|
||||
private func handleSnapshotGet(connection: NWConnection) {
|
||||
var keys: JSONDict = [:]
|
||||
for (k, read) in readHandlers {
|
||||
keys[k] = read() ?? NSNull()
|
||||
}
|
||||
let envelope: JSONDict = [
|
||||
"_schema_version": 1,
|
||||
"_app_build_id": appBuildId,
|
||||
"_accessor_hash": accessorHash,
|
||||
"keys": keys,
|
||||
]
|
||||
send(connection: connection, status: 200, body: envelope)
|
||||
}
|
||||
|
||||
private func handleSnapshotRestore(connection: NWConnection, request: ParsedRequest) {
|
||||
guard requireSession(in: request, connection: connection) else { return }
|
||||
guard let envelope = try? JSONSerialization.jsonObject(with: request.body) as? JSONDict else {
|
||||
send(connection: connection, status: 400, body: ["error": "invalid_json"])
|
||||
return
|
||||
}
|
||||
// Schema gate.
|
||||
if let hash = envelope["_accessor_hash"] as? String, hash != accessorHash {
|
||||
send(connection: connection, status: 409, body: [
|
||||
"error": "schema_mismatch",
|
||||
"expected_hash": accessorHash,
|
||||
"got_hash": hash,
|
||||
])
|
||||
return
|
||||
}
|
||||
guard let keys = envelope["keys"] as? JSONDict else {
|
||||
send(connection: connection, status: 400, body: ["error": "missing_keys"])
|
||||
return
|
||||
}
|
||||
guard let restore = atomicRestore else {
|
||||
send(connection: connection, status: 503, body: ["error": "atomic_restore_not_registered"])
|
||||
return
|
||||
}
|
||||
// Validate-then-apply via the codegen-supplied closure. The closure does
|
||||
// a single struct-assignment so SwiftUI sees one change notification.
|
||||
switch restore(keys) {
|
||||
case .ok:
|
||||
send(connection: connection, status: 200, body: ["ok": true])
|
||||
case .missingKey(let k):
|
||||
send(connection: connection, status: 400, body: ["error": "validation_failed", "key": k, "reason": "missing"])
|
||||
case .typeMismatch(let k):
|
||||
send(connection: connection, status: 400, body: ["error": "validation_failed", "key": k, "reason": "type-mismatch"])
|
||||
case .schemaMismatch(let expected, let got):
|
||||
send(connection: connection, status: 409, body: ["error": "schema_mismatch", "expected_hash": expected, "got_hash": got])
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Stubs (real impls live in DebugBridgeManager + UIKit)
|
||||
|
||||
private func handleElements(connection: NWConnection) {
|
||||
let tree = ElementsBridge.snapshot()
|
||||
send(connection: connection, status: 200, body: ["elements": tree])
|
||||
}
|
||||
|
||||
private func handleScreenshot(connection: NWConnection) {
|
||||
if let png = ScreenshotBridge.capturePNG() {
|
||||
send(connection: connection, status: 200, body: ["png_base64": png.base64EncodedString()])
|
||||
} else {
|
||||
send(connection: connection, status: 500, body: ["error": "screenshot_unavailable"])
|
||||
}
|
||||
}
|
||||
|
||||
private func handleMutation(connection: NWConnection, request: ParsedRequest, op: String) {
|
||||
guard requireSession(in: request, connection: connection) else { return }
|
||||
guard let payload = try? JSONSerialization.jsonObject(with: request.body) as? JSONDict else {
|
||||
send(connection: connection, status: 400, body: ["error": "invalid_json"])
|
||||
return
|
||||
}
|
||||
let ok = MutationBridge.dispatch(op: op, payload: payload)
|
||||
send(connection: connection, status: ok ? 200 : 400, body: ["op": op, "ok": ok])
|
||||
}
|
||||
|
||||
// MARK: Response
|
||||
|
||||
private func send(connection: NWConnection, status: Int, body: JSONDict) {
|
||||
let json = (try? JSONSerialization.data(withJSONObject: body)) ?? Data("{}".utf8)
|
||||
let statusText: String
|
||||
switch status {
|
||||
case 200: statusText = "OK"
|
||||
case 400: statusText = "Bad Request"
|
||||
case 401: statusText = "Unauthorized"
|
||||
case 404: statusText = "Not Found"
|
||||
case 409: statusText = "Conflict"
|
||||
case 413: statusText = "Payload Too Large"
|
||||
case 423: statusText = "Locked"
|
||||
case 429: statusText = "Too Many Requests"
|
||||
case 500: statusText = "Internal Server Error"
|
||||
case 503: statusText = "Service Unavailable"
|
||||
default: statusText = "Status"
|
||||
}
|
||||
let header = "HTTP/1.1 \(status) \(statusText)\r\nContent-Type: application/json\r\nContent-Length: \(json.count)\r\nConnection: close\r\n\r\n"
|
||||
var packet = Data(header.utf8)
|
||||
packet.append(json)
|
||||
connection.send(content: packet, completion: .contentProcessed { _ in
|
||||
connection.cancel()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Bridges (implementation provided by DebugBridgeManager)
|
||||
|
||||
@MainActor
|
||||
public enum ElementsBridge {
|
||||
public static var resolver: () -> [JSONDict] = { [] }
|
||||
static func snapshot() -> [JSONDict] { resolver() }
|
||||
}
|
||||
|
||||
@MainActor
|
||||
public enum ScreenshotBridge {
|
||||
public static var resolver: () -> Data? = { nil }
|
||||
static func capturePNG() -> Data? { resolver() }
|
||||
}
|
||||
|
||||
@MainActor
|
||||
public enum MutationBridge {
|
||||
public static var resolver: (String, JSONDict) -> Bool = { _, _ in false }
|
||||
static func dispatch(op: String, payload: JSONDict) -> Bool { resolver(op, payload) }
|
||||
}
|
||||
|
||||
#endif // DEBUG
|
||||
@@ -0,0 +1,301 @@
|
||||
//
|
||||
// DebugBridgeTouch.m — minimal port of KIF's in-process touch synthesis.
|
||||
// Original code: https://github.com/kif-framework/KIF — MIT-licensed
|
||||
// (Square, Inc. + KIF contributors). Adapted to a single-file, tap-only,
|
||||
// iOS 18+ aware subset for the gstack/ios-qa DebugBridge.
|
||||
//
|
||||
// Uses these private UIKit selectors (DEBUG-only; never shipped to App Store):
|
||||
// UITouch: _setLocationInWindow:resetPrevious:, _setIsFirstTouchForView:,
|
||||
// setPhase:, setTimestamp:, setView:, setWindow:, setTapCount:,
|
||||
// _setHidEvent:
|
||||
// UIEvent: _clearTouches, _addTouch:forDelayedDelivery:, _setHIDEvent:
|
||||
// UIApplication: _touchesEvent
|
||||
// UIView: _hitTestWithContext: (iOS 18+ for SwiftUI hit-testing)
|
||||
// NSObject: _UIHitTestContext contextWithPoint:radius: (iOS 18+)
|
||||
//
|
||||
// IOKit private symbols (linked dynamically via the IOKit framework on iOS):
|
||||
// IOHIDEventCreateDigitizerEvent, IOHIDEventCreateDigitizerFingerEventWithQuality,
|
||||
// IOHIDEventSetIntegerValue, IOHIDEventAppendEvent.
|
||||
|
||||
#import "DebugBridgeTouch.h"
|
||||
#import <TargetConditionals.h>
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <objc/runtime.h>
|
||||
#import <objc/message.h>
|
||||
#import <mach/mach_time.h>
|
||||
|
||||
#pragma mark - IOHIDEvent (private symbols from IOKit)
|
||||
|
||||
typedef struct __IOHIDEvent * IOHIDEventRef;
|
||||
|
||||
#define IOHIDEventFieldBase(type) (type << 16)
|
||||
#ifdef __LP64__
|
||||
typedef double IOHIDFloat;
|
||||
#else
|
||||
typedef float IOHIDFloat;
|
||||
#endif
|
||||
typedef UInt32 IOOptionBits;
|
||||
typedef uint32_t IOHIDDigitizerTransducerType;
|
||||
typedef uint32_t IOHIDEventField;
|
||||
|
||||
enum {
|
||||
kIOHIDDigitizerTransducerTypeStylus = 0,
|
||||
kIOHIDDigitizerTransducerTypePuck,
|
||||
kIOHIDDigitizerTransducerTypeFinger,
|
||||
kIOHIDDigitizerTransducerTypeHand
|
||||
};
|
||||
|
||||
enum {
|
||||
kIOHIDEventTypeDigitizer = 11,
|
||||
};
|
||||
|
||||
enum {
|
||||
kIOHIDDigitizerEventRange = 0x00000001,
|
||||
kIOHIDDigitizerEventTouch = 0x00000002,
|
||||
kIOHIDDigitizerEventPosition = 0x00000004,
|
||||
};
|
||||
|
||||
enum {
|
||||
kIOHIDEventFieldDigitizerX = IOHIDEventFieldBase(kIOHIDEventTypeDigitizer),
|
||||
kIOHIDEventFieldDigitizerY,
|
||||
kIOHIDEventFieldDigitizerZ,
|
||||
kIOHIDEventFieldDigitizerButtonMask,
|
||||
kIOHIDEventFieldDigitizerType,
|
||||
kIOHIDEventFieldDigitizerIndex,
|
||||
kIOHIDEventFieldDigitizerIdentity,
|
||||
kIOHIDEventFieldDigitizerEventMask,
|
||||
kIOHIDEventFieldDigitizerRange,
|
||||
kIOHIDEventFieldDigitizerTouch,
|
||||
kIOHIDEventFieldDigitizerPressure,
|
||||
kIOHIDEventFieldDigitizerAuxiliaryPressure,
|
||||
kIOHIDEventFieldDigitizerTwist,
|
||||
kIOHIDEventFieldDigitizerTiltX,
|
||||
kIOHIDEventFieldDigitizerTiltY,
|
||||
kIOHIDEventFieldDigitizerAltitude,
|
||||
kIOHIDEventFieldDigitizerAzimuth,
|
||||
kIOHIDEventFieldDigitizerQuality,
|
||||
kIOHIDEventFieldDigitizerDensity,
|
||||
kIOHIDEventFieldDigitizerIrregularity,
|
||||
kIOHIDEventFieldDigitizerMajorRadius,
|
||||
kIOHIDEventFieldDigitizerMinorRadius,
|
||||
kIOHIDEventFieldDigitizerCollection,
|
||||
kIOHIDEventFieldDigitizerCollectionChord,
|
||||
kIOHIDEventFieldDigitizerChildEventMask,
|
||||
kIOHIDEventFieldDigitizerIsDisplayIntegrated,
|
||||
};
|
||||
|
||||
// IOKit is a PRIVATE framework on iOS — we can't link it via -framework. Load
|
||||
// at runtime via dlopen/dlsym. This is the standard approach for KIF-style
|
||||
// touch synthesis on iOS, including in DEBUG-only test harnesses.
|
||||
#import <dlfcn.h>
|
||||
|
||||
typedef IOHIDEventRef (*IOHIDEventCreateDigitizerEventFn)(CFAllocatorRef, AbsoluteTime,
|
||||
IOHIDDigitizerTransducerType, uint32_t, uint32_t, uint32_t, uint32_t,
|
||||
IOHIDFloat, IOHIDFloat, IOHIDFloat, IOHIDFloat, IOHIDFloat, Boolean, Boolean, IOOptionBits);
|
||||
|
||||
typedef IOHIDEventRef (*IOHIDEventCreateDigitizerFingerEventWithQualityFn)(CFAllocatorRef,
|
||||
AbsoluteTime, uint32_t, uint32_t, uint32_t, IOHIDFloat, IOHIDFloat, IOHIDFloat,
|
||||
IOHIDFloat, IOHIDFloat, IOHIDFloat, IOHIDFloat, IOHIDFloat, IOHIDFloat,
|
||||
IOHIDFloat, Boolean, Boolean, IOOptionBits);
|
||||
|
||||
typedef void (*IOHIDEventSetIntegerValueFn)(IOHIDEventRef, IOHIDEventField, int);
|
||||
typedef void (*IOHIDEventAppendEventFn)(IOHIDEventRef, IOHIDEventRef);
|
||||
|
||||
static IOHIDEventCreateDigitizerEventFn _IOHIDEventCreateDigitizerEvent;
|
||||
static IOHIDEventCreateDigitizerFingerEventWithQualityFn _IOHIDEventCreateDigitizerFingerEventWithQuality;
|
||||
static IOHIDEventSetIntegerValueFn _IOHIDEventSetIntegerValue;
|
||||
static IOHIDEventAppendEventFn _IOHIDEventAppendEvent;
|
||||
|
||||
static BOOL _IOKitLoaded = NO;
|
||||
static BOOL DBT_LoadIOKit(void) {
|
||||
if (_IOKitLoaded) return YES;
|
||||
void *handle = dlopen("/System/Library/Frameworks/IOKit.framework/IOKit", RTLD_NOW);
|
||||
if (!handle) {
|
||||
handle = dlopen("/System/Library/PrivateFrameworks/IOKit.framework/IOKit", RTLD_NOW);
|
||||
}
|
||||
if (!handle) return NO;
|
||||
_IOHIDEventCreateDigitizerEvent = (IOHIDEventCreateDigitizerEventFn)dlsym(handle, "IOHIDEventCreateDigitizerEvent");
|
||||
_IOHIDEventCreateDigitizerFingerEventWithQuality = (IOHIDEventCreateDigitizerFingerEventWithQualityFn)dlsym(handle, "IOHIDEventCreateDigitizerFingerEventWithQuality");
|
||||
_IOHIDEventSetIntegerValue = (IOHIDEventSetIntegerValueFn)dlsym(handle, "IOHIDEventSetIntegerValue");
|
||||
_IOHIDEventAppendEvent = (IOHIDEventAppendEventFn)dlsym(handle, "IOHIDEventAppendEvent");
|
||||
_IOKitLoaded = (_IOHIDEventCreateDigitizerEvent && _IOHIDEventCreateDigitizerFingerEventWithQuality &&
|
||||
_IOHIDEventSetIntegerValue && _IOHIDEventAppendEvent);
|
||||
return _IOKitLoaded;
|
||||
}
|
||||
|
||||
static IOHIDEventRef DBT_IOHIDEventWithTouch(UITouch *touch) CF_RETURNS_RETAINED;
|
||||
static IOHIDEventRef DBT_IOHIDEventWithTouch(UITouch *touch) {
|
||||
if (!DBT_LoadIOKit()) return NULL;
|
||||
uint64_t abTime = mach_absolute_time();
|
||||
AbsoluteTime timeStamp;
|
||||
timeStamp.hi = (UInt32)(abTime >> 32);
|
||||
timeStamp.lo = (UInt32)(abTime);
|
||||
|
||||
IOHIDEventRef handEvent = _IOHIDEventCreateDigitizerEvent(kCFAllocatorDefault,
|
||||
timeStamp, kIOHIDDigitizerTransducerTypeHand,
|
||||
0, 0, kIOHIDDigitizerEventTouch, 0,
|
||||
0, 0, 0, 0, 0,
|
||||
0, true, 0);
|
||||
_IOHIDEventSetIntegerValue(handEvent, kIOHIDEventFieldDigitizerIsDisplayIntegrated, 1);
|
||||
|
||||
uint32_t eventMask = (touch.phase == UITouchPhaseMoved)
|
||||
? kIOHIDDigitizerEventPosition
|
||||
: (kIOHIDDigitizerEventRange | kIOHIDDigitizerEventTouch);
|
||||
uint32_t isTouching = (touch.phase == UITouchPhaseEnded) ? 0 : 1;
|
||||
|
||||
CGPoint loc = [touch locationInView:touch.window];
|
||||
|
||||
IOHIDEventRef fingerEvent = _IOHIDEventCreateDigitizerFingerEventWithQuality(kCFAllocatorDefault,
|
||||
timeStamp, 1, 2, eventMask,
|
||||
(IOHIDFloat)loc.x, (IOHIDFloat)loc.y, 0.0,
|
||||
0, 0, 5.0, 5.0, 1.0, 1.0, 1.0,
|
||||
(IOHIDFloat)isTouching, (IOHIDFloat)isTouching, 0);
|
||||
_IOHIDEventSetIntegerValue(fingerEvent, kIOHIDEventFieldDigitizerIsDisplayIntegrated, 1);
|
||||
|
||||
_IOHIDEventAppendEvent(handEvent, fingerEvent);
|
||||
CFRelease(fingerEvent);
|
||||
|
||||
return handEvent;
|
||||
}
|
||||
|
||||
#pragma mark - Private selectors
|
||||
|
||||
@interface UITouch ()
|
||||
- (void)setWindow:(UIWindow *)window;
|
||||
- (void)setView:(UIView *)view;
|
||||
- (void)setTapCount:(NSUInteger)tapCount;
|
||||
- (void)setTimestamp:(NSTimeInterval)timestamp;
|
||||
- (void)setPhase:(UITouchPhase)touchPhase;
|
||||
- (void)setGestureView:(UIView *)view;
|
||||
- (void)_setLocationInWindow:(CGPoint)location resetPrevious:(BOOL)resetPrevious;
|
||||
- (void)_setIsFirstTouchForView:(BOOL)firstTouchForView;
|
||||
- (void)_setHidEvent:(IOHIDEventRef)event;
|
||||
@end
|
||||
|
||||
@interface UIEvent (DBTPrivate)
|
||||
- (void)_clearTouches;
|
||||
- (void)_addTouch:(UITouch *)touch forDelayedDelivery:(BOOL)delayed;
|
||||
- (void)_setHIDEvent:(IOHIDEventRef)event;
|
||||
- (void)_setTimestamp:(NSTimeInterval)timestamp;
|
||||
@end
|
||||
|
||||
@interface UIApplication (DBTPrivate)
|
||||
- (UIEvent *)_touchesEvent;
|
||||
@end
|
||||
|
||||
@interface UIView (DBTPrivate)
|
||||
- (id)_hitTestWithContext:(id)context;
|
||||
@end
|
||||
|
||||
#pragma mark - SwiftUI-aware hit test (iOS 18+)
|
||||
|
||||
// Returns `id` because iOS 18's _hitTestWithContext: can return either a UIView
|
||||
// OR a SwiftUI.UIKitGestureContainer (a plain UIResponder, NOT a UIView).
|
||||
// The latter is the case for SwiftUI Buttons. KIF's observation: the returned
|
||||
// responder is still compatible with UITouch.setView: even when it isn't a
|
||||
// UIView — so we pass it through as-is. Filtering by isKindOfClass:UIView
|
||||
// here would drop every SwiftUI Button tap silently. Mirrors KIF PR #1323.
|
||||
static id DBT_HitTestView(UIWindow *window, CGPoint point) {
|
||||
UIView *fallback = [window hitTest:point withEvent:nil];
|
||||
|
||||
if (@available(iOS 18.0, *)) {
|
||||
Class ctxClass = NSClassFromString(@"_UIHitTestContext");
|
||||
SEL ctxSel = NSSelectorFromString(@"contextWithPoint:radius:");
|
||||
if (ctxClass && [ctxClass respondsToSelector:ctxSel] &&
|
||||
[UIView instancesRespondToSelector:@selector(_hitTestWithContext:)]) {
|
||||
id (*sendCtx)(id, SEL, CGPoint, CGFloat) =
|
||||
(id (*)(id, SEL, CGPoint, CGFloat))objc_msgSend;
|
||||
id ctx = sendCtx(ctxClass, ctxSel, point, 0);
|
||||
if (ctx) {
|
||||
id found = nil;
|
||||
UIView *current = fallback;
|
||||
while (found == nil && current != nil) {
|
||||
found = [current _hitTestWithContext:ctx];
|
||||
current = current.superview;
|
||||
}
|
||||
if (found) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
#pragma mark - Public API
|
||||
|
||||
@implementation DebugBridgeTouch
|
||||
|
||||
+ (BOOL)sendTapAtPoint:(CGPoint)point inWindow:(UIWindow *)window {
|
||||
if (!window) return NO;
|
||||
|
||||
id hit = DBT_HitTestView(window, point);
|
||||
if (!hit) return NO;
|
||||
|
||||
// Build a single synthetic UITouch via private setters. Order matters —
|
||||
// setWindow: clears internal state and must come first.
|
||||
UITouch *touch = [[UITouch alloc] init];
|
||||
[touch setWindow:window];
|
||||
[touch setTapCount:1];
|
||||
[touch _setLocationInWindow:point resetPrevious:YES];
|
||||
// setView: typed UIView * but accepts SwiftUI.UIKitGestureContainer
|
||||
// (UIResponder) too — that's how SwiftUI Buttons get routed on iOS 18+.
|
||||
[touch setView:(UIView *)hit];
|
||||
[touch setPhase:UITouchPhaseBegan];
|
||||
if ([touch respondsToSelector:@selector(_setIsFirstTouchForView:)]) {
|
||||
[touch _setIsFirstTouchForView:YES];
|
||||
}
|
||||
[touch setTimestamp:[[NSProcessInfo processInfo] systemUptime]];
|
||||
if ([touch respondsToSelector:@selector(setGestureView:)] &&
|
||||
[hit isKindOfClass:[UIView class]]) {
|
||||
[touch setGestureView:(UIView *)hit];
|
||||
}
|
||||
|
||||
// Attach a real IOHIDEvent (required iOS 9+).
|
||||
IOHIDEventRef hidEventBegan = DBT_IOHIDEventWithTouch(touch);
|
||||
[touch _setHidEvent:hidEventBegan];
|
||||
|
||||
UIEvent *event = [[UIApplication sharedApplication] _touchesEvent];
|
||||
if (!event) {
|
||||
CFRelease(hidEventBegan);
|
||||
return NO;
|
||||
}
|
||||
[event _clearTouches];
|
||||
[event _setHIDEvent:hidEventBegan];
|
||||
[event _addTouch:touch forDelayedDelivery:NO];
|
||||
|
||||
[[UIApplication sharedApplication] sendEvent:event];
|
||||
CFRelease(hidEventBegan);
|
||||
|
||||
// Ended phase
|
||||
[touch setPhase:UITouchPhaseEnded];
|
||||
[touch setTimestamp:[[NSProcessInfo processInfo] systemUptime]];
|
||||
IOHIDEventRef hidEventEnded = DBT_IOHIDEventWithTouch(touch);
|
||||
[touch _setHidEvent:hidEventEnded];
|
||||
[event _clearTouches];
|
||||
[event _setHIDEvent:hidEventEnded];
|
||||
[event _addTouch:touch forDelayedDelivery:NO];
|
||||
[[UIApplication sharedApplication] sendEvent:event];
|
||||
CFRelease(hidEventEnded);
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#else // !TARGET_OS_IOS
|
||||
|
||||
// macOS / Catalyst / other non-iOS host build: no-op stub so the module
|
||||
// resolves cleanly without UIKit or IOKit. The Swift cross-platform tests
|
||||
// don't exercise touch synthesis; that's iOS-only by definition.
|
||||
@implementation DebugBridgeTouch
|
||||
+ (BOOL)sendTapAtPoint:(CGPoint)point inWindow:(UIWindow *)window {
|
||||
(void)point; (void)window;
|
||||
return NO;
|
||||
}
|
||||
@end
|
||||
|
||||
#endif // TARGET_OS_IOS
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// DebugBridgeTouch.h — public Objective-C interface for in-process touch
|
||||
// synthesis. Implementation derived from KIF (https://github.com/kif-framework/KIF),
|
||||
// MIT-licensed. The minimal subset needed to deliver a real UITouch to a
|
||||
// point on the key window, including SwiftUI Buttons via iOS 18+
|
||||
// _UIHitTestContext. DEBUG-only — never link in Release.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CoreGraphics/CoreGraphics.h>
|
||||
#import <TargetConditionals.h>
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
#import <UIKit/UIKit.h>
|
||||
#else
|
||||
// macOS build: forward-declare UIWindow so the module compiles without UIKit.
|
||||
// The host CI runs swift build on macOS to validate the cross-platform Swift
|
||||
// surface; DebugBridgeTouch's implementation is a no-op there. On iOS the
|
||||
// real UIWindow comes from UIKit and the implementation is active.
|
||||
@class UIWindow;
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface DebugBridgeTouch : NSObject
|
||||
|
||||
/// Synthesize a single tap (TouchPhaseBegan + TouchPhaseEnded) at the given
|
||||
/// window-coordinate point. Returns YES if the touch was delivered (a hit
|
||||
/// view was found and the event passed through UIApplication.sendEvent).
|
||||
/// On non-iOS platforms returns NO unconditionally.
|
||||
+ (BOOL)sendTapAtPoint:(CGPoint)point inWindow:(UIWindow *)window;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,308 @@
|
||||
// AUTO-GENERATED from gstack/ios-qa/templates/Bridges.swift.template
|
||||
//
|
||||
// Real UIKit-backed implementations of the three bridges StateServer
|
||||
// declares: ScreenshotBridge (PNG capture), ElementsBridge (accessibility
|
||||
// tree), MutationBridge (tap/swipe/type via accessibility actions + hit
|
||||
// testing). Everything #if DEBUG && canImport(UIKit) so Release builds
|
||||
// don't link UIKit or carry any of this code.
|
||||
//
|
||||
// Wire from the consuming app:
|
||||
//
|
||||
// #if DEBUG && canImport(UIKit)
|
||||
// import DebugBridgeUI
|
||||
// DebugBridgeUIWiring.installAll()
|
||||
// #endif
|
||||
|
||||
#if DEBUG && canImport(UIKit)
|
||||
|
||||
import DebugBridgeCore
|
||||
import DebugBridgeTouch
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
@MainActor
|
||||
public enum DebugBridgeUIWiring {
|
||||
/// Install all three bridge resolvers. Idempotent — calling multiple
|
||||
/// times reinstalls the same closures. Must be called on @MainActor
|
||||
/// because every UIKit access requires the main actor.
|
||||
public static func installAll() {
|
||||
ScreenshotBridge.resolver = { ScreenshotBridgeImpl.capturePNG() }
|
||||
ElementsBridge.resolver = { ElementsBridgeImpl.snapshot() }
|
||||
MutationBridge.resolver = { op, payload in MutationBridgeImpl.dispatch(op: op, payload: payload) }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ScreenshotBridge implementation
|
||||
|
||||
@MainActor
|
||||
enum ScreenshotBridgeImpl {
|
||||
/// Capture a PNG of the active window. Uses UIGraphicsImageRenderer
|
||||
/// (modern API, replaces UIGraphicsBeginImageContext). Returns nil if
|
||||
/// no key window is available (e.g., app backgrounded).
|
||||
static func capturePNG() -> Data? {
|
||||
guard let scene = activeScene(), let window = activeKeyWindow(in: scene) else { return nil }
|
||||
let bounds = window.bounds
|
||||
let renderer = UIGraphicsImageRenderer(bounds: bounds)
|
||||
let image = renderer.image { _ in
|
||||
// drawHierarchy is the documented way to snapshot real UIKit
|
||||
// layers including layer-backed views. afterScreenUpdates: false
|
||||
// because we want the CURRENT visible state, not a forced layout.
|
||||
window.drawHierarchy(in: bounds, afterScreenUpdates: false)
|
||||
}
|
||||
return image.pngData()
|
||||
}
|
||||
|
||||
private static func activeScene() -> UIWindowScene? {
|
||||
UIApplication.shared.connectedScenes
|
||||
.compactMap { $0 as? UIWindowScene }
|
||||
.first { $0.activationState == .foregroundActive }
|
||||
?? (UIApplication.shared.connectedScenes.first as? UIWindowScene)
|
||||
}
|
||||
|
||||
private static func activeKeyWindow(in scene: UIWindowScene) -> UIWindow? {
|
||||
scene.windows.first(where: { $0.isKeyWindow }) ?? scene.windows.first
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ElementsBridge implementation
|
||||
|
||||
@MainActor
|
||||
enum ElementsBridgeImpl {
|
||||
/// Walk the accessibility hierarchy + emit a flat list of elements.
|
||||
/// Each entry has frame (in window coords), accessibility label,
|
||||
/// identifier, traits as a bitmask, and a parent path. Skips
|
||||
/// non-accessible / hidden views.
|
||||
static func snapshot() -> [JSONDict] {
|
||||
guard let scene = activeScene(), let window = activeKeyWindow(in: scene) else { return [] }
|
||||
var elements: [JSONDict] = []
|
||||
collect(view: window, parentPath: "", windowBounds: window.bounds, into: &elements)
|
||||
return elements
|
||||
}
|
||||
|
||||
private static func collect(view: UIView, parentPath: String, windowBounds: CGRect, into elements: inout [JSONDict]) {
|
||||
// Skip hidden / zero-size / off-screen subtrees early.
|
||||
if view.isHidden || view.alpha < 0.01 { return }
|
||||
|
||||
let frameInWindow = view.convert(view.bounds, to: nil)
|
||||
if !windowBounds.intersects(frameInWindow) { return }
|
||||
|
||||
let isAccessible = view.isAccessibilityElement
|
||||
let label = view.accessibilityLabel ?? ""
|
||||
let identifier = view.accessibilityIdentifier ?? ""
|
||||
let traits = Int(view.accessibilityTraits.rawValue)
|
||||
let value = (view.accessibilityValue ?? "") as String
|
||||
let className = String(describing: type(of: view))
|
||||
let path = parentPath.isEmpty ? className : "\(parentPath) > \(className)"
|
||||
|
||||
// Emit if any of:
|
||||
// - Marked accessible (covers UIKit-native widgets)
|
||||
// - Has explicit AX label / identifier
|
||||
// - Is a known interactive type (UIControl, UITextField, UIScrollView)
|
||||
// - Hosts a SwiftUI view (UIHostingController's view class)
|
||||
let isInteractive = view is UIControl || view is UIScrollView || view is UITextInput
|
||||
let isHosting = className.contains("Hosting") || className.contains("SwiftUI")
|
||||
if isAccessible || !label.isEmpty || !identifier.isEmpty || isInteractive || isHosting {
|
||||
elements.append([
|
||||
"path": path,
|
||||
"class": className,
|
||||
"label": label,
|
||||
"identifier": identifier,
|
||||
"value": value,
|
||||
"traits": traits,
|
||||
"frame": [
|
||||
"x": Int(frameInWindow.origin.x),
|
||||
"y": Int(frameInWindow.origin.y),
|
||||
"w": Int(frameInWindow.size.width),
|
||||
"h": Int(frameInWindow.size.height),
|
||||
],
|
||||
"is_user_interaction_enabled": view.isUserInteractionEnabled,
|
||||
])
|
||||
}
|
||||
|
||||
// Recurse into accessibility-elements first (some custom views vend
|
||||
// synthetic children), then UIView subviews. SwiftUI's host views
|
||||
// populate accessibilityElements lazily — many return nil before
|
||||
// VoiceOver triggers them. Force population by reading accessibilityElementCount.
|
||||
_ = view.accessibilityElementCount()
|
||||
if let axElements = view.accessibilityElements {
|
||||
for case let element as NSObject in axElements {
|
||||
if let v = element as? UIView {
|
||||
collect(view: v, parentPath: path, windowBounds: windowBounds, into: &elements)
|
||||
} else {
|
||||
// Synthetic accessibility element (no UIView). Capture frame in screen coords.
|
||||
let af = (element.value(forKey: "accessibilityFrame") as? CGRect) ?? .zero
|
||||
elements.append([
|
||||
"path": "\(path) > <synthetic>",
|
||||
"class": "AccessibilityElement",
|
||||
"label": (element.value(forKey: "accessibilityLabel") as? String) ?? "",
|
||||
"identifier": (element.value(forKey: "accessibilityIdentifier") as? String) ?? "",
|
||||
"value": (element.value(forKey: "accessibilityValue") as? String) ?? "",
|
||||
"traits": (element.value(forKey: "accessibilityTraits") as? NSNumber)?.intValue ?? 0,
|
||||
"frame": [
|
||||
"x": Int(af.origin.x),
|
||||
"y": Int(af.origin.y),
|
||||
"w": Int(af.size.width),
|
||||
"h": Int(af.size.height),
|
||||
],
|
||||
"is_user_interaction_enabled": true,
|
||||
])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// accessibilityElements is nil — iterate by index. SwiftUI uses
|
||||
// this dynamic protocol pattern; many AX elements only respond
|
||||
// to accessibilityElementCount + accessibilityElement(at:).
|
||||
let count = view.accessibilityElementCount()
|
||||
for i in 0..<count {
|
||||
guard let element = view.accessibilityElement(at: i) as? NSObject else { continue }
|
||||
if let v = element as? UIView {
|
||||
collect(view: v, parentPath: path, windowBounds: windowBounds, into: &elements)
|
||||
} else {
|
||||
let af = (element.value(forKey: "accessibilityFrame") as? CGRect) ?? .zero
|
||||
elements.append([
|
||||
"path": "\(path) > <ax\(i)>",
|
||||
"class": String(describing: type(of: element)),
|
||||
"label": (element.value(forKey: "accessibilityLabel") as? String) ?? "",
|
||||
"identifier": (element.value(forKey: "accessibilityIdentifier") as? String) ?? "",
|
||||
"value": (element.value(forKey: "accessibilityValue") as? String) ?? "",
|
||||
"traits": (element.value(forKey: "accessibilityTraits") as? NSNumber)?.intValue ?? 0,
|
||||
"frame": [
|
||||
"x": Int(af.origin.x),
|
||||
"y": Int(af.origin.y),
|
||||
"w": Int(af.size.width),
|
||||
"h": Int(af.size.height),
|
||||
],
|
||||
"is_user_interaction_enabled": true,
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
for sub in view.subviews {
|
||||
collect(view: sub, parentPath: path, windowBounds: windowBounds, into: &elements)
|
||||
}
|
||||
}
|
||||
|
||||
private static func activeScene() -> UIWindowScene? {
|
||||
UIApplication.shared.connectedScenes
|
||||
.compactMap { $0 as? UIWindowScene }
|
||||
.first { $0.activationState == .foregroundActive }
|
||||
?? (UIApplication.shared.connectedScenes.first as? UIWindowScene)
|
||||
}
|
||||
|
||||
private static func activeKeyWindow(in scene: UIWindowScene) -> UIWindow? {
|
||||
scene.windows.first(where: { $0.isKeyWindow }) ?? scene.windows.first
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - MutationBridge implementation
|
||||
|
||||
@MainActor
|
||||
enum MutationBridgeImpl {
|
||||
/// Route a mutation op to the right handler. Returns true on success,
|
||||
/// false on failure (which the StateServer surfaces as 400 to the agent).
|
||||
static func dispatch(op: String, payload: JSONDict) -> Bool {
|
||||
switch op {
|
||||
case "tap": return handleTap(payload)
|
||||
case "type": return handleType(payload)
|
||||
case "swipe": return handleSwipe(payload)
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
||||
/// Tap at (x, y) in window coordinates. Delegates to DebugBridgeTouch
|
||||
/// (KIF-derived in-process touch synthesis). The Obj-C target builds a
|
||||
/// real UITouch + IOHIDEvent + UIEvent and dispatches via
|
||||
/// `UIApplication.sendEvent`, which is what UIKit uses for real touches.
|
||||
/// This works for UIControl, SwiftUI Button (via iOS 18+
|
||||
/// `_UIHitTestContext`), gesture recognizers, and anything else that
|
||||
/// listens to the real event-dispatch path.
|
||||
private static func handleTap(_ payload: JSONDict) -> Bool {
|
||||
guard let x = payload["x"] as? NSNumber,
|
||||
let y = payload["y"] as? NSNumber else { return false }
|
||||
let point = CGPoint(x: x.doubleValue, y: y.doubleValue)
|
||||
guard let scene = activeScene(), let window = activeKeyWindow(in: scene) else { return false }
|
||||
return DebugBridgeTouch.sendTap(at: point, in: window)
|
||||
}
|
||||
|
||||
/// Set text on the first responder if it's a UITextField or UITextView.
|
||||
private static func handleType(_ payload: JSONDict) -> Bool {
|
||||
guard let text = payload["text"] as? String else { return false }
|
||||
guard let scene = activeScene(), let window = activeKeyWindow(in: scene) else { return false }
|
||||
guard let responder = findFirstResponder(in: window) else { return false }
|
||||
if let field = responder as? UITextField {
|
||||
field.text = text
|
||||
field.sendActions(for: .editingChanged)
|
||||
return true
|
||||
}
|
||||
if let view = responder as? UITextView {
|
||||
view.text = text
|
||||
view.delegate?.textViewDidChange?(view)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/// Swipe via UIScrollView programmatic scroll OR via setContentOffset on
|
||||
/// the deepest UIScrollView in the hit-tested ancestor chain. Less
|
||||
/// faithful than synthesized touches but covers common scroll scenarios.
|
||||
private static func handleSwipe(_ payload: JSONDict) -> Bool {
|
||||
guard let fx = payload["from_x"] as? NSNumber,
|
||||
let fy = payload["from_y"] as? NSNumber,
|
||||
let tx = payload["to_x"] as? NSNumber,
|
||||
let ty = payload["to_y"] as? NSNumber else { return false }
|
||||
let from = CGPoint(x: fx.doubleValue, y: fy.doubleValue)
|
||||
let to = CGPoint(x: tx.doubleValue, y: ty.doubleValue)
|
||||
|
||||
guard let scene = activeScene(), let window = activeKeyWindow(in: scene) else { return false }
|
||||
guard let hit = window.hitTest(from, with: nil) else { return false }
|
||||
|
||||
// Find the nearest enclosing UIScrollView.
|
||||
var node: UIView? = hit
|
||||
while let cur = node {
|
||||
if let scroll = cur as? UIScrollView {
|
||||
let dx = from.x - to.x
|
||||
let dy = from.y - to.y
|
||||
var off = scroll.contentOffset
|
||||
off.x = max(0, min(scroll.contentSize.width - scroll.bounds.width, off.x + dx))
|
||||
off.y = max(0, min(scroll.contentSize.height - scroll.bounds.height, off.y + dy))
|
||||
scroll.setContentOffset(off, animated: true)
|
||||
return true
|
||||
}
|
||||
node = cur.superview
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MARK: helpers
|
||||
|
||||
private static func walkUp(_ view: UIView) -> UIView? {
|
||||
var node: UIView? = view
|
||||
while let cur = node {
|
||||
if cur is UIControl { return cur }
|
||||
node = cur.superview
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
private static func findFirstResponder(in view: UIView) -> UIResponder? {
|
||||
if view.isFirstResponder { return view }
|
||||
for sub in view.subviews {
|
||||
if let found = findFirstResponder(in: sub) { return found }
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private static func activeScene() -> UIWindowScene? {
|
||||
UIApplication.shared.connectedScenes
|
||||
.compactMap { $0 as? UIWindowScene }
|
||||
.first { $0.activationState == .foregroundActive }
|
||||
?? (UIApplication.shared.connectedScenes.first as? UIWindowScene)
|
||||
}
|
||||
|
||||
private static func activeKeyWindow(in scene: UIWindowScene) -> UIWindow? {
|
||||
scene.windows.first(where: { $0.isKeyWindow }) ?? scene.windows.first
|
||||
}
|
||||
}
|
||||
|
||||
#endif // DEBUG && canImport(UIKit)
|
||||
@@ -0,0 +1,137 @@
|
||||
// AUTO-GENERATED from gstack/ios-qa/templates/DebugOverlay.swift.template
|
||||
//
|
||||
// DebugOverlay — on-device visual presence. Animated brand-colored border +
|
||||
// agent attribution chip + (optional) recording watermark. Renders above
|
||||
// sheets, alerts, and modals via a dedicated UIWindow with high windowLevel.
|
||||
//
|
||||
// Everything in this file is gated #if DEBUG and gone in Release.
|
||||
|
||||
#if DEBUG && canImport(UIKit)
|
||||
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
@MainActor
|
||||
public final class DebugOverlayWindow {
|
||||
public static let shared = DebugOverlayWindow()
|
||||
|
||||
private var window: UIWindow?
|
||||
|
||||
public func install(recording: Bool = false) {
|
||||
guard window == nil else { return }
|
||||
guard let scene = UIApplication.shared.connectedScenes.compactMap({ $0 as? UIWindowScene }).first else { return }
|
||||
|
||||
let w = PassThroughWindow(windowScene: scene)
|
||||
w.windowLevel = .alert + 1
|
||||
w.backgroundColor = .clear
|
||||
w.isUserInteractionEnabled = false
|
||||
|
||||
let host = UIHostingController(rootView: OverlayRoot(recording: recording))
|
||||
host.view.backgroundColor = .clear
|
||||
w.rootViewController = host
|
||||
w.isHidden = false
|
||||
|
||||
window = w
|
||||
}
|
||||
|
||||
public func setAttribution(_ identity: String) {
|
||||
OverlayAttributionState.shared.identity = identity
|
||||
}
|
||||
}
|
||||
|
||||
/// A window that lets touches pass through to underlying windows.
|
||||
private final class PassThroughWindow: UIWindow {
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
let view = super.hitTest(point, with: event)
|
||||
return view == rootViewController?.view ? nil : view
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
final class OverlayAttributionState: ObservableObject {
|
||||
static let shared = OverlayAttributionState()
|
||||
@Published var identity: String = "Claude Code (local)"
|
||||
}
|
||||
|
||||
private struct OverlayRoot: View {
|
||||
@StateObject private var attribution = OverlayAttributionState.shared
|
||||
@State private var phase: CGFloat = 0
|
||||
let recording: Bool
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
// Animated brand border
|
||||
BorderShape()
|
||||
.stroke(
|
||||
AngularGradient(
|
||||
gradient: Gradient(colors: [
|
||||
BrandColor.accent.opacity(0.0),
|
||||
BrandColor.accent.opacity(0.8),
|
||||
BrandColor.accent.opacity(0.0),
|
||||
]),
|
||||
center: .center,
|
||||
angle: .degrees(phase * 360)
|
||||
),
|
||||
lineWidth: 4
|
||||
)
|
||||
.ignoresSafeArea()
|
||||
.onAppear {
|
||||
withAnimation(.linear(duration: 2.0).repeatForever(autoreverses: false)) {
|
||||
phase = 1.0
|
||||
}
|
||||
}
|
||||
|
||||
// Attribution chip (top safe area)
|
||||
VStack {
|
||||
HStack {
|
||||
Spacer()
|
||||
Text("Driven by \(attribution.identity)")
|
||||
.font(.caption2.weight(.semibold))
|
||||
.foregroundColor(.white)
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 4)
|
||||
.background(
|
||||
Capsule().fill(BrandColor.accent.opacity(0.85))
|
||||
)
|
||||
.padding(.trailing, 12)
|
||||
.padding(.top, 8)
|
||||
Spacer().frame(width: 0)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
|
||||
// Recording watermark (diagonal, bottom-right)
|
||||
if recording {
|
||||
VStack {
|
||||
Spacer()
|
||||
HStack {
|
||||
Spacer()
|
||||
Text("AGENT DEMO")
|
||||
.font(.system(size: 10, weight: .heavy, design: .monospaced))
|
||||
.foregroundColor(.red.opacity(0.7))
|
||||
.rotationEffect(.degrees(-30))
|
||||
.padding(.trailing, 16)
|
||||
.padding(.bottom, 30)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.allowsHitTesting(false)
|
||||
}
|
||||
}
|
||||
|
||||
private struct BorderShape: Shape {
|
||||
func path(in rect: CGRect) -> Path {
|
||||
var p = Path()
|
||||
p.addRoundedRect(in: rect.insetBy(dx: 2, dy: 2), cornerSize: CGSize(width: 16, height: 16))
|
||||
return p
|
||||
}
|
||||
}
|
||||
|
||||
private enum BrandColor {
|
||||
// gstack brand color — resolved from DESIGN.md when codegen runs.
|
||||
// Default falls back to a deep blue.
|
||||
static let accent = Color(red: 0.0, green: 0.46, blue: 1.0)
|
||||
}
|
||||
|
||||
#endif // DEBUG && canImport(UIKit)
|
||||
@@ -0,0 +1,60 @@
|
||||
// FixtureApp — minimal SwiftUI app used by the ios-qa device-path E2E test.
|
||||
//
|
||||
// On launch:
|
||||
// 1. Boot StateServer (loopback :::1/127.0.0.1 + 9999)
|
||||
// 2. Log boot token to os_log so devicectl + the Mac daemon can scrape it
|
||||
// 3. Render a single ContentView so the app stays foreground
|
||||
//
|
||||
// Everything ios-qa-related is gated #if DEBUG. Release builds compile this
|
||||
// to a no-op app (no StateServer, no DebugBridge import, no overlay).
|
||||
|
||||
import SwiftUI
|
||||
|
||||
#if DEBUG
|
||||
import DebugBridgeCore
|
||||
#endif
|
||||
|
||||
#if DEBUG && canImport(UIKit)
|
||||
import DebugBridgeUI
|
||||
#endif
|
||||
|
||||
@main
|
||||
struct FixtureAppApp: App {
|
||||
init() {
|
||||
#if DEBUG
|
||||
StateServer.shared.start()
|
||||
// Wire the three UIKit-backed bridges so /screenshot, /elements,
|
||||
// /tap, /type, /swipe actually do something on the device.
|
||||
#if canImport(UIKit)
|
||||
DebugBridgeUIWiring.installAll()
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ContentView: View {
|
||||
@State private var counter: Int = 0
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 24) {
|
||||
Text("ios-qa fixture")
|
||||
.font(.largeTitle.bold())
|
||||
Text("StateServer should be on :9999")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
Button("Tap (\(counter))") {
|
||||
counter += 1
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.accessibilityIdentifier("tap-button")
|
||||
}
|
||||
.padding()
|
||||
.accessibilityIdentifier("fixture-content")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Canonical app state for the fixture. Every snapshot-eligible field is
|
||||
// marked with the @Snapshotable property wrapper that the codegen tool
|
||||
// detects via attribute scan.
|
||||
//
|
||||
// Note: we DON'T use @Observable here because the macro expansion converts
|
||||
// stored properties into computed ones, which the @Snapshotable wrapper
|
||||
// can't apply to. In production apps that need both observability AND
|
||||
// snapshotting, the right pattern is:
|
||||
// - Use ObservableObject + @Published (older API), or
|
||||
// - Hold all @Snapshotable state in a nested struct + replace it
|
||||
// wholesale on restore so SwiftUI sees a single change notification
|
||||
// (the canonical-state-struct atomicity strategy from the plan).
|
||||
|
||||
import Foundation
|
||||
|
||||
public final class FixtureAppState {
|
||||
@Snapshotable public var isLoggedIn: Bool = false
|
||||
@Snapshotable public var username: String = ""
|
||||
@Snapshotable public var tapCounter: Int = 0
|
||||
/// Not snapshotted — ephemeral cache that should never leak via /state/snapshot.
|
||||
public var ephemeralCache: [String: String] = [:]
|
||||
|
||||
public init() {}
|
||||
}
|
||||
|
||||
/// Property wrapper marker for snapshot-eligible state. The actual wrapper
|
||||
/// is a no-op at runtime; codegen-tool detection happens via attribute scan.
|
||||
@propertyWrapper
|
||||
public struct Snapshotable<Value> {
|
||||
public var wrappedValue: Value
|
||||
public init(wrappedValue: Value) { self.wrappedValue = wrappedValue }
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>ios-qa fixture</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>UILaunchScreen</key>
|
||||
<dict/>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
// XCTest unit test for StateServer. Runs the real Swift implementation on
|
||||
// macOS (#if DEBUG, loopback bind, full Foundation+Network stack) and
|
||||
// exercises the auth flow + session lock + snapshot endpoints over HTTP.
|
||||
//
|
||||
// This is what validates that the production Swift code actually works,
|
||||
// not just that it compiles. Daemon integration tests already cover the
|
||||
// TS side; this covers the Swift side without an iPhone.
|
||||
|
||||
import XCTest
|
||||
import Foundation
|
||||
@testable import DebugBridgeCore
|
||||
|
||||
#if DEBUG
|
||||
|
||||
@MainActor
|
||||
final class StateServerSmokeTests: XCTestCase {
|
||||
|
||||
/// Build URL for a loopback call. Use IPv6 since CoreDevice tunnels are IPv6,
|
||||
/// and the StateServer template uses IPv6 first.
|
||||
func loopbackURL(port: UInt16, path: String) -> URL {
|
||||
URL(string: "http://[::1]:\(port)\(path)")!
|
||||
}
|
||||
|
||||
/// Issue an HTTP request and decode JSON. Returns (status, body).
|
||||
func request(method: String, url: URL, headers: [String: String] = [:], body: Data? = nil) async throws -> (Int, [String: Any]) {
|
||||
var req = URLRequest(url: url)
|
||||
req.httpMethod = method
|
||||
for (k, v) in headers { req.setValue(v, forHTTPHeaderField: k) }
|
||||
if let body = body { req.httpBody = body }
|
||||
let (data, response) = try await URLSession.shared.data(for: req)
|
||||
let status = (response as? HTTPURLResponse)?.statusCode ?? 0
|
||||
let json = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any] ?? [:]
|
||||
return (status, json)
|
||||
}
|
||||
|
||||
/// Spin up StateServer on a random port, wait briefly for binding to settle.
|
||||
/// Returns the port. Uses StateServer.shared since it's a singleton.
|
||||
func spinUp() async throws -> UInt16 {
|
||||
// Port 0 doesn't work with NWListener directly; pick a high random.
|
||||
let port: UInt16 = UInt16.random(in: 30000...39999)
|
||||
StateServer.shared.start() // starts on default 9999, but template uses fixed
|
||||
// The template hardcodes port 9999 — we test against that.
|
||||
// Sleep briefly for binding to complete.
|
||||
try await Task.sleep(nanoseconds: 100_000_000) // 100ms
|
||||
return 9999
|
||||
}
|
||||
|
||||
func test_healthz_returns_200_without_auth() async throws {
|
||||
let port = try await spinUp()
|
||||
let (status, body) = try await request(method: "GET", url: loopbackURL(port: port, path: "/healthz"))
|
||||
XCTAssertEqual(status, 200, "healthz should return 200 without auth on loopback")
|
||||
XCTAssertEqual(body["version"] as? String, "1.0.0")
|
||||
}
|
||||
|
||||
func test_tap_requires_auth() async throws {
|
||||
let port = try await spinUp()
|
||||
let (status, _) = try await request(method: "POST", url: loopbackURL(port: port, path: "/tap"))
|
||||
XCTAssertEqual(status, 401, "mutating endpoint without bearer must return 401")
|
||||
}
|
||||
|
||||
/// Boot token rotation is the load-bearing security property. Confirm:
|
||||
/// 1. Boot token is required for /auth/rotate
|
||||
/// 2. After rotation, boot token is dead
|
||||
/// 3. Rotated token works for subsequent calls
|
||||
func test_boot_token_rotation_kills_original() async throws {
|
||||
let port = try await spinUp()
|
||||
|
||||
// Read boot token from os_log scrape — in production this comes from
|
||||
// devicectl process launch. For this test we can read it from the
|
||||
// bootTokenPath file. (StateServer writes a 0600 file as fallback.)
|
||||
let bootTokenPath = NSTemporaryDirectory() + "gstack-ios-qa.token"
|
||||
let bootToken = try? String(contentsOfFile: bootTokenPath, encoding: .utf8)
|
||||
guard let bt = bootToken?.trimmingCharacters(in: .whitespacesAndNewlines), !bt.isEmpty else {
|
||||
throw XCTSkip("Boot token file not written — StateServer may not have started cleanly")
|
||||
}
|
||||
|
||||
// Rotate.
|
||||
let newToken = "rotated-test-token-\(UUID().uuidString)"
|
||||
let rotateBody = try JSONSerialization.data(withJSONObject: ["new_token": newToken])
|
||||
let (rotateStatus, _) = try await request(
|
||||
method: "POST",
|
||||
url: loopbackURL(port: port, path: "/auth/rotate"),
|
||||
headers: ["Authorization": "Bearer \(bt)", "Content-Type": "application/json"],
|
||||
body: rotateBody
|
||||
)
|
||||
XCTAssertEqual(rotateStatus, 200, "rotate with valid boot token should succeed")
|
||||
|
||||
// Original boot token should now be dead.
|
||||
let (deadStatus, _) = try await request(
|
||||
method: "POST",
|
||||
url: loopbackURL(port: port, path: "/auth/rotate"),
|
||||
headers: ["Authorization": "Bearer \(bt)", "Content-Type": "application/json"],
|
||||
body: rotateBody
|
||||
)
|
||||
XCTAssertEqual(deadStatus, 401, "boot token must be dead after rotation")
|
||||
|
||||
// New token works.
|
||||
let (acqStatus, _) = try await request(
|
||||
method: "POST",
|
||||
url: loopbackURL(port: port, path: "/session/acquire"),
|
||||
headers: ["Authorization": "Bearer \(newToken)"]
|
||||
)
|
||||
XCTAssertEqual(acqStatus, 200, "rotated token must work for session acquire")
|
||||
}
|
||||
}
|
||||
|
||||
#endif // DEBUG
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
name: FixtureApp
|
||||
options:
|
||||
deploymentTarget:
|
||||
iOS: "16.0"
|
||||
bundleIdPrefix: com.gstack.iosqa
|
||||
developmentLanguage: en
|
||||
createIntermediateGroups: true
|
||||
|
||||
settings:
|
||||
DEVELOPMENT_TEAM: 623FYQ2M88
|
||||
CODE_SIGN_STYLE: Automatic
|
||||
ENABLE_USER_SCRIPT_SANDBOXING: NO
|
||||
# Personal-team bundle IDs are scoped per-team; this prefix is unique.
|
||||
PRODUCT_BUNDLE_IDENTIFIER: com.gstack.iosqa.fixture
|
||||
|
||||
# Local SPM package providing DebugBridgeCore + DebugBridgeUI as dependencies.
|
||||
# packages keyword (with `path:`) means a sibling local package next to project.yml.
|
||||
packages:
|
||||
DebugBridge:
|
||||
path: .
|
||||
|
||||
targets:
|
||||
FixtureApp:
|
||||
type: application
|
||||
platform: iOS
|
||||
deploymentTarget: "16.0"
|
||||
sources:
|
||||
- path: Sources/FixtureApp
|
||||
dependencies:
|
||||
- package: DebugBridge
|
||||
product: DebugBridgeCore
|
||||
- package: DebugBridge
|
||||
product: DebugBridgeUI
|
||||
info:
|
||||
path: Sources/FixtureApp/Info.plist
|
||||
properties:
|
||||
CFBundleDisplayName: ios-qa fixture
|
||||
UILaunchScreen: {}
|
||||
UISupportedInterfaceOrientations: [UIInterfaceOrientationPortrait]
|
||||
UIRequiredDeviceCapabilities: [arm64]
|
||||
settings:
|
||||
base:
|
||||
PRODUCT_BUNDLE_IDENTIFIER: com.gstack.iosqa.fixture
|
||||
DEVELOPMENT_TEAM: 623FYQ2M88
|
||||
CODE_SIGN_STYLE: Automatic
|
||||
TARGETED_DEVICE_FAMILY: "1"
|
||||
SWIFT_VERSION: "5.9"
|
||||
IPHONEOS_DEPLOYMENT_TARGET: "16.0"
|
||||
ENABLE_PREVIEWS: YES
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
# Weekend Project: Dependency Graph Visualizer
|
||||
|
||||
I want to build a tool that takes a codebase and visualizes its dependency graph — modules, imports, which files depend on which. For fun, for learning. Maybe open-source it.
|
||||
|
||||
## What I have so far
|
||||
|
||||
- Rough idea: point it at a repo, get an interactive graph
|
||||
- Stack I'm leaning toward: TypeScript + D3 or Cytoscape for rendering
|
||||
- Potential: could work for JS/TS first, maybe Python later
|
||||
|
||||
## What I don't know yet
|
||||
|
||||
- How to make the visualization actually useful vs just pretty
|
||||
- Whether this should be a CLI, a web tool, or a VS Code extension
|
||||
- What would make someone else want to use it
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
# Plan: Team Velocity Dashboard
|
||||
|
||||
## Context
|
||||
|
||||
We're building a dashboard for engineering managers to track team code velocity — commits per engineer, PR cycle time, review latency, CI pass rate. The data already lives in GitHub; we're just aggregating it for a manager's single-pane view.
|
||||
|
||||
## Changes
|
||||
|
||||
1. New React component `TeamVelocityDashboard` in `src/dashboard/`
|
||||
2. REST API endpoint `GET /api/team/velocity?days=30` returning aggregated metrics
|
||||
3. Background job pulling GitHub data every 15 minutes into Postgres
|
||||
4. Simple filter UI: team, date range, metric
|
||||
|
||||
## Architecture
|
||||
|
||||
- Frontend: React + shadcn/ui
|
||||
- Backend: Express + PostgreSQL
|
||||
- Data source: GitHub REST API (cached 15min)
|
||||
|
||||
## Open questions
|
||||
|
||||
- Should we support multiple repos per team?
|
||||
- Do we show individual engineer names or aggregate only?
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# Our Idea: AI Tools for Product Managers
|
||||
|
||||
We're building AI tools for product managers at mid-market SaaS companies. The product combines a bunch of the things PMs already do — writing PRDs, gathering user feedback, analyzing usage data, drafting roadmaps — and uses LLMs to speed each of them up.
|
||||
|
||||
## Who we're targeting
|
||||
|
||||
Product managers at SaaS companies with 50-500 engineers. These PMs are stretched thin, juggle a lot of surface area, and would benefit from AI assistance.
|
||||
|
||||
## What we've done so far
|
||||
|
||||
- Talked to a few PMs we know from prior jobs
|
||||
- Built a prototype that summarizes Zoom customer calls into a PRD stub
|
||||
- Got on a waitlist of about 40 signups from LinkedIn posts
|
||||
@@ -0,0 +1,30 @@
|
||||
# Founder pitch — pixel.fund
|
||||
|
||||
Founder: Maya Chen (CEO, ex-Stripe), co-founder Aria Patel (CTO,
|
||||
ex-Robinhood). YC W26.
|
||||
|
||||
## What
|
||||
|
||||
A donation-budget tool for solo creators. Set a monthly $ floor for
|
||||
causes you care about, pixel.fund auto-allocates each dollar across your
|
||||
chosen orgs (Direct Relief, GiveDirectly, etc.) the moment a Stripe
|
||||
payout lands. One-line embeddable receipt. 1% platform fee.
|
||||
|
||||
## Traction
|
||||
|
||||
- 2026-04-01 launched private beta with 14 creators from her newsletter
|
||||
- 2026-05-15 hit 51 paying creators, $4,200 MRR
|
||||
- Waitlist of 230 from a single tweet by a tech-Twitter influencer
|
||||
- Two creators asked about a "team plan" (multi-seat) unprompted
|
||||
|
||||
## Status quo
|
||||
|
||||
Creators today either (a) write checks ad-hoc and forget about it, or
|
||||
(b) use Patreon-style platforms where the "cause" is opaque (general
|
||||
fund). Maya talked to 40 creators in YC interviews — 31 said they "want
|
||||
to give more but it's mental overhead."
|
||||
|
||||
## What Maya wants from office hours
|
||||
|
||||
Should she chase the team-plan signal, or go deeper on the solo flow
|
||||
first? She's two weeks from running out of YC dorm food.
|
||||
Vendored
+487
@@ -0,0 +1,487 @@
|
||||
/**
|
||||
* Overlay-efficacy fixture registry.
|
||||
*
|
||||
* Each fixture defines a reproducible A/B test for one behavioral nudge
|
||||
* embedded in a model-overlays/*.md file. The harness at
|
||||
* test/skill-e2e-overlay-harness.test.ts iterates this registry and runs
|
||||
* `fixture.trials` A/B trials per fixture, asserting `fixture.pass(arms)`.
|
||||
*
|
||||
* Adding a new overlay eval = one entry in this list. The harness handles
|
||||
* arm wiring, concurrency, artifact storage, rate-limit retries, and the
|
||||
* cross-harness diagnostic.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import {
|
||||
firstTurnParallelism,
|
||||
type AgentSdkResult,
|
||||
} from '../helpers/agent-sdk-runner';
|
||||
|
||||
const REPO_ROOT = path.resolve(__dirname, '..', '..');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface OverlayFixture {
|
||||
/** Unique, lowercase/digits/dash only. Used in artifact paths. */
|
||||
id: string;
|
||||
/** Path to the overlay file, relative to repo root. */
|
||||
overlayPath: string;
|
||||
/** API model ID, not the overlay family name. */
|
||||
model: string;
|
||||
/** Integer >= 3. Trials per arm. */
|
||||
trials: number;
|
||||
/** Max concurrent queries for this fixture's arms. Default 3. */
|
||||
concurrency?: number;
|
||||
/** Populate the workspace dir before each trial. */
|
||||
setupWorkspace: (dir: string) => void;
|
||||
/** The prompt the model receives. Non-empty. */
|
||||
userPrompt: string;
|
||||
/** Per-fixture tool allowlist. Omit to use runner default [Read, Glob, Grep, Bash]. */
|
||||
allowedTools?: string[];
|
||||
/** Max turns per trial. Omit to use runner default (5). */
|
||||
maxTurns?: number;
|
||||
/**
|
||||
* Direction of the expected effect. `higher_is_better` = overlay should
|
||||
* increase the metric (e.g. fanout, files touched for literal scope).
|
||||
* `lower_is_better` = overlay should decrease it (e.g. Bash count, turn count).
|
||||
* Used only for cosmetic logging in the test output; `pass` is the actual gate.
|
||||
*/
|
||||
direction?: 'higher_is_better' | 'lower_is_better';
|
||||
/** Compute the per-trial metric from the typed SDK result. */
|
||||
metric: (r: AgentSdkResult) => number;
|
||||
/** Acceptance predicate across all arms' per-trial metrics. */
|
||||
pass: (arms: { overlay: number[]; off: number[] }) => boolean;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Validation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function validateFixtures(fixtures: OverlayFixture[]): void {
|
||||
const ids = new Set<string>();
|
||||
for (const f of fixtures) {
|
||||
if (!f.id || !/^[a-z0-9-]+$/.test(f.id)) {
|
||||
throw new Error(
|
||||
`fixture id must be non-empty, lowercase/digits/dash only: ${JSON.stringify(f.id)}`,
|
||||
);
|
||||
}
|
||||
if (ids.has(f.id)) {
|
||||
throw new Error(`duplicate fixture id: ${f.id}`);
|
||||
}
|
||||
ids.add(f.id);
|
||||
|
||||
if (!Number.isInteger(f.trials) || f.trials < 3) {
|
||||
throw new Error(`${f.id}: trials must be an integer >= 3 (got ${f.trials})`);
|
||||
}
|
||||
if (
|
||||
f.concurrency !== undefined &&
|
||||
(!Number.isInteger(f.concurrency) || f.concurrency < 1)
|
||||
) {
|
||||
throw new Error(
|
||||
`${f.id}: concurrency must be an integer >= 1 (got ${f.concurrency})`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!f.model) throw new Error(`${f.id}: model must be non-empty`);
|
||||
if (!f.userPrompt) throw new Error(`${f.id}: userPrompt must be non-empty`);
|
||||
|
||||
if (path.isAbsolute(f.overlayPath) || f.overlayPath.includes('..')) {
|
||||
throw new Error(
|
||||
`${f.id}: overlayPath must be relative and must not contain '..' (got ${f.overlayPath})`,
|
||||
);
|
||||
}
|
||||
const fullPath = path.resolve(REPO_ROOT, f.overlayPath);
|
||||
if (!fs.existsSync(fullPath)) {
|
||||
throw new Error(`${f.id}: overlay file not found at ${f.overlayPath}`);
|
||||
}
|
||||
|
||||
for (const fn of ['setupWorkspace', 'metric', 'pass'] as const) {
|
||||
if (typeof f[fn] !== 'function') {
|
||||
throw new Error(`${f.id}: ${fn} must be a function`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metric + predicate helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function mean(xs: number[]): number {
|
||||
if (xs.length === 0) return 0;
|
||||
return xs.reduce((a, b) => a + b, 0) / xs.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard fanout predicate: overlay mean beats off mean by at least 0.5
|
||||
* parallel tool_use blocks in first turn, AND at least 3 of the overlay
|
||||
* trials emit >= 2 parallel tool_use blocks.
|
||||
*
|
||||
* The combined rule catches both "overlay nudges every trial slightly"
|
||||
* (mean) and "overlay sometimes triggers real fanout" (floor). A single
|
||||
* 0.5 lift with every trial still emitting 1 call would be suspicious;
|
||||
* this predicate rejects it.
|
||||
*/
|
||||
export function fanoutPass(arms: { overlay: number[]; off: number[] }): boolean {
|
||||
const lift = mean(arms.overlay) - mean(arms.off);
|
||||
const floorHits = arms.overlay.filter((n) => n >= 2).length;
|
||||
return lift >= 0.5 && floorHits >= 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic "lower is better" pass predicate: overlay mean should drop the
|
||||
* metric by at least 20% vs baseline. Used for nudges like "effort-match"
|
||||
* (fewer turns) and "dedicated tools vs Bash" (fewer Bash calls).
|
||||
*/
|
||||
export function lowerIsBetter20Pct(arms: { overlay: number[]; off: number[] }): boolean {
|
||||
const meanOff = mean(arms.off);
|
||||
if (meanOff === 0) return mean(arms.overlay) <= meanOff;
|
||||
return mean(arms.overlay) <= meanOff * 0.8;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic "higher is better" pass predicate: overlay mean should lift the
|
||||
* metric by at least 20% vs baseline. Used for nudges like "literal
|
||||
* interpretation" (more files touched when scope is ambiguous).
|
||||
*/
|
||||
export function higherIsBetter20Pct(arms: { overlay: number[]; off: number[] }): boolean {
|
||||
const meanOff = mean(arms.off);
|
||||
const meanOn = mean(arms.overlay);
|
||||
if (meanOff === 0) return meanOn > 0;
|
||||
return meanOn >= meanOff * 1.2;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metrics
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Count the total number of Bash tool_use blocks across ALL assistant turns.
|
||||
* Signal for "dedicated tools over Bash" nudge in claude.md.
|
||||
*/
|
||||
export function bashToolCallCount(r: AgentSdkResult): number {
|
||||
return r.toolCalls.filter((c) => c.tool === 'Bash').length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Total turns the session used to complete. Signal for "effort-match the
|
||||
* step" nudge in opus-4-7.md — trivial prompts should complete quickly.
|
||||
*/
|
||||
export function turnsToCompletion(r: AgentSdkResult): number {
|
||||
return r.turnsUsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count of unique files the model edited or wrote. Signal for "literal
|
||||
* interpretation" nudge in opus-4-7.md — "fix the tests" with multiple
|
||||
* failures should touch all of them.
|
||||
*/
|
||||
export function uniqueFilesEdited(r: AgentSdkResult): number {
|
||||
const touched = new Set<string>();
|
||||
for (const call of r.toolCalls) {
|
||||
if (call.tool === 'Edit' || call.tool === 'Write' || call.tool === 'MultiEdit') {
|
||||
const input = call.input as { file_path?: string } | null;
|
||||
if (input?.file_path) touched.add(input.file_path);
|
||||
}
|
||||
}
|
||||
return touched.size;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Fixtures
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const OVERLAY_FIXTURES: OverlayFixture[] = [
|
||||
{
|
||||
id: 'opus-4-7-fanout-toy',
|
||||
overlayPath: 'model-overlays/opus-4-7.md',
|
||||
model: 'claude-opus-4-7',
|
||||
trials: 10,
|
||||
concurrency: 3,
|
||||
setupWorkspace: (dir) => {
|
||||
fs.writeFileSync(path.join(dir, 'alpha.txt'), 'Alpha file: used in module A.\n');
|
||||
fs.writeFileSync(path.join(dir, 'beta.txt'), 'Beta file: used in module B.\n');
|
||||
fs.writeFileSync(path.join(dir, 'gamma.txt'), 'Gamma file: used in module C.\n');
|
||||
},
|
||||
userPrompt:
|
||||
'Read alpha.txt, beta.txt, and gamma.txt and summarize each in one line.',
|
||||
metric: (r) => firstTurnParallelism(r.assistantTurns[0]),
|
||||
pass: fanoutPass,
|
||||
},
|
||||
{
|
||||
id: 'opus-4-7-fanout-realistic',
|
||||
overlayPath: 'model-overlays/opus-4-7.md',
|
||||
model: 'claude-opus-4-7',
|
||||
trials: 10,
|
||||
concurrency: 3,
|
||||
setupWorkspace: (dir) => {
|
||||
fs.writeFileSync(
|
||||
path.join(dir, 'app.ts'),
|
||||
"import { config } from './config';\nimport { util } from './src/util';\n\nexport function main() { return config.name + ':' + util(); }\n",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(dir, 'config.ts'),
|
||||
"export const config = { name: 'demo', version: 1 };\n",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(dir, 'README.md'),
|
||||
'# demo project\n\nA small demo. Entry: `app.ts`. Config: `config.ts`.\n',
|
||||
);
|
||||
fs.mkdirSync(path.join(dir, 'src'), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(dir, 'src', 'util.ts'),
|
||||
"export function util() { return 'util-result'; }\n",
|
||||
);
|
||||
},
|
||||
userPrompt:
|
||||
'Audit this project: read app.ts, config.ts, and README.md, and glob for ' +
|
||||
'every .ts file under src/. Summarize what you find in 3 bullet points.',
|
||||
metric: (r) => firstTurnParallelism(r.assistantTurns[0]),
|
||||
pass: fanoutPass,
|
||||
},
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// claude.md / "Dedicated tools over Bash"
|
||||
// -------------------------------------------------------------------------
|
||||
{
|
||||
id: 'claude-dedicated-tools-vs-bash',
|
||||
overlayPath: 'model-overlays/claude.md',
|
||||
model: 'claude-opus-4-7',
|
||||
trials: 10,
|
||||
concurrency: 3,
|
||||
direction: 'lower_is_better',
|
||||
// 5 files + summary = needs more than default 5 turns. SDK throws
|
||||
// instead of returning a result when it hits the cap.
|
||||
maxTurns: 15,
|
||||
setupWorkspace: (dir) => {
|
||||
fs.mkdirSync(path.join(dir, 'src'), { recursive: true });
|
||||
fs.writeFileSync(path.join(dir, 'src', 'index.ts'), "export const x = 1;\n");
|
||||
fs.writeFileSync(path.join(dir, 'src', 'util.ts'), "export function util() { return 42; }\n");
|
||||
fs.writeFileSync(path.join(dir, 'src', 'types.ts'), "export type Foo = { a: number };\n");
|
||||
fs.writeFileSync(path.join(dir, 'src', 'config.ts'), "export const c = { n: 'demo' };\n");
|
||||
fs.writeFileSync(path.join(dir, 'src', 'api.ts'), "export async function fetchFoo() { return null; }\n");
|
||||
},
|
||||
userPrompt:
|
||||
"List every TypeScript file under src/ and tell me what each exports. " +
|
||||
"You may use any tools available.",
|
||||
// Metric: total Bash tool_use count across the whole session.
|
||||
// The overlay says "prefer Read/Glob/Grep over cat/find/grep shell."
|
||||
// A model following that should emit Glob + Read, not Bash ls/find/cat.
|
||||
metric: bashToolCallCount,
|
||||
pass: lowerIsBetter20Pct,
|
||||
},
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// opus-4-7.md / "Effort-match the step"
|
||||
// -------------------------------------------------------------------------
|
||||
{
|
||||
id: 'opus-4-7-effort-match-trivial',
|
||||
overlayPath: 'model-overlays/opus-4-7.md',
|
||||
model: 'claude-opus-4-7',
|
||||
trials: 10,
|
||||
concurrency: 3,
|
||||
direction: 'lower_is_better',
|
||||
maxTurns: 8,
|
||||
setupWorkspace: (dir) => {
|
||||
fs.writeFileSync(
|
||||
path.join(dir, 'config.json'),
|
||||
'{"name": "demo", "version": "1.0.0"}\n',
|
||||
);
|
||||
},
|
||||
userPrompt: "What's the version in config.json?",
|
||||
// Metric: turns used to reach a result.
|
||||
// The overlay says "simple lookups don't need deep reasoning."
|
||||
// A trivial prompt like this should complete in 2 turns
|
||||
// (Read, respond). Extended deliberation bumps this up.
|
||||
metric: turnsToCompletion,
|
||||
pass: lowerIsBetter20Pct,
|
||||
},
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// opus-4-7.md / "Literal interpretation awareness"
|
||||
// -------------------------------------------------------------------------
|
||||
{
|
||||
id: 'opus-4-7-literal-interpretation',
|
||||
overlayPath: 'model-overlays/opus-4-7.md',
|
||||
model: 'claude-opus-4-7',
|
||||
trials: 10,
|
||||
concurrency: 3,
|
||||
direction: 'higher_is_better',
|
||||
// Allow Edit and Write so the model can fix the tests.
|
||||
allowedTools: ['Read', 'Glob', 'Grep', 'Bash', 'Edit', 'Write'],
|
||||
maxTurns: 15,
|
||||
setupWorkspace: (dir) => {
|
||||
// Three failing test files. The "obvious" one is named first; the
|
||||
// other two require the model to enumerate to notice them.
|
||||
fs.writeFileSync(
|
||||
path.join(dir, 'auth.test.ts'),
|
||||
"import { test, expect } from 'bun:test';\n" +
|
||||
"test('auth returns true', () => { expect(1).toBe(2); });\n",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(dir, 'billing.test.ts'),
|
||||
"import { test, expect } from 'bun:test';\n" +
|
||||
"test('billing computes total', () => { expect('ten').toBe('10'); });\n",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(dir, 'notifications.test.ts'),
|
||||
"import { test, expect } from 'bun:test';\n" +
|
||||
"test('notifications fire on save', () => { expect([1,2]).toEqual([1,2,3]); });\n",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(dir, 'README.md'),
|
||||
"# demo\n\nSmall project with three failing test files.\n",
|
||||
);
|
||||
},
|
||||
userPrompt: "Fix the failing tests.",
|
||||
// Metric: number of unique test files the model edited/wrote.
|
||||
// The overlay says "fix ALL failing tests, not just the obvious one."
|
||||
// Overlay-ON should touch all 3 test files. Overlay-OFF might stop
|
||||
// at the first one after making it pass.
|
||||
metric: uniqueFilesEdited,
|
||||
pass: higherIsBetter20Pct,
|
||||
},
|
||||
|
||||
// =========================================================================
|
||||
// Sonnet 4.6 variants of the Opus-4.7 fixtures.
|
||||
//
|
||||
// Rationale: /claude.md + /opus-4-7.md overlays measured as no-op or
|
||||
// counterproductive on Opus 4.7. Before deleting the whole overlay stack,
|
||||
// check whether weaker Claude models (Sonnet, Haiku) benefit from the same
|
||||
// nudges. Same overlays, same prompts, same metrics, different model ID.
|
||||
// Sonnet is ~4x cheaper than Opus so these 5 add ~$3 to a run.
|
||||
// =========================================================================
|
||||
|
||||
{
|
||||
id: 'opus-4-7-fanout-toy-sonnet',
|
||||
overlayPath: 'model-overlays/opus-4-7.md',
|
||||
model: 'claude-sonnet-4-6',
|
||||
trials: 10,
|
||||
concurrency: 3,
|
||||
setupWorkspace: (dir) => {
|
||||
fs.writeFileSync(path.join(dir, 'alpha.txt'), 'Alpha file: used in module A.\n');
|
||||
fs.writeFileSync(path.join(dir, 'beta.txt'), 'Beta file: used in module B.\n');
|
||||
fs.writeFileSync(path.join(dir, 'gamma.txt'), 'Gamma file: used in module C.\n');
|
||||
},
|
||||
userPrompt:
|
||||
'Read alpha.txt, beta.txt, and gamma.txt and summarize each in one line.',
|
||||
metric: (r) => firstTurnParallelism(r.assistantTurns[0]),
|
||||
pass: fanoutPass,
|
||||
},
|
||||
|
||||
{
|
||||
id: 'opus-4-7-fanout-realistic-sonnet',
|
||||
overlayPath: 'model-overlays/opus-4-7.md',
|
||||
model: 'claude-sonnet-4-6',
|
||||
trials: 10,
|
||||
concurrency: 3,
|
||||
setupWorkspace: (dir) => {
|
||||
fs.writeFileSync(
|
||||
path.join(dir, 'app.ts'),
|
||||
"import { config } from './config';\nimport { util } from './src/util';\n\nexport function main() { return config.name + ':' + util(); }\n",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(dir, 'config.ts'),
|
||||
"export const config = { name: 'demo', version: 1 };\n",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(dir, 'README.md'),
|
||||
'# demo project\n\nA small demo. Entry: `app.ts`. Config: `config.ts`.\n',
|
||||
);
|
||||
fs.mkdirSync(path.join(dir, 'src'), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(dir, 'src', 'util.ts'),
|
||||
"export function util() { return 'util-result'; }\n",
|
||||
);
|
||||
},
|
||||
userPrompt:
|
||||
'Audit this project: read app.ts, config.ts, and README.md, and glob for ' +
|
||||
'every .ts file under src/. Summarize what you find in 3 bullet points.',
|
||||
metric: (r) => firstTurnParallelism(r.assistantTurns[0]),
|
||||
pass: fanoutPass,
|
||||
},
|
||||
|
||||
{
|
||||
id: 'claude-dedicated-tools-vs-bash-sonnet',
|
||||
overlayPath: 'model-overlays/claude.md',
|
||||
model: 'claude-sonnet-4-6',
|
||||
trials: 10,
|
||||
concurrency: 3,
|
||||
direction: 'lower_is_better',
|
||||
maxTurns: 15,
|
||||
setupWorkspace: (dir) => {
|
||||
fs.mkdirSync(path.join(dir, 'src'), { recursive: true });
|
||||
fs.writeFileSync(path.join(dir, 'src', 'index.ts'), "export const x = 1;\n");
|
||||
fs.writeFileSync(path.join(dir, 'src', 'util.ts'), "export function util() { return 42; }\n");
|
||||
fs.writeFileSync(path.join(dir, 'src', 'types.ts'), "export type Foo = { a: number };\n");
|
||||
fs.writeFileSync(path.join(dir, 'src', 'config.ts'), "export const c = { n: 'demo' };\n");
|
||||
fs.writeFileSync(path.join(dir, 'src', 'api.ts'), "export async function fetchFoo() { return null; }\n");
|
||||
},
|
||||
userPrompt:
|
||||
"List every TypeScript file under src/ and tell me what each exports. " +
|
||||
"You may use any tools available.",
|
||||
metric: bashToolCallCount,
|
||||
pass: lowerIsBetter20Pct,
|
||||
},
|
||||
|
||||
{
|
||||
id: 'opus-4-7-effort-match-trivial-sonnet',
|
||||
overlayPath: 'model-overlays/opus-4-7.md',
|
||||
model: 'claude-sonnet-4-6',
|
||||
trials: 10,
|
||||
concurrency: 3,
|
||||
direction: 'lower_is_better',
|
||||
maxTurns: 8,
|
||||
setupWorkspace: (dir) => {
|
||||
fs.writeFileSync(
|
||||
path.join(dir, 'config.json'),
|
||||
'{"name": "demo", "version": "1.0.0"}\n',
|
||||
);
|
||||
},
|
||||
userPrompt: "What's the version in config.json?",
|
||||
metric: turnsToCompletion,
|
||||
pass: lowerIsBetter20Pct,
|
||||
},
|
||||
|
||||
{
|
||||
id: 'opus-4-7-literal-interpretation-sonnet',
|
||||
overlayPath: 'model-overlays/opus-4-7.md',
|
||||
model: 'claude-sonnet-4-6',
|
||||
trials: 10,
|
||||
concurrency: 3,
|
||||
direction: 'higher_is_better',
|
||||
allowedTools: ['Read', 'Glob', 'Grep', 'Bash', 'Edit', 'Write'],
|
||||
maxTurns: 15,
|
||||
setupWorkspace: (dir) => {
|
||||
fs.writeFileSync(
|
||||
path.join(dir, 'auth.test.ts'),
|
||||
"import { test, expect } from 'bun:test';\n" +
|
||||
"test('auth returns true', () => { expect(1).toBe(2); });\n",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(dir, 'billing.test.ts'),
|
||||
"import { test, expect } from 'bun:test';\n" +
|
||||
"test('billing computes total', () => { expect('ten').toBe('10'); });\n",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(dir, 'notifications.test.ts'),
|
||||
"import { test, expect } from 'bun:test';\n" +
|
||||
"test('notifications fire on save', () => { expect([1,2]).toEqual([1,2,3]); });\n",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(dir, 'README.md'),
|
||||
"# demo\n\nSmall project with three failing test files.\n",
|
||||
);
|
||||
},
|
||||
userPrompt: "Fix the failing tests.",
|
||||
metric: uniqueFilesEdited,
|
||||
pass: higherIsBetter20Pct,
|
||||
},
|
||||
];
|
||||
|
||||
// Validate at module load so a broken fixture fails fast at test startup,
|
||||
// not mid-run after burning API dollars.
|
||||
validateFixtures(OVERLAY_FIXTURES);
|
||||
+623
@@ -0,0 +1,623 @@
|
||||
{
|
||||
"tag": "v1.44.1",
|
||||
"capturedAt": "2026-05-26T03:29:32.568Z",
|
||||
"capturedFromCommit": "74bc8054",
|
||||
"capturedFromBranch": "garrytan/slim-skill-tokens",
|
||||
"totalSkills": 51,
|
||||
"totalCorpusBytes": 2915151,
|
||||
"estTotalCatalogTokens": 9319,
|
||||
"topHeaviest": [
|
||||
{
|
||||
"skill": "ship",
|
||||
"skillMdBytes": 163553,
|
||||
"skillMdLines": 3094,
|
||||
"estTokens": 40888,
|
||||
"tmplBytes": 48869,
|
||||
"descriptionLen": 557,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
{
|
||||
"skill": "plan-ceo-review",
|
||||
"skillMdBytes": 130891,
|
||||
"skillMdLines": 2224,
|
||||
"estTokens": 32723,
|
||||
"tmplBytes": 63393,
|
||||
"descriptionLen": 1326,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
{
|
||||
"skill": "office-hours",
|
||||
"skillMdBytes": 111088,
|
||||
"skillMdLines": 2090,
|
||||
"estTokens": 27772,
|
||||
"tmplBytes": 55466,
|
||||
"descriptionLen": 1579,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
{
|
||||
"skill": "plan-design-review",
|
||||
"skillMdBytes": 105592,
|
||||
"skillMdLines": 1944,
|
||||
"estTokens": 26398,
|
||||
"tmplBytes": 28624,
|
||||
"descriptionLen": 568,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
{
|
||||
"skill": "plan-devex-review",
|
||||
"skillMdBytes": 104571,
|
||||
"skillMdLines": 2145,
|
||||
"estTokens": 26143,
|
||||
"tmplBytes": 35680,
|
||||
"descriptionLen": 886,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
{
|
||||
"skill": "plan-eng-review",
|
||||
"skillMdBytes": 101409,
|
||||
"skillMdLines": 1788,
|
||||
"estTokens": 25352,
|
||||
"tmplBytes": 26234,
|
||||
"descriptionLen": 743,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
{
|
||||
"skill": "design-review",
|
||||
"skillMdBytes": 94055,
|
||||
"skillMdLines": 1960,
|
||||
"estTokens": 23514,
|
||||
"tmplBytes": 11674,
|
||||
"descriptionLen": 709,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
{
|
||||
"skill": "review",
|
||||
"skillMdBytes": 92443,
|
||||
"skillMdLines": 1789,
|
||||
"estTokens": 23111,
|
||||
"tmplBytes": 14099,
|
||||
"descriptionLen": 512,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
{
|
||||
"skill": "land-and-deploy",
|
||||
"skillMdBytes": 90281,
|
||||
"skillMdLines": 1883,
|
||||
"estTokens": 22570,
|
||||
"tmplBytes": 48624,
|
||||
"descriptionLen": 378,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
{
|
||||
"skill": "autoplan",
|
||||
"skillMdBytes": 89274,
|
||||
"skillMdLines": 1811,
|
||||
"estTokens": 22319,
|
||||
"tmplBytes": 45271,
|
||||
"descriptionLen": 857,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
}
|
||||
],
|
||||
"skills": {
|
||||
"autoplan": {
|
||||
"skill": "autoplan",
|
||||
"skillMdBytes": 89274,
|
||||
"skillMdLines": 1811,
|
||||
"estTokens": 22319,
|
||||
"tmplBytes": 45271,
|
||||
"descriptionLen": 857,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
"benchmark": {
|
||||
"skill": "benchmark",
|
||||
"skillMdBytes": 32537,
|
||||
"skillMdLines": 728,
|
||||
"estTokens": 8134,
|
||||
"tmplBytes": 9378,
|
||||
"descriptionLen": 549,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"benchmark-models": {
|
||||
"skill": "benchmark-models",
|
||||
"skillMdBytes": 28606,
|
||||
"skillMdLines": 603,
|
||||
"estTokens": 7152,
|
||||
"tmplBytes": 6631,
|
||||
"descriptionLen": 740,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"browse": {
|
||||
"skill": "browse",
|
||||
"skillMdBytes": 47290,
|
||||
"skillMdLines": 911,
|
||||
"estTokens": 11823,
|
||||
"tmplBytes": 10805,
|
||||
"descriptionLen": 612,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"canary": {
|
||||
"skill": "canary",
|
||||
"skillMdBytes": 45502,
|
||||
"skillMdLines": 1017,
|
||||
"estTokens": 11376,
|
||||
"tmplBytes": 8033,
|
||||
"descriptionLen": 477,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"careful": {
|
||||
"skill": "careful",
|
||||
"skillMdBytes": 2531,
|
||||
"skillMdLines": 64,
|
||||
"estTokens": 633,
|
||||
"tmplBytes": 2435,
|
||||
"descriptionLen": 625,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"codex": {
|
||||
"skill": "codex",
|
||||
"skillMdBytes": 78018,
|
||||
"skillMdLines": 1545,
|
||||
"estTokens": 19505,
|
||||
"tmplBytes": 34143,
|
||||
"descriptionLen": 626,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"context-restore": {
|
||||
"skill": "context-restore",
|
||||
"skillMdBytes": 39894,
|
||||
"skillMdLines": 875,
|
||||
"estTokens": 9974,
|
||||
"tmplBytes": 5255,
|
||||
"descriptionLen": 636,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"context-save": {
|
||||
"skill": "context-save",
|
||||
"skillMdBytes": 44091,
|
||||
"skillMdLines": 994,
|
||||
"estTokens": 11023,
|
||||
"tmplBytes": 9293,
|
||||
"descriptionLen": 562,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"cso": {
|
||||
"skill": "cso",
|
||||
"skillMdBytes": 75797,
|
||||
"skillMdLines": 1477,
|
||||
"estTokens": 18949,
|
||||
"tmplBytes": 35158,
|
||||
"descriptionLen": 774,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"design-consultation": {
|
||||
"skill": "design-consultation",
|
||||
"skillMdBytes": 76963,
|
||||
"skillMdLines": 1578,
|
||||
"estTokens": 19241,
|
||||
"tmplBytes": 25899,
|
||||
"descriptionLen": 1201,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"design-html": {
|
||||
"skill": "design-html",
|
||||
"skillMdBytes": 64951,
|
||||
"skillMdLines": 1476,
|
||||
"estTokens": 16238,
|
||||
"tmplBytes": 22567,
|
||||
"descriptionLen": 870,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"design-review": {
|
||||
"skill": "design-review",
|
||||
"skillMdBytes": 94055,
|
||||
"skillMdLines": 1960,
|
||||
"estTokens": 23514,
|
||||
"tmplBytes": 11674,
|
||||
"descriptionLen": 709,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"design-shotgun": {
|
||||
"skill": "design-shotgun",
|
||||
"skillMdBytes": 60571,
|
||||
"skillMdLines": 1327,
|
||||
"estTokens": 15143,
|
||||
"tmplBytes": 13331,
|
||||
"descriptionLen": 1057,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"devex-review": {
|
||||
"skill": "devex-review",
|
||||
"skillMdBytes": 62815,
|
||||
"skillMdLines": 1259,
|
||||
"estTokens": 15704,
|
||||
"tmplBytes": 7984,
|
||||
"descriptionLen": 827,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"document-generate": {
|
||||
"skill": "document-generate",
|
||||
"skillMdBytes": 51386,
|
||||
"skillMdLines": 1204,
|
||||
"estTokens": 12847,
|
||||
"tmplBytes": 15093,
|
||||
"descriptionLen": 671,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"document-release": {
|
||||
"skill": "document-release",
|
||||
"skillMdBytes": 56652,
|
||||
"skillMdLines": 1262,
|
||||
"estTokens": 14163,
|
||||
"tmplBytes": 20362,
|
||||
"descriptionLen": 707,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"freeze": {
|
||||
"skill": "freeze",
|
||||
"skillMdBytes": 3134,
|
||||
"skillMdLines": 88,
|
||||
"estTokens": 784,
|
||||
"tmplBytes": 3038,
|
||||
"descriptionLen": 761,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"gstack-upgrade": {
|
||||
"skill": "gstack-upgrade",
|
||||
"skillMdBytes": 10794,
|
||||
"skillMdLines": 280,
|
||||
"estTokens": 2699,
|
||||
"tmplBytes": 10667,
|
||||
"descriptionLen": 439,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"guard": {
|
||||
"skill": "guard",
|
||||
"skillMdBytes": 3277,
|
||||
"skillMdLines": 88,
|
||||
"estTokens": 819,
|
||||
"tmplBytes": 3181,
|
||||
"descriptionLen": 968,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"health": {
|
||||
"skill": "health",
|
||||
"skillMdBytes": 46313,
|
||||
"skillMdLines": 1041,
|
||||
"estTokens": 11578,
|
||||
"tmplBytes": 11617,
|
||||
"descriptionLen": 463,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"investigate": {
|
||||
"skill": "investigate",
|
||||
"skillMdBytes": 48810,
|
||||
"skillMdLines": 1039,
|
||||
"estTokens": 12203,
|
||||
"tmplBytes": 11561,
|
||||
"descriptionLen": 1811,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"ios-clean": {
|
||||
"skill": "ios-clean",
|
||||
"skillMdBytes": 39447,
|
||||
"skillMdLines": 840,
|
||||
"estTokens": 9862,
|
||||
"tmplBytes": 3851,
|
||||
"descriptionLen": 761,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"ios-design-review": {
|
||||
"skill": "ios-design-review",
|
||||
"skillMdBytes": 40037,
|
||||
"skillMdLines": 841,
|
||||
"estTokens": 10009,
|
||||
"tmplBytes": 4417,
|
||||
"descriptionLen": 836,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"ios-fix": {
|
||||
"skill": "ios-fix",
|
||||
"skillMdBytes": 39164,
|
||||
"skillMdLines": 837,
|
||||
"estTokens": 9791,
|
||||
"tmplBytes": 3574,
|
||||
"descriptionLen": 767,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"ios-qa": {
|
||||
"skill": "ios-qa",
|
||||
"skillMdBytes": 45677,
|
||||
"skillMdLines": 957,
|
||||
"estTokens": 11419,
|
||||
"tmplBytes": 10090,
|
||||
"descriptionLen": 875,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"ios-sync": {
|
||||
"skill": "ios-sync",
|
||||
"skillMdBytes": 39137,
|
||||
"skillMdLines": 831,
|
||||
"estTokens": 9784,
|
||||
"tmplBytes": 3544,
|
||||
"descriptionLen": 727,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"land-and-deploy": {
|
||||
"skill": "land-and-deploy",
|
||||
"skillMdBytes": 90281,
|
||||
"skillMdLines": 1883,
|
||||
"estTokens": 22570,
|
||||
"tmplBytes": 48624,
|
||||
"descriptionLen": 378,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"landing-report": {
|
||||
"skill": "landing-report",
|
||||
"skillMdBytes": 42382,
|
||||
"skillMdLines": 901,
|
||||
"estTokens": 10596,
|
||||
"tmplBytes": 6806,
|
||||
"descriptionLen": 512,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"learn": {
|
||||
"skill": "learn",
|
||||
"skillMdBytes": 40119,
|
||||
"skillMdLines": 918,
|
||||
"estTokens": 10030,
|
||||
"tmplBytes": 5594,
|
||||
"descriptionLen": 460,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"make-pdf": {
|
||||
"skill": "make-pdf",
|
||||
"skillMdBytes": 28721,
|
||||
"skillMdLines": 644,
|
||||
"estTokens": 7180,
|
||||
"tmplBytes": 5106,
|
||||
"descriptionLen": 698,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"office-hours": {
|
||||
"skill": "office-hours",
|
||||
"skillMdBytes": 111088,
|
||||
"skillMdLines": 2090,
|
||||
"estTokens": 27772,
|
||||
"tmplBytes": 55466,
|
||||
"descriptionLen": 1579,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"open-gstack-browser": {
|
||||
"skill": "open-gstack-browser",
|
||||
"skillMdBytes": 44529,
|
||||
"skillMdLines": 981,
|
||||
"estTokens": 11132,
|
||||
"tmplBytes": 7702,
|
||||
"descriptionLen": 586,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"pair-agent": {
|
||||
"skill": "pair-agent",
|
||||
"skillMdBytes": 45339,
|
||||
"skillMdLines": 1036,
|
||||
"estTokens": 11335,
|
||||
"tmplBytes": 8548,
|
||||
"descriptionLen": 709,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"plan-ceo-review": {
|
||||
"skill": "plan-ceo-review",
|
||||
"skillMdBytes": 130891,
|
||||
"skillMdLines": 2224,
|
||||
"estTokens": 32723,
|
||||
"tmplBytes": 63393,
|
||||
"descriptionLen": 1326,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
"plan-design-review": {
|
||||
"skill": "plan-design-review",
|
||||
"skillMdBytes": 105592,
|
||||
"skillMdLines": 1944,
|
||||
"estTokens": 26398,
|
||||
"tmplBytes": 28624,
|
||||
"descriptionLen": 568,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
"plan-devex-review": {
|
||||
"skill": "plan-devex-review",
|
||||
"skillMdBytes": 104571,
|
||||
"skillMdLines": 2145,
|
||||
"estTokens": 26143,
|
||||
"tmplBytes": 35680,
|
||||
"descriptionLen": 886,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
"plan-eng-review": {
|
||||
"skill": "plan-eng-review",
|
||||
"skillMdBytes": 101409,
|
||||
"skillMdLines": 1788,
|
||||
"estTokens": 25352,
|
||||
"tmplBytes": 26234,
|
||||
"descriptionLen": 743,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
"plan-tune": {
|
||||
"skill": "plan-tune",
|
||||
"skillMdBytes": 50123,
|
||||
"skillMdLines": 1105,
|
||||
"estTokens": 12531,
|
||||
"tmplBytes": 15586,
|
||||
"descriptionLen": 997,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"qa": {
|
||||
"skill": "qa",
|
||||
"skillMdBytes": 72267,
|
||||
"skillMdLines": 1648,
|
||||
"estTokens": 18067,
|
||||
"tmplBytes": 12701,
|
||||
"descriptionLen": 814,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"qa-only": {
|
||||
"skill": "qa-only",
|
||||
"skillMdBytes": 54819,
|
||||
"skillMdLines": 1220,
|
||||
"estTokens": 13705,
|
||||
"tmplBytes": 3851,
|
||||
"descriptionLen": 605,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"retro": {
|
||||
"skill": "retro",
|
||||
"skillMdBytes": 81286,
|
||||
"skillMdLines": 1777,
|
||||
"estTokens": 20322,
|
||||
"tmplBytes": 42427,
|
||||
"descriptionLen": 979,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"review": {
|
||||
"skill": "review",
|
||||
"skillMdBytes": 92443,
|
||||
"skillMdLines": 1789,
|
||||
"estTokens": 23111,
|
||||
"tmplBytes": 14099,
|
||||
"descriptionLen": 512,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"scrape": {
|
||||
"skill": "scrape",
|
||||
"skillMdBytes": 42040,
|
||||
"skillMdLines": 914,
|
||||
"estTokens": 10510,
|
||||
"tmplBytes": 5220,
|
||||
"descriptionLen": 519,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"setup-browser-cookies": {
|
||||
"skill": "setup-browser-cookies",
|
||||
"skillMdBytes": 25886,
|
||||
"skillMdLines": 577,
|
||||
"estTokens": 6472,
|
||||
"tmplBytes": 2724,
|
||||
"descriptionLen": 433,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"setup-deploy": {
|
||||
"skill": "setup-deploy",
|
||||
"skillMdBytes": 42326,
|
||||
"skillMdLines": 946,
|
||||
"estTokens": 10582,
|
||||
"tmplBytes": 7780,
|
||||
"descriptionLen": 564,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"setup-gbrain": {
|
||||
"skill": "setup-gbrain",
|
||||
"skillMdBytes": 76791,
|
||||
"skillMdLines": 1733,
|
||||
"estTokens": 19198,
|
||||
"tmplBytes": 42245,
|
||||
"descriptionLen": 512,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"ship": {
|
||||
"skill": "ship",
|
||||
"skillMdBytes": 163553,
|
||||
"skillMdLines": 3094,
|
||||
"estTokens": 40888,
|
||||
"tmplBytes": 48869,
|
||||
"descriptionLen": 557,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
"skillify": {
|
||||
"skill": "skillify",
|
||||
"skillMdBytes": 51935,
|
||||
"skillMdLines": 1196,
|
||||
"estTokens": 12984,
|
||||
"tmplBytes": 15107,
|
||||
"descriptionLen": 571,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"sync-gbrain": {
|
||||
"skill": "sync-gbrain",
|
||||
"skillMdBytes": 48555,
|
||||
"skillMdLines": 1057,
|
||||
"estTokens": 12139,
|
||||
"tmplBytes": 13996,
|
||||
"descriptionLen": 510,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"unfreeze": {
|
||||
"skill": "unfreeze",
|
||||
"skillMdBytes": 1482,
|
||||
"skillMdLines": 46,
|
||||
"estTokens": 371,
|
||||
"tmplBytes": 1386,
|
||||
"descriptionLen": 350,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
}
|
||||
}
|
||||
}
|
||||
+623
@@ -0,0 +1,623 @@
|
||||
{
|
||||
"tag": "v1.46.0.0",
|
||||
"capturedAt": "2026-05-26T04:17:57.247Z",
|
||||
"capturedFromCommit": "2aff29e9",
|
||||
"capturedFromBranch": "garrytan/slim-skill-tokens",
|
||||
"totalSkills": 51,
|
||||
"totalCorpusBytes": 2882468,
|
||||
"estTotalCatalogTokens": 4045,
|
||||
"topHeaviest": [
|
||||
{
|
||||
"skill": "ship",
|
||||
"skillMdBytes": 162702,
|
||||
"skillMdLines": 3020,
|
||||
"estTokens": 40676,
|
||||
"tmplBytes": 48869,
|
||||
"descriptionLen": 291,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
{
|
||||
"skill": "plan-ceo-review",
|
||||
"skillMdBytes": 130034,
|
||||
"skillMdLines": 2151,
|
||||
"estTokens": 32509,
|
||||
"tmplBytes": 63393,
|
||||
"descriptionLen": 794,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
{
|
||||
"skill": "office-hours",
|
||||
"skillMdBytes": 110388,
|
||||
"skillMdLines": 2020,
|
||||
"estTokens": 27597,
|
||||
"tmplBytes": 55466,
|
||||
"descriptionLen": 860,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
{
|
||||
"skill": "plan-design-review",
|
||||
"skillMdBytes": 105401,
|
||||
"skillMdLines": 1882,
|
||||
"estTokens": 26350,
|
||||
"tmplBytes": 28624,
|
||||
"descriptionLen": 218,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
{
|
||||
"skill": "plan-devex-review",
|
||||
"skillMdBytes": 103713,
|
||||
"skillMdLines": 2073,
|
||||
"estTokens": 25928,
|
||||
"tmplBytes": 35680,
|
||||
"descriptionLen": 250,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
{
|
||||
"skill": "plan-eng-review",
|
||||
"skillMdBytes": 100555,
|
||||
"skillMdLines": 1716,
|
||||
"estTokens": 25139,
|
||||
"tmplBytes": 26234,
|
||||
"descriptionLen": 231,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
{
|
||||
"skill": "design-review",
|
||||
"skillMdBytes": 93200,
|
||||
"skillMdLines": 1886,
|
||||
"estTokens": 23300,
|
||||
"tmplBytes": 11674,
|
||||
"descriptionLen": 304,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
{
|
||||
"skill": "review",
|
||||
"skillMdBytes": 91594,
|
||||
"skillMdLines": 1716,
|
||||
"estTokens": 22899,
|
||||
"tmplBytes": 14099,
|
||||
"descriptionLen": 205,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
{
|
||||
"skill": "land-and-deploy",
|
||||
"skillMdBytes": 89432,
|
||||
"skillMdLines": 1810,
|
||||
"estTokens": 22358,
|
||||
"tmplBytes": 48624,
|
||||
"descriptionLen": 160,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
{
|
||||
"skill": "autoplan",
|
||||
"skillMdBytes": 88416,
|
||||
"skillMdLines": 1738,
|
||||
"estTokens": 22104,
|
||||
"tmplBytes": 45271,
|
||||
"descriptionLen": 366,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
}
|
||||
],
|
||||
"skills": {
|
||||
"autoplan": {
|
||||
"skill": "autoplan",
|
||||
"skillMdBytes": 88416,
|
||||
"skillMdLines": 1738,
|
||||
"estTokens": 22104,
|
||||
"tmplBytes": 45271,
|
||||
"descriptionLen": 366,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
"benchmark": {
|
||||
"skill": "benchmark",
|
||||
"skillMdBytes": 32556,
|
||||
"skillMdLines": 733,
|
||||
"estTokens": 8139,
|
||||
"tmplBytes": 9378,
|
||||
"descriptionLen": 213,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"benchmark-models": {
|
||||
"skill": "benchmark-models",
|
||||
"skillMdBytes": 28623,
|
||||
"skillMdLines": 608,
|
||||
"estTokens": 7156,
|
||||
"tmplBytes": 6631,
|
||||
"descriptionLen": 217,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"browse": {
|
||||
"skill": "browse",
|
||||
"skillMdBytes": 47308,
|
||||
"skillMdLines": 915,
|
||||
"estTokens": 11827,
|
||||
"tmplBytes": 10805,
|
||||
"descriptionLen": 181,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"canary": {
|
||||
"skill": "canary",
|
||||
"skillMdBytes": 44651,
|
||||
"skillMdLines": 944,
|
||||
"estTokens": 11163,
|
||||
"tmplBytes": 8033,
|
||||
"descriptionLen": 180,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"careful": {
|
||||
"skill": "careful",
|
||||
"skillMdBytes": 2551,
|
||||
"skillMdLines": 68,
|
||||
"estTokens": 638,
|
||||
"tmplBytes": 2435,
|
||||
"descriptionLen": 315,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"codex": {
|
||||
"skill": "codex",
|
||||
"skillMdBytes": 77166,
|
||||
"skillMdLines": 1473,
|
||||
"estTokens": 19292,
|
||||
"tmplBytes": 34143,
|
||||
"descriptionLen": 187,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"context-restore": {
|
||||
"skill": "context-restore",
|
||||
"skillMdBytes": 39039,
|
||||
"skillMdLines": 802,
|
||||
"estTokens": 9760,
|
||||
"tmplBytes": 5255,
|
||||
"descriptionLen": 238,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"context-save": {
|
||||
"skill": "context-save",
|
||||
"skillMdBytes": 43236,
|
||||
"skillMdLines": 920,
|
||||
"estTokens": 10809,
|
||||
"tmplBytes": 9293,
|
||||
"descriptionLen": 168,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"cso": {
|
||||
"skill": "cso",
|
||||
"skillMdBytes": 74943,
|
||||
"skillMdLines": 1405,
|
||||
"estTokens": 18736,
|
||||
"tmplBytes": 35158,
|
||||
"descriptionLen": 196,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"design-consultation": {
|
||||
"skill": "design-consultation",
|
||||
"skillMdBytes": 76768,
|
||||
"skillMdLines": 1515,
|
||||
"estTokens": 19192,
|
||||
"tmplBytes": 25899,
|
||||
"descriptionLen": 888,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"design-html": {
|
||||
"skill": "design-html",
|
||||
"skillMdBytes": 64093,
|
||||
"skillMdLines": 1403,
|
||||
"estTokens": 16023,
|
||||
"tmplBytes": 22567,
|
||||
"descriptionLen": 233,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"design-review": {
|
||||
"skill": "design-review",
|
||||
"skillMdBytes": 93200,
|
||||
"skillMdLines": 1886,
|
||||
"estTokens": 23300,
|
||||
"tmplBytes": 11674,
|
||||
"descriptionLen": 304,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"design-shotgun": {
|
||||
"skill": "design-shotgun",
|
||||
"skillMdBytes": 60382,
|
||||
"skillMdLines": 1265,
|
||||
"estTokens": 15096,
|
||||
"tmplBytes": 13331,
|
||||
"descriptionLen": 786,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"devex-review": {
|
||||
"skill": "devex-review",
|
||||
"skillMdBytes": 61959,
|
||||
"skillMdLines": 1187,
|
||||
"estTokens": 15490,
|
||||
"tmplBytes": 7984,
|
||||
"descriptionLen": 201,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"document-generate": {
|
||||
"skill": "document-generate",
|
||||
"skillMdBytes": 50533,
|
||||
"skillMdLines": 1130,
|
||||
"estTokens": 12633,
|
||||
"tmplBytes": 15093,
|
||||
"descriptionLen": 334,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"document-release": {
|
||||
"skill": "document-release",
|
||||
"skillMdBytes": 55797,
|
||||
"skillMdLines": 1189,
|
||||
"estTokens": 13949,
|
||||
"tmplBytes": 20362,
|
||||
"descriptionLen": 192,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"freeze": {
|
||||
"skill": "freeze",
|
||||
"skillMdBytes": 3154,
|
||||
"skillMdLines": 92,
|
||||
"estTokens": 789,
|
||||
"tmplBytes": 3038,
|
||||
"descriptionLen": 503,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"gstack-upgrade": {
|
||||
"skill": "gstack-upgrade",
|
||||
"skillMdBytes": 10817,
|
||||
"skillMdLines": 285,
|
||||
"estTokens": 2704,
|
||||
"tmplBytes": 10667,
|
||||
"descriptionLen": 163,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"guard": {
|
||||
"skill": "guard",
|
||||
"skillMdBytes": 3297,
|
||||
"skillMdLines": 91,
|
||||
"estTokens": 824,
|
||||
"tmplBytes": 3181,
|
||||
"descriptionLen": 686,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"health": {
|
||||
"skill": "health",
|
||||
"skillMdBytes": 45462,
|
||||
"skillMdLines": 968,
|
||||
"estTokens": 11366,
|
||||
"tmplBytes": 11617,
|
||||
"descriptionLen": 184,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"investigate": {
|
||||
"skill": "investigate",
|
||||
"skillMdBytes": 47955,
|
||||
"skillMdLines": 966,
|
||||
"estTokens": 11989,
|
||||
"tmplBytes": 11561,
|
||||
"descriptionLen": 1379,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"ios-clean": {
|
||||
"skill": "ios-clean",
|
||||
"skillMdBytes": 38591,
|
||||
"skillMdLines": 767,
|
||||
"estTokens": 9648,
|
||||
"tmplBytes": 3851,
|
||||
"descriptionLen": 252,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"ios-design-review": {
|
||||
"skill": "ios-design-review",
|
||||
"skillMdBytes": 39177,
|
||||
"skillMdLines": 769,
|
||||
"estTokens": 9794,
|
||||
"tmplBytes": 4417,
|
||||
"descriptionLen": 209,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"ios-fix": {
|
||||
"skill": "ios-fix",
|
||||
"skillMdBytes": 38306,
|
||||
"skillMdLines": 765,
|
||||
"estTokens": 9577,
|
||||
"tmplBytes": 3574,
|
||||
"descriptionLen": 187,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"ios-qa": {
|
||||
"skill": "ios-qa",
|
||||
"skillMdBytes": 44817,
|
||||
"skillMdLines": 885,
|
||||
"estTokens": 11204,
|
||||
"tmplBytes": 10090,
|
||||
"descriptionLen": 223,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"ios-sync": {
|
||||
"skill": "ios-sync",
|
||||
"skillMdBytes": 38283,
|
||||
"skillMdLines": 758,
|
||||
"estTokens": 9571,
|
||||
"tmplBytes": 3544,
|
||||
"descriptionLen": 269,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"land-and-deploy": {
|
||||
"skill": "land-and-deploy",
|
||||
"skillMdBytes": 89432,
|
||||
"skillMdLines": 1810,
|
||||
"estTokens": 22358,
|
||||
"tmplBytes": 48624,
|
||||
"descriptionLen": 160,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"landing-report": {
|
||||
"skill": "landing-report",
|
||||
"skillMdBytes": 41531,
|
||||
"skillMdLines": 828,
|
||||
"estTokens": 10383,
|
||||
"tmplBytes": 6806,
|
||||
"descriptionLen": 195,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"learn": {
|
||||
"skill": "learn",
|
||||
"skillMdBytes": 39268,
|
||||
"skillMdLines": 845,
|
||||
"estTokens": 9817,
|
||||
"tmplBytes": 5594,
|
||||
"descriptionLen": 178,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"make-pdf": {
|
||||
"skill": "make-pdf",
|
||||
"skillMdBytes": 28740,
|
||||
"skillMdLines": 649,
|
||||
"estTokens": 7185,
|
||||
"tmplBytes": 5106,
|
||||
"descriptionLen": 177,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"office-hours": {
|
||||
"skill": "office-hours",
|
||||
"skillMdBytes": 110388,
|
||||
"skillMdLines": 2020,
|
||||
"estTokens": 27597,
|
||||
"tmplBytes": 55466,
|
||||
"descriptionLen": 860,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"open-gstack-browser": {
|
||||
"skill": "open-gstack-browser",
|
||||
"skillMdBytes": 43677,
|
||||
"skillMdLines": 908,
|
||||
"estTokens": 10919,
|
||||
"tmplBytes": 7702,
|
||||
"descriptionLen": 204,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"pair-agent": {
|
||||
"skill": "pair-agent",
|
||||
"skillMdBytes": 44485,
|
||||
"skillMdLines": 964,
|
||||
"estTokens": 11121,
|
||||
"tmplBytes": 8548,
|
||||
"descriptionLen": 167,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"plan-ceo-review": {
|
||||
"skill": "plan-ceo-review",
|
||||
"skillMdBytes": 130034,
|
||||
"skillMdLines": 2151,
|
||||
"estTokens": 32509,
|
||||
"tmplBytes": 63393,
|
||||
"descriptionLen": 794,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
"plan-design-review": {
|
||||
"skill": "plan-design-review",
|
||||
"skillMdBytes": 105401,
|
||||
"skillMdLines": 1882,
|
||||
"estTokens": 26350,
|
||||
"tmplBytes": 28624,
|
||||
"descriptionLen": 218,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
"plan-devex-review": {
|
||||
"skill": "plan-devex-review",
|
||||
"skillMdBytes": 103713,
|
||||
"skillMdLines": 2073,
|
||||
"estTokens": 25928,
|
||||
"tmplBytes": 35680,
|
||||
"descriptionLen": 250,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
"plan-eng-review": {
|
||||
"skill": "plan-eng-review",
|
||||
"skillMdBytes": 100555,
|
||||
"skillMdLines": 1716,
|
||||
"estTokens": 25139,
|
||||
"tmplBytes": 26234,
|
||||
"descriptionLen": 231,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
"plan-tune": {
|
||||
"skill": "plan-tune",
|
||||
"skillMdBytes": 49263,
|
||||
"skillMdLines": 1031,
|
||||
"estTokens": 12316,
|
||||
"tmplBytes": 15586,
|
||||
"descriptionLen": 325,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"qa": {
|
||||
"skill": "qa",
|
||||
"skillMdBytes": 71409,
|
||||
"skillMdLines": 1576,
|
||||
"estTokens": 17852,
|
||||
"tmplBytes": 12701,
|
||||
"descriptionLen": 218,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"qa-only": {
|
||||
"skill": "qa-only",
|
||||
"skillMdBytes": 53967,
|
||||
"skillMdLines": 1148,
|
||||
"estTokens": 13492,
|
||||
"tmplBytes": 3851,
|
||||
"descriptionLen": 165,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"retro": {
|
||||
"skill": "retro",
|
||||
"skillMdBytes": 80435,
|
||||
"skillMdLines": 1704,
|
||||
"estTokens": 20109,
|
||||
"tmplBytes": 42427,
|
||||
"descriptionLen": 648,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"review": {
|
||||
"skill": "review",
|
||||
"skillMdBytes": 91594,
|
||||
"skillMdLines": 1716,
|
||||
"estTokens": 22899,
|
||||
"tmplBytes": 14099,
|
||||
"descriptionLen": 205,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"scrape": {
|
||||
"skill": "scrape",
|
||||
"skillMdBytes": 41187,
|
||||
"skillMdLines": 841,
|
||||
"estTokens": 10297,
|
||||
"tmplBytes": 5220,
|
||||
"descriptionLen": 167,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"setup-browser-cookies": {
|
||||
"skill": "setup-browser-cookies",
|
||||
"skillMdBytes": 25908,
|
||||
"skillMdLines": 580,
|
||||
"estTokens": 6477,
|
||||
"tmplBytes": 2724,
|
||||
"descriptionLen": 222,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"setup-deploy": {
|
||||
"skill": "setup-deploy",
|
||||
"skillMdBytes": 41473,
|
||||
"skillMdLines": 873,
|
||||
"estTokens": 10368,
|
||||
"tmplBytes": 7780,
|
||||
"descriptionLen": 197,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"setup-gbrain": {
|
||||
"skill": "setup-gbrain",
|
||||
"skillMdBytes": 75940,
|
||||
"skillMdLines": 1658,
|
||||
"estTokens": 18985,
|
||||
"tmplBytes": 42245,
|
||||
"descriptionLen": 323,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"ship": {
|
||||
"skill": "ship",
|
||||
"skillMdBytes": 162702,
|
||||
"skillMdLines": 3020,
|
||||
"estTokens": 40676,
|
||||
"tmplBytes": 48869,
|
||||
"descriptionLen": 291,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
"skillify": {
|
||||
"skill": "skillify",
|
||||
"skillMdBytes": 51080,
|
||||
"skillMdLines": 1122,
|
||||
"estTokens": 12770,
|
||||
"tmplBytes": 15107,
|
||||
"descriptionLen": 233,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"sync-gbrain": {
|
||||
"skill": "sync-gbrain",
|
||||
"skillMdBytes": 47702,
|
||||
"skillMdLines": 982,
|
||||
"estTokens": 11926,
|
||||
"tmplBytes": 13996,
|
||||
"descriptionLen": 299,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"unfreeze": {
|
||||
"skill": "unfreeze",
|
||||
"skillMdBytes": 1504,
|
||||
"skillMdLines": 49,
|
||||
"estTokens": 376,
|
||||
"tmplBytes": 1386,
|
||||
"descriptionLen": 199,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
}
|
||||
}
|
||||
}
|
||||
+634
@@ -0,0 +1,634 @@
|
||||
{
|
||||
"tag": "v1.47.0.0",
|
||||
"capturedAt": "2026-05-27T05:50:57.656Z",
|
||||
"capturedFromCommit": "e08e5fa8",
|
||||
"capturedFromBranch": "garrytan/askuserquestion-split-on-overflow",
|
||||
"totalSkills": 52,
|
||||
"totalCorpusBytes": 3090887,
|
||||
"estTotalCatalogTokens": 4116,
|
||||
"topHeaviest": [
|
||||
{
|
||||
"skill": "ship",
|
||||
"skillMdBytes": 166782,
|
||||
"skillMdLines": 3099,
|
||||
"estTokens": 41696,
|
||||
"tmplBytes": 50495,
|
||||
"descriptionLen": 291,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
{
|
||||
"skill": "plan-ceo-review",
|
||||
"skillMdBytes": 132488,
|
||||
"skillMdLines": 2197,
|
||||
"estTokens": 33122,
|
||||
"tmplBytes": 63393,
|
||||
"descriptionLen": 794,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
{
|
||||
"skill": "office-hours",
|
||||
"skillMdBytes": 112842,
|
||||
"skillMdLines": 2066,
|
||||
"estTokens": 28211,
|
||||
"tmplBytes": 55466,
|
||||
"descriptionLen": 860,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
{
|
||||
"skill": "plan-design-review",
|
||||
"skillMdBytes": 107855,
|
||||
"skillMdLines": 1928,
|
||||
"estTokens": 26964,
|
||||
"tmplBytes": 28624,
|
||||
"descriptionLen": 218,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
{
|
||||
"skill": "plan-devex-review",
|
||||
"skillMdBytes": 106167,
|
||||
"skillMdLines": 2119,
|
||||
"estTokens": 26542,
|
||||
"tmplBytes": 35680,
|
||||
"descriptionLen": 250,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
{
|
||||
"skill": "plan-eng-review",
|
||||
"skillMdBytes": 103009,
|
||||
"skillMdLines": 1762,
|
||||
"estTokens": 25752,
|
||||
"tmplBytes": 26234,
|
||||
"descriptionLen": 231,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
{
|
||||
"skill": "spec",
|
||||
"skillMdBytes": 102629,
|
||||
"skillMdLines": 2141,
|
||||
"estTokens": 25657,
|
||||
"tmplBytes": 28429,
|
||||
"descriptionLen": 282,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
{
|
||||
"skill": "design-review",
|
||||
"skillMdBytes": 95654,
|
||||
"skillMdLines": 1932,
|
||||
"estTokens": 23914,
|
||||
"tmplBytes": 11674,
|
||||
"descriptionLen": 304,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
{
|
||||
"skill": "review",
|
||||
"skillMdBytes": 94048,
|
||||
"skillMdLines": 1762,
|
||||
"estTokens": 23512,
|
||||
"tmplBytes": 14099,
|
||||
"descriptionLen": 205,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
{
|
||||
"skill": "land-and-deploy",
|
||||
"skillMdBytes": 91886,
|
||||
"skillMdLines": 1856,
|
||||
"estTokens": 22972,
|
||||
"tmplBytes": 48624,
|
||||
"descriptionLen": 160,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
}
|
||||
],
|
||||
"skills": {
|
||||
"autoplan": {
|
||||
"skill": "autoplan",
|
||||
"skillMdBytes": 90870,
|
||||
"skillMdLines": 1784,
|
||||
"estTokens": 22718,
|
||||
"tmplBytes": 45271,
|
||||
"descriptionLen": 366,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
"benchmark": {
|
||||
"skill": "benchmark",
|
||||
"skillMdBytes": 33266,
|
||||
"skillMdLines": 747,
|
||||
"estTokens": 8317,
|
||||
"tmplBytes": 9378,
|
||||
"descriptionLen": 213,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"benchmark-models": {
|
||||
"skill": "benchmark-models",
|
||||
"skillMdBytes": 29333,
|
||||
"skillMdLines": 622,
|
||||
"estTokens": 7333,
|
||||
"tmplBytes": 6631,
|
||||
"descriptionLen": 217,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"browse": {
|
||||
"skill": "browse",
|
||||
"skillMdBytes": 48018,
|
||||
"skillMdLines": 929,
|
||||
"estTokens": 12005,
|
||||
"tmplBytes": 10805,
|
||||
"descriptionLen": 181,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"canary": {
|
||||
"skill": "canary",
|
||||
"skillMdBytes": 47105,
|
||||
"skillMdLines": 990,
|
||||
"estTokens": 11776,
|
||||
"tmplBytes": 8033,
|
||||
"descriptionLen": 180,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"careful": {
|
||||
"skill": "careful",
|
||||
"skillMdBytes": 2551,
|
||||
"skillMdLines": 68,
|
||||
"estTokens": 638,
|
||||
"tmplBytes": 2435,
|
||||
"descriptionLen": 315,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"codex": {
|
||||
"skill": "codex",
|
||||
"skillMdBytes": 79620,
|
||||
"skillMdLines": 1519,
|
||||
"estTokens": 19905,
|
||||
"tmplBytes": 34143,
|
||||
"descriptionLen": 187,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"context-restore": {
|
||||
"skill": "context-restore",
|
||||
"skillMdBytes": 41493,
|
||||
"skillMdLines": 848,
|
||||
"estTokens": 10373,
|
||||
"tmplBytes": 5255,
|
||||
"descriptionLen": 238,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"context-save": {
|
||||
"skill": "context-save",
|
||||
"skillMdBytes": 45690,
|
||||
"skillMdLines": 966,
|
||||
"estTokens": 11423,
|
||||
"tmplBytes": 9293,
|
||||
"descriptionLen": 168,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"cso": {
|
||||
"skill": "cso",
|
||||
"skillMdBytes": 77397,
|
||||
"skillMdLines": 1451,
|
||||
"estTokens": 19349,
|
||||
"tmplBytes": 35158,
|
||||
"descriptionLen": 196,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"design-consultation": {
|
||||
"skill": "design-consultation",
|
||||
"skillMdBytes": 79222,
|
||||
"skillMdLines": 1561,
|
||||
"estTokens": 19806,
|
||||
"tmplBytes": 25899,
|
||||
"descriptionLen": 888,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"design-html": {
|
||||
"skill": "design-html",
|
||||
"skillMdBytes": 66547,
|
||||
"skillMdLines": 1449,
|
||||
"estTokens": 16637,
|
||||
"tmplBytes": 22567,
|
||||
"descriptionLen": 233,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"design-review": {
|
||||
"skill": "design-review",
|
||||
"skillMdBytes": 95654,
|
||||
"skillMdLines": 1932,
|
||||
"estTokens": 23914,
|
||||
"tmplBytes": 11674,
|
||||
"descriptionLen": 304,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"design-shotgun": {
|
||||
"skill": "design-shotgun",
|
||||
"skillMdBytes": 62836,
|
||||
"skillMdLines": 1311,
|
||||
"estTokens": 15709,
|
||||
"tmplBytes": 13331,
|
||||
"descriptionLen": 786,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"devex-review": {
|
||||
"skill": "devex-review",
|
||||
"skillMdBytes": 64413,
|
||||
"skillMdLines": 1233,
|
||||
"estTokens": 16103,
|
||||
"tmplBytes": 7984,
|
||||
"descriptionLen": 201,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"document-generate": {
|
||||
"skill": "document-generate",
|
||||
"skillMdBytes": 52987,
|
||||
"skillMdLines": 1176,
|
||||
"estTokens": 13247,
|
||||
"tmplBytes": 15093,
|
||||
"descriptionLen": 334,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"document-release": {
|
||||
"skill": "document-release",
|
||||
"skillMdBytes": 58251,
|
||||
"skillMdLines": 1235,
|
||||
"estTokens": 14563,
|
||||
"tmplBytes": 20362,
|
||||
"descriptionLen": 192,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"freeze": {
|
||||
"skill": "freeze",
|
||||
"skillMdBytes": 3154,
|
||||
"skillMdLines": 92,
|
||||
"estTokens": 789,
|
||||
"tmplBytes": 3038,
|
||||
"descriptionLen": 503,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"gstack-upgrade": {
|
||||
"skill": "gstack-upgrade",
|
||||
"skillMdBytes": 10817,
|
||||
"skillMdLines": 285,
|
||||
"estTokens": 2704,
|
||||
"tmplBytes": 10667,
|
||||
"descriptionLen": 163,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"guard": {
|
||||
"skill": "guard",
|
||||
"skillMdBytes": 3297,
|
||||
"skillMdLines": 91,
|
||||
"estTokens": 824,
|
||||
"tmplBytes": 3181,
|
||||
"descriptionLen": 686,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"health": {
|
||||
"skill": "health",
|
||||
"skillMdBytes": 47916,
|
||||
"skillMdLines": 1014,
|
||||
"estTokens": 11979,
|
||||
"tmplBytes": 11617,
|
||||
"descriptionLen": 184,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"investigate": {
|
||||
"skill": "investigate",
|
||||
"skillMdBytes": 50409,
|
||||
"skillMdLines": 1012,
|
||||
"estTokens": 12602,
|
||||
"tmplBytes": 11561,
|
||||
"descriptionLen": 1379,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"ios-clean": {
|
||||
"skill": "ios-clean",
|
||||
"skillMdBytes": 41045,
|
||||
"skillMdLines": 813,
|
||||
"estTokens": 10261,
|
||||
"tmplBytes": 3851,
|
||||
"descriptionLen": 252,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"ios-design-review": {
|
||||
"skill": "ios-design-review",
|
||||
"skillMdBytes": 41631,
|
||||
"skillMdLines": 815,
|
||||
"estTokens": 10408,
|
||||
"tmplBytes": 4417,
|
||||
"descriptionLen": 209,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"ios-fix": {
|
||||
"skill": "ios-fix",
|
||||
"skillMdBytes": 40760,
|
||||
"skillMdLines": 811,
|
||||
"estTokens": 10190,
|
||||
"tmplBytes": 3574,
|
||||
"descriptionLen": 187,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"ios-qa": {
|
||||
"skill": "ios-qa",
|
||||
"skillMdBytes": 47271,
|
||||
"skillMdLines": 931,
|
||||
"estTokens": 11818,
|
||||
"tmplBytes": 10090,
|
||||
"descriptionLen": 223,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"ios-sync": {
|
||||
"skill": "ios-sync",
|
||||
"skillMdBytes": 40737,
|
||||
"skillMdLines": 804,
|
||||
"estTokens": 10184,
|
||||
"tmplBytes": 3544,
|
||||
"descriptionLen": 269,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"land-and-deploy": {
|
||||
"skill": "land-and-deploy",
|
||||
"skillMdBytes": 91886,
|
||||
"skillMdLines": 1856,
|
||||
"estTokens": 22972,
|
||||
"tmplBytes": 48624,
|
||||
"descriptionLen": 160,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"landing-report": {
|
||||
"skill": "landing-report",
|
||||
"skillMdBytes": 43985,
|
||||
"skillMdLines": 874,
|
||||
"estTokens": 10996,
|
||||
"tmplBytes": 6806,
|
||||
"descriptionLen": 195,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"learn": {
|
||||
"skill": "learn",
|
||||
"skillMdBytes": 41722,
|
||||
"skillMdLines": 891,
|
||||
"estTokens": 10431,
|
||||
"tmplBytes": 5594,
|
||||
"descriptionLen": 178,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"make-pdf": {
|
||||
"skill": "make-pdf",
|
||||
"skillMdBytes": 29450,
|
||||
"skillMdLines": 663,
|
||||
"estTokens": 7363,
|
||||
"tmplBytes": 5106,
|
||||
"descriptionLen": 177,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"office-hours": {
|
||||
"skill": "office-hours",
|
||||
"skillMdBytes": 112842,
|
||||
"skillMdLines": 2066,
|
||||
"estTokens": 28211,
|
||||
"tmplBytes": 55466,
|
||||
"descriptionLen": 860,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"open-gstack-browser": {
|
||||
"skill": "open-gstack-browser",
|
||||
"skillMdBytes": 46131,
|
||||
"skillMdLines": 954,
|
||||
"estTokens": 11533,
|
||||
"tmplBytes": 7702,
|
||||
"descriptionLen": 204,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"pair-agent": {
|
||||
"skill": "pair-agent",
|
||||
"skillMdBytes": 46939,
|
||||
"skillMdLines": 1010,
|
||||
"estTokens": 11735,
|
||||
"tmplBytes": 8548,
|
||||
"descriptionLen": 167,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"plan-ceo-review": {
|
||||
"skill": "plan-ceo-review",
|
||||
"skillMdBytes": 132488,
|
||||
"skillMdLines": 2197,
|
||||
"estTokens": 33122,
|
||||
"tmplBytes": 63393,
|
||||
"descriptionLen": 794,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
"plan-design-review": {
|
||||
"skill": "plan-design-review",
|
||||
"skillMdBytes": 107855,
|
||||
"skillMdLines": 1928,
|
||||
"estTokens": 26964,
|
||||
"tmplBytes": 28624,
|
||||
"descriptionLen": 218,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
"plan-devex-review": {
|
||||
"skill": "plan-devex-review",
|
||||
"skillMdBytes": 106167,
|
||||
"skillMdLines": 2119,
|
||||
"estTokens": 26542,
|
||||
"tmplBytes": 35680,
|
||||
"descriptionLen": 250,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
"plan-eng-review": {
|
||||
"skill": "plan-eng-review",
|
||||
"skillMdBytes": 103009,
|
||||
"skillMdLines": 1762,
|
||||
"estTokens": 25752,
|
||||
"tmplBytes": 26234,
|
||||
"descriptionLen": 231,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
"plan-tune": {
|
||||
"skill": "plan-tune",
|
||||
"skillMdBytes": 64017,
|
||||
"skillMdLines": 1357,
|
||||
"estTokens": 16004,
|
||||
"tmplBytes": 25196,
|
||||
"descriptionLen": 325,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false,
|
||||
"_baseline_note": "Rebased from 51717 → 64017 in plan-tune cathedral v1.52.0.0 (T13). Cathedral added Dream cycle, Recent auto-decisions, Audit unmarked, Dream cycle review/distill sections — all load-bearing for hook substrate. See CHANGELOG.md [1.52.0.0]."
|
||||
},
|
||||
"qa": {
|
||||
"skill": "qa",
|
||||
"skillMdBytes": 73863,
|
||||
"skillMdLines": 1622,
|
||||
"estTokens": 18466,
|
||||
"tmplBytes": 12701,
|
||||
"descriptionLen": 218,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"qa-only": {
|
||||
"skill": "qa-only",
|
||||
"skillMdBytes": 56421,
|
||||
"skillMdLines": 1194,
|
||||
"estTokens": 14105,
|
||||
"tmplBytes": 3851,
|
||||
"descriptionLen": 165,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"retro": {
|
||||
"skill": "retro",
|
||||
"skillMdBytes": 82889,
|
||||
"skillMdLines": 1750,
|
||||
"estTokens": 20722,
|
||||
"tmplBytes": 42427,
|
||||
"descriptionLen": 648,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"review": {
|
||||
"skill": "review",
|
||||
"skillMdBytes": 94048,
|
||||
"skillMdLines": 1762,
|
||||
"estTokens": 23512,
|
||||
"tmplBytes": 14099,
|
||||
"descriptionLen": 205,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"scrape": {
|
||||
"skill": "scrape",
|
||||
"skillMdBytes": 43641,
|
||||
"skillMdLines": 887,
|
||||
"estTokens": 10910,
|
||||
"tmplBytes": 5220,
|
||||
"descriptionLen": 167,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"setup-browser-cookies": {
|
||||
"skill": "setup-browser-cookies",
|
||||
"skillMdBytes": 26618,
|
||||
"skillMdLines": 594,
|
||||
"estTokens": 6655,
|
||||
"tmplBytes": 2724,
|
||||
"descriptionLen": 222,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"setup-deploy": {
|
||||
"skill": "setup-deploy",
|
||||
"skillMdBytes": 43927,
|
||||
"skillMdLines": 919,
|
||||
"estTokens": 10982,
|
||||
"tmplBytes": 7780,
|
||||
"descriptionLen": 197,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"setup-gbrain": {
|
||||
"skill": "setup-gbrain",
|
||||
"skillMdBytes": 78394,
|
||||
"skillMdLines": 1704,
|
||||
"estTokens": 19599,
|
||||
"tmplBytes": 42245,
|
||||
"descriptionLen": 323,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"ship": {
|
||||
"skill": "ship",
|
||||
"skillMdBytes": 166782,
|
||||
"skillMdLines": 3099,
|
||||
"estTokens": 41696,
|
||||
"tmplBytes": 50495,
|
||||
"descriptionLen": 291,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
"skillify": {
|
||||
"skill": "skillify",
|
||||
"skillMdBytes": 53534,
|
||||
"skillMdLines": 1168,
|
||||
"estTokens": 13384,
|
||||
"tmplBytes": 15107,
|
||||
"descriptionLen": 233,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"spec": {
|
||||
"skill": "spec",
|
||||
"skillMdBytes": 102629,
|
||||
"skillMdLines": 2141,
|
||||
"estTokens": 25657,
|
||||
"tmplBytes": 28429,
|
||||
"descriptionLen": 282,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"sync-gbrain": {
|
||||
"skill": "sync-gbrain",
|
||||
"skillMdBytes": 50156,
|
||||
"skillMdLines": 1028,
|
||||
"estTokens": 12539,
|
||||
"tmplBytes": 13996,
|
||||
"descriptionLen": 299,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"unfreeze": {
|
||||
"skill": "unfreeze",
|
||||
"skillMdBytes": 1504,
|
||||
"skillMdLines": 49,
|
||||
"estTokens": 376,
|
||||
"tmplBytes": 1386,
|
||||
"descriptionLen": 199,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
}
|
||||
}
|
||||
}
|
||||
+633
@@ -0,0 +1,633 @@
|
||||
{
|
||||
"tag": "v1.53.0.0",
|
||||
"capturedAt": "2026-05-30T18:00:56.209Z",
|
||||
"capturedFromCommit": "352f6a57",
|
||||
"capturedFromBranch": "garrytan/setup-plan-tune-hooks-flags",
|
||||
"totalSkills": 52,
|
||||
"totalCorpusBytes": 3179282,
|
||||
"estTotalCatalogTokens": 4116,
|
||||
"topHeaviest": [
|
||||
{
|
||||
"skill": "ship",
|
||||
"skillMdBytes": 170491,
|
||||
"skillMdLines": 3153,
|
||||
"estTokens": 42623,
|
||||
"tmplBytes": 53240,
|
||||
"descriptionLen": 291,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
{
|
||||
"skill": "plan-ceo-review",
|
||||
"skillMdBytes": 137751,
|
||||
"skillMdLines": 2290,
|
||||
"estTokens": 34438,
|
||||
"tmplBytes": 63461,
|
||||
"descriptionLen": 794,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
{
|
||||
"skill": "office-hours",
|
||||
"skillMdBytes": 118280,
|
||||
"skillMdLines": 2161,
|
||||
"estTokens": 29570,
|
||||
"tmplBytes": 55534,
|
||||
"descriptionLen": 860,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
{
|
||||
"skill": "plan-design-review",
|
||||
"skillMdBytes": 112728,
|
||||
"skillMdLines": 2019,
|
||||
"estTokens": 28182,
|
||||
"tmplBytes": 28717,
|
||||
"descriptionLen": 218,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
{
|
||||
"skill": "plan-devex-review",
|
||||
"skillMdBytes": 111292,
|
||||
"skillMdLines": 2212,
|
||||
"estTokens": 27823,
|
||||
"tmplBytes": 35773,
|
||||
"descriptionLen": 250,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
{
|
||||
"skill": "spec",
|
||||
"skillMdBytes": 109688,
|
||||
"skillMdLines": 2239,
|
||||
"estTokens": 27422,
|
||||
"tmplBytes": 30590,
|
||||
"descriptionLen": 282,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
{
|
||||
"skill": "plan-eng-review",
|
||||
"skillMdBytes": 107655,
|
||||
"skillMdLines": 1849,
|
||||
"estTokens": 26914,
|
||||
"tmplBytes": 26302,
|
||||
"descriptionLen": 231,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
{
|
||||
"skill": "design-review",
|
||||
"skillMdBytes": 96618,
|
||||
"skillMdLines": 1936,
|
||||
"estTokens": 24155,
|
||||
"tmplBytes": 11674,
|
||||
"descriptionLen": 304,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
{
|
||||
"skill": "review",
|
||||
"skillMdBytes": 95012,
|
||||
"skillMdLines": 1766,
|
||||
"estTokens": 23753,
|
||||
"tmplBytes": 14099,
|
||||
"descriptionLen": 205,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
{
|
||||
"skill": "land-and-deploy",
|
||||
"skillMdBytes": 92850,
|
||||
"skillMdLines": 1860,
|
||||
"estTokens": 23213,
|
||||
"tmplBytes": 48624,
|
||||
"descriptionLen": 160,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
}
|
||||
],
|
||||
"skills": {
|
||||
"autoplan": {
|
||||
"skill": "autoplan",
|
||||
"skillMdBytes": 91834,
|
||||
"skillMdLines": 1788,
|
||||
"estTokens": 22959,
|
||||
"tmplBytes": 45271,
|
||||
"descriptionLen": 366,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
"benchmark": {
|
||||
"skill": "benchmark",
|
||||
"skillMdBytes": 33266,
|
||||
"skillMdLines": 747,
|
||||
"estTokens": 8317,
|
||||
"tmplBytes": 9378,
|
||||
"descriptionLen": 213,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"benchmark-models": {
|
||||
"skill": "benchmark-models",
|
||||
"skillMdBytes": 29333,
|
||||
"skillMdLines": 622,
|
||||
"estTokens": 7333,
|
||||
"tmplBytes": 6631,
|
||||
"descriptionLen": 217,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"browse": {
|
||||
"skill": "browse",
|
||||
"skillMdBytes": 48151,
|
||||
"skillMdLines": 930,
|
||||
"estTokens": 12038,
|
||||
"tmplBytes": 10805,
|
||||
"descriptionLen": 181,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"canary": {
|
||||
"skill": "canary",
|
||||
"skillMdBytes": 48069,
|
||||
"skillMdLines": 994,
|
||||
"estTokens": 12017,
|
||||
"tmplBytes": 8033,
|
||||
"descriptionLen": 180,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"careful": {
|
||||
"skill": "careful",
|
||||
"skillMdBytes": 2551,
|
||||
"skillMdLines": 68,
|
||||
"estTokens": 638,
|
||||
"tmplBytes": 2435,
|
||||
"descriptionLen": 315,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"codex": {
|
||||
"skill": "codex",
|
||||
"skillMdBytes": 80584,
|
||||
"skillMdLines": 1523,
|
||||
"estTokens": 20146,
|
||||
"tmplBytes": 34143,
|
||||
"descriptionLen": 187,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"context-restore": {
|
||||
"skill": "context-restore",
|
||||
"skillMdBytes": 42457,
|
||||
"skillMdLines": 852,
|
||||
"estTokens": 10614,
|
||||
"tmplBytes": 5255,
|
||||
"descriptionLen": 238,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"context-save": {
|
||||
"skill": "context-save",
|
||||
"skillMdBytes": 46654,
|
||||
"skillMdLines": 970,
|
||||
"estTokens": 11664,
|
||||
"tmplBytes": 9293,
|
||||
"descriptionLen": 168,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"cso": {
|
||||
"skill": "cso",
|
||||
"skillMdBytes": 78849,
|
||||
"skillMdLines": 1462,
|
||||
"estTokens": 19712,
|
||||
"tmplBytes": 35646,
|
||||
"descriptionLen": 196,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"design-consultation": {
|
||||
"skill": "design-consultation",
|
||||
"skillMdBytes": 80186,
|
||||
"skillMdLines": 1565,
|
||||
"estTokens": 20047,
|
||||
"tmplBytes": 25899,
|
||||
"descriptionLen": 888,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"design-html": {
|
||||
"skill": "design-html",
|
||||
"skillMdBytes": 67511,
|
||||
"skillMdLines": 1453,
|
||||
"estTokens": 16878,
|
||||
"tmplBytes": 22567,
|
||||
"descriptionLen": 233,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"design-review": {
|
||||
"skill": "design-review",
|
||||
"skillMdBytes": 96618,
|
||||
"skillMdLines": 1936,
|
||||
"estTokens": 24155,
|
||||
"tmplBytes": 11674,
|
||||
"descriptionLen": 304,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"design-shotgun": {
|
||||
"skill": "design-shotgun",
|
||||
"skillMdBytes": 63800,
|
||||
"skillMdLines": 1315,
|
||||
"estTokens": 15950,
|
||||
"tmplBytes": 13331,
|
||||
"descriptionLen": 786,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"devex-review": {
|
||||
"skill": "devex-review",
|
||||
"skillMdBytes": 65377,
|
||||
"skillMdLines": 1237,
|
||||
"estTokens": 16344,
|
||||
"tmplBytes": 7984,
|
||||
"descriptionLen": 201,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"document-generate": {
|
||||
"skill": "document-generate",
|
||||
"skillMdBytes": 54797,
|
||||
"skillMdLines": 1194,
|
||||
"estTokens": 13699,
|
||||
"tmplBytes": 15939,
|
||||
"descriptionLen": 334,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"document-release": {
|
||||
"skill": "document-release",
|
||||
"skillMdBytes": 59827,
|
||||
"skillMdLines": 1248,
|
||||
"estTokens": 14957,
|
||||
"tmplBytes": 20974,
|
||||
"descriptionLen": 192,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"freeze": {
|
||||
"skill": "freeze",
|
||||
"skillMdBytes": 3154,
|
||||
"skillMdLines": 92,
|
||||
"estTokens": 789,
|
||||
"tmplBytes": 3038,
|
||||
"descriptionLen": 503,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"gstack-upgrade": {
|
||||
"skill": "gstack-upgrade",
|
||||
"skillMdBytes": 10817,
|
||||
"skillMdLines": 285,
|
||||
"estTokens": 2704,
|
||||
"tmplBytes": 10667,
|
||||
"descriptionLen": 163,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"guard": {
|
||||
"skill": "guard",
|
||||
"skillMdBytes": 3297,
|
||||
"skillMdLines": 91,
|
||||
"estTokens": 824,
|
||||
"tmplBytes": 3181,
|
||||
"descriptionLen": 686,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"health": {
|
||||
"skill": "health",
|
||||
"skillMdBytes": 48880,
|
||||
"skillMdLines": 1018,
|
||||
"estTokens": 12220,
|
||||
"tmplBytes": 11617,
|
||||
"descriptionLen": 184,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"investigate": {
|
||||
"skill": "investigate",
|
||||
"skillMdBytes": 51373,
|
||||
"skillMdLines": 1016,
|
||||
"estTokens": 12843,
|
||||
"tmplBytes": 11561,
|
||||
"descriptionLen": 1379,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"ios-clean": {
|
||||
"skill": "ios-clean",
|
||||
"skillMdBytes": 42009,
|
||||
"skillMdLines": 817,
|
||||
"estTokens": 10502,
|
||||
"tmplBytes": 3851,
|
||||
"descriptionLen": 252,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"ios-design-review": {
|
||||
"skill": "ios-design-review",
|
||||
"skillMdBytes": 42595,
|
||||
"skillMdLines": 819,
|
||||
"estTokens": 10649,
|
||||
"tmplBytes": 4417,
|
||||
"descriptionLen": 209,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"ios-fix": {
|
||||
"skill": "ios-fix",
|
||||
"skillMdBytes": 41724,
|
||||
"skillMdLines": 815,
|
||||
"estTokens": 10431,
|
||||
"tmplBytes": 3574,
|
||||
"descriptionLen": 187,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"ios-qa": {
|
||||
"skill": "ios-qa",
|
||||
"skillMdBytes": 48235,
|
||||
"skillMdLines": 935,
|
||||
"estTokens": 12059,
|
||||
"tmplBytes": 10090,
|
||||
"descriptionLen": 223,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"ios-sync": {
|
||||
"skill": "ios-sync",
|
||||
"skillMdBytes": 41701,
|
||||
"skillMdLines": 808,
|
||||
"estTokens": 10425,
|
||||
"tmplBytes": 3544,
|
||||
"descriptionLen": 269,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"land-and-deploy": {
|
||||
"skill": "land-and-deploy",
|
||||
"skillMdBytes": 92850,
|
||||
"skillMdLines": 1860,
|
||||
"estTokens": 23213,
|
||||
"tmplBytes": 48624,
|
||||
"descriptionLen": 160,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"landing-report": {
|
||||
"skill": "landing-report",
|
||||
"skillMdBytes": 44949,
|
||||
"skillMdLines": 878,
|
||||
"estTokens": 11237,
|
||||
"tmplBytes": 6806,
|
||||
"descriptionLen": 195,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"learn": {
|
||||
"skill": "learn",
|
||||
"skillMdBytes": 42686,
|
||||
"skillMdLines": 895,
|
||||
"estTokens": 10672,
|
||||
"tmplBytes": 5594,
|
||||
"descriptionLen": 178,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"make-pdf": {
|
||||
"skill": "make-pdf",
|
||||
"skillMdBytes": 29890,
|
||||
"skillMdLines": 670,
|
||||
"estTokens": 7473,
|
||||
"tmplBytes": 5546,
|
||||
"descriptionLen": 177,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"office-hours": {
|
||||
"skill": "office-hours",
|
||||
"skillMdBytes": 118280,
|
||||
"skillMdLines": 2161,
|
||||
"estTokens": 29570,
|
||||
"tmplBytes": 55534,
|
||||
"descriptionLen": 860,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"open-gstack-browser": {
|
||||
"skill": "open-gstack-browser",
|
||||
"skillMdBytes": 47095,
|
||||
"skillMdLines": 958,
|
||||
"estTokens": 11774,
|
||||
"tmplBytes": 7702,
|
||||
"descriptionLen": 204,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"pair-agent": {
|
||||
"skill": "pair-agent",
|
||||
"skillMdBytes": 47903,
|
||||
"skillMdLines": 1014,
|
||||
"estTokens": 11976,
|
||||
"tmplBytes": 8548,
|
||||
"descriptionLen": 167,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"plan-ceo-review": {
|
||||
"skill": "plan-ceo-review",
|
||||
"skillMdBytes": 137751,
|
||||
"skillMdLines": 2290,
|
||||
"estTokens": 34438,
|
||||
"tmplBytes": 63461,
|
||||
"descriptionLen": 794,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
"plan-design-review": {
|
||||
"skill": "plan-design-review",
|
||||
"skillMdBytes": 112728,
|
||||
"skillMdLines": 2019,
|
||||
"estTokens": 28182,
|
||||
"tmplBytes": 28717,
|
||||
"descriptionLen": 218,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
"plan-devex-review": {
|
||||
"skill": "plan-devex-review",
|
||||
"skillMdBytes": 111292,
|
||||
"skillMdLines": 2212,
|
||||
"estTokens": 27823,
|
||||
"tmplBytes": 35773,
|
||||
"descriptionLen": 250,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
"plan-eng-review": {
|
||||
"skill": "plan-eng-review",
|
||||
"skillMdBytes": 107655,
|
||||
"skillMdLines": 1849,
|
||||
"estTokens": 26914,
|
||||
"tmplBytes": 26302,
|
||||
"descriptionLen": 231,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
"plan-tune": {
|
||||
"skill": "plan-tune",
|
||||
"skillMdBytes": 64017,
|
||||
"skillMdLines": 1355,
|
||||
"estTokens": 16004,
|
||||
"tmplBytes": 26922,
|
||||
"descriptionLen": 325,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"qa": {
|
||||
"skill": "qa",
|
||||
"skillMdBytes": 74827,
|
||||
"skillMdLines": 1626,
|
||||
"estTokens": 18707,
|
||||
"tmplBytes": 12701,
|
||||
"descriptionLen": 218,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"qa-only": {
|
||||
"skill": "qa-only",
|
||||
"skillMdBytes": 57385,
|
||||
"skillMdLines": 1198,
|
||||
"estTokens": 14346,
|
||||
"tmplBytes": 3851,
|
||||
"descriptionLen": 165,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"retro": {
|
||||
"skill": "retro",
|
||||
"skillMdBytes": 83853,
|
||||
"skillMdLines": 1754,
|
||||
"estTokens": 20963,
|
||||
"tmplBytes": 42427,
|
||||
"descriptionLen": 648,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"review": {
|
||||
"skill": "review",
|
||||
"skillMdBytes": 95012,
|
||||
"skillMdLines": 1766,
|
||||
"estTokens": 23753,
|
||||
"tmplBytes": 14099,
|
||||
"descriptionLen": 205,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"scrape": {
|
||||
"skill": "scrape",
|
||||
"skillMdBytes": 44605,
|
||||
"skillMdLines": 891,
|
||||
"estTokens": 11151,
|
||||
"tmplBytes": 5220,
|
||||
"descriptionLen": 167,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"setup-browser-cookies": {
|
||||
"skill": "setup-browser-cookies",
|
||||
"skillMdBytes": 26618,
|
||||
"skillMdLines": 594,
|
||||
"estTokens": 6655,
|
||||
"tmplBytes": 2724,
|
||||
"descriptionLen": 222,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"setup-deploy": {
|
||||
"skill": "setup-deploy",
|
||||
"skillMdBytes": 44891,
|
||||
"skillMdLines": 923,
|
||||
"estTokens": 11223,
|
||||
"tmplBytes": 7780,
|
||||
"descriptionLen": 197,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"setup-gbrain": {
|
||||
"skill": "setup-gbrain",
|
||||
"skillMdBytes": 81964,
|
||||
"skillMdLines": 1777,
|
||||
"estTokens": 20491,
|
||||
"tmplBytes": 44851,
|
||||
"descriptionLen": 323,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"ship": {
|
||||
"skill": "ship",
|
||||
"skillMdBytes": 170491,
|
||||
"skillMdLines": 3153,
|
||||
"estTokens": 42623,
|
||||
"tmplBytes": 53240,
|
||||
"descriptionLen": 291,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
"skillify": {
|
||||
"skill": "skillify",
|
||||
"skillMdBytes": 54498,
|
||||
"skillMdLines": 1172,
|
||||
"estTokens": 13625,
|
||||
"tmplBytes": 15107,
|
||||
"descriptionLen": 233,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"spec": {
|
||||
"skill": "spec",
|
||||
"skillMdBytes": 109688,
|
||||
"skillMdLines": 2239,
|
||||
"estTokens": 27422,
|
||||
"tmplBytes": 30590,
|
||||
"descriptionLen": 282,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"sync-gbrain": {
|
||||
"skill": "sync-gbrain",
|
||||
"skillMdBytes": 53201,
|
||||
"skillMdLines": 1070,
|
||||
"estTokens": 13300,
|
||||
"tmplBytes": 16077,
|
||||
"descriptionLen": 299,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"unfreeze": {
|
||||
"skill": "unfreeze",
|
||||
"skillMdBytes": 1504,
|
||||
"skillMdLines": 49,
|
||||
"estTokens": 376,
|
||||
"tmplBytes": 1386,
|
||||
"descriptionLen": 199,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
}
|
||||
}
|
||||
}
|
||||
+633
@@ -0,0 +1,633 @@
|
||||
{
|
||||
"tag": "v1.57.7.0",
|
||||
"capturedAt": "2026-05-30T18:00:56.209Z",
|
||||
"capturedFromCommit": "49035bdd",
|
||||
"capturedFromBranch": "garrytan/plan-flag-unresolved-issues",
|
||||
"totalSkills": 52,
|
||||
"totalCorpusBytes": 3359373,
|
||||
"estTotalCatalogTokens": 4116,
|
||||
"topHeaviest": [
|
||||
{
|
||||
"skill": "ship",
|
||||
"skillMdBytes": 174407,
|
||||
"skillMdLines": 3137,
|
||||
"estTokens": 43602,
|
||||
"tmplBytes": 53240,
|
||||
"descriptionLen": 291,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
{
|
||||
"skill": "plan-ceo-review",
|
||||
"skillMdBytes": 144411,
|
||||
"skillMdLines": 2349,
|
||||
"estTokens": 36103,
|
||||
"tmplBytes": 63461,
|
||||
"descriptionLen": 794,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
{
|
||||
"skill": "office-hours",
|
||||
"skillMdBytes": 123037,
|
||||
"skillMdLines": 2200,
|
||||
"estTokens": 30759,
|
||||
"tmplBytes": 55534,
|
||||
"descriptionLen": 860,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
{
|
||||
"skill": "plan-design-review",
|
||||
"skillMdBytes": 118532,
|
||||
"skillMdLines": 2073,
|
||||
"estTokens": 29633,
|
||||
"tmplBytes": 28717,
|
||||
"descriptionLen": 218,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
{
|
||||
"skill": "plan-devex-review",
|
||||
"skillMdBytes": 117907,
|
||||
"skillMdLines": 2277,
|
||||
"estTokens": 29477,
|
||||
"tmplBytes": 35773,
|
||||
"descriptionLen": 250,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
{
|
||||
"skill": "spec",
|
||||
"skillMdBytes": 117382,
|
||||
"skillMdLines": 2276,
|
||||
"estTokens": 29346,
|
||||
"tmplBytes": 30590,
|
||||
"descriptionLen": 282,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
{
|
||||
"skill": "plan-eng-review",
|
||||
"skillMdBytes": 114209,
|
||||
"skillMdLines": 1906,
|
||||
"estTokens": 28552,
|
||||
"tmplBytes": 26302,
|
||||
"descriptionLen": 231,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
{
|
||||
"skill": "design-review",
|
||||
"skillMdBytes": 100149,
|
||||
"skillMdLines": 1953,
|
||||
"estTokens": 25037,
|
||||
"tmplBytes": 11674,
|
||||
"descriptionLen": 304,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
{
|
||||
"skill": "review",
|
||||
"skillMdBytes": 99573,
|
||||
"skillMdLines": 1787,
|
||||
"estTokens": 24893,
|
||||
"tmplBytes": 14099,
|
||||
"descriptionLen": 205,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
{
|
||||
"skill": "land-and-deploy",
|
||||
"skillMdBytes": 96379,
|
||||
"skillMdLines": 1877,
|
||||
"estTokens": 24095,
|
||||
"tmplBytes": 48624,
|
||||
"descriptionLen": 160,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
}
|
||||
],
|
||||
"skills": {
|
||||
"autoplan": {
|
||||
"skill": "autoplan",
|
||||
"skillMdBytes": 95365,
|
||||
"skillMdLines": 1805,
|
||||
"estTokens": 23841,
|
||||
"tmplBytes": 45271,
|
||||
"descriptionLen": 366,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
"benchmark": {
|
||||
"skill": "benchmark",
|
||||
"skillMdBytes": 33646,
|
||||
"skillMdLines": 750,
|
||||
"estTokens": 8412,
|
||||
"tmplBytes": 9378,
|
||||
"descriptionLen": 213,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"benchmark-models": {
|
||||
"skill": "benchmark-models",
|
||||
"skillMdBytes": 29713,
|
||||
"skillMdLines": 625,
|
||||
"estTokens": 7428,
|
||||
"tmplBytes": 6631,
|
||||
"descriptionLen": 217,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"browse": {
|
||||
"skill": "browse",
|
||||
"skillMdBytes": 48531,
|
||||
"skillMdLines": 933,
|
||||
"estTokens": 12133,
|
||||
"tmplBytes": 10805,
|
||||
"descriptionLen": 181,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"canary": {
|
||||
"skill": "canary",
|
||||
"skillMdBytes": 51598,
|
||||
"skillMdLines": 1011,
|
||||
"estTokens": 12900,
|
||||
"tmplBytes": 8033,
|
||||
"descriptionLen": 180,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"careful": {
|
||||
"skill": "careful",
|
||||
"skillMdBytes": 2567,
|
||||
"skillMdLines": 68,
|
||||
"estTokens": 642,
|
||||
"tmplBytes": 2435,
|
||||
"descriptionLen": 315,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"codex": {
|
||||
"skill": "codex",
|
||||
"skillMdBytes": 85212,
|
||||
"skillMdLines": 1555,
|
||||
"estTokens": 21303,
|
||||
"tmplBytes": 34143,
|
||||
"descriptionLen": 187,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"context-restore": {
|
||||
"skill": "context-restore",
|
||||
"skillMdBytes": 45986,
|
||||
"skillMdLines": 869,
|
||||
"estTokens": 11497,
|
||||
"tmplBytes": 5255,
|
||||
"descriptionLen": 238,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"context-save": {
|
||||
"skill": "context-save",
|
||||
"skillMdBytes": 50183,
|
||||
"skillMdLines": 987,
|
||||
"estTokens": 12546,
|
||||
"tmplBytes": 9293,
|
||||
"descriptionLen": 168,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"cso": {
|
||||
"skill": "cso",
|
||||
"skillMdBytes": 83808,
|
||||
"skillMdLines": 1498,
|
||||
"estTokens": 20952,
|
||||
"tmplBytes": 35646,
|
||||
"descriptionLen": 196,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"design-consultation": {
|
||||
"skill": "design-consultation",
|
||||
"skillMdBytes": 84683,
|
||||
"skillMdLines": 1598,
|
||||
"estTokens": 21171,
|
||||
"tmplBytes": 25899,
|
||||
"descriptionLen": 888,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"design-html": {
|
||||
"skill": "design-html",
|
||||
"skillMdBytes": 71042,
|
||||
"skillMdLines": 1470,
|
||||
"estTokens": 17761,
|
||||
"tmplBytes": 22567,
|
||||
"descriptionLen": 233,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"design-review": {
|
||||
"skill": "design-review",
|
||||
"skillMdBytes": 100149,
|
||||
"skillMdLines": 1953,
|
||||
"estTokens": 25037,
|
||||
"tmplBytes": 11674,
|
||||
"descriptionLen": 304,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"design-shotgun": {
|
||||
"skill": "design-shotgun",
|
||||
"skillMdBytes": 67331,
|
||||
"skillMdLines": 1332,
|
||||
"estTokens": 16833,
|
||||
"tmplBytes": 13331,
|
||||
"descriptionLen": 786,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"devex-review": {
|
||||
"skill": "devex-review",
|
||||
"skillMdBytes": 69681,
|
||||
"skillMdLines": 1264,
|
||||
"estTokens": 17420,
|
||||
"tmplBytes": 7984,
|
||||
"descriptionLen": 201,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"document-generate": {
|
||||
"skill": "document-generate",
|
||||
"skillMdBytes": 58327,
|
||||
"skillMdLines": 1211,
|
||||
"estTokens": 14582,
|
||||
"tmplBytes": 15939,
|
||||
"descriptionLen": 334,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"document-release": {
|
||||
"skill": "document-release",
|
||||
"skillMdBytes": 64403,
|
||||
"skillMdLines": 1281,
|
||||
"estTokens": 16101,
|
||||
"tmplBytes": 20974,
|
||||
"descriptionLen": 192,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"freeze": {
|
||||
"skill": "freeze",
|
||||
"skillMdBytes": 3184,
|
||||
"skillMdLines": 92,
|
||||
"estTokens": 796,
|
||||
"tmplBytes": 3038,
|
||||
"descriptionLen": 503,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"gstack-upgrade": {
|
||||
"skill": "gstack-upgrade",
|
||||
"skillMdBytes": 10817,
|
||||
"skillMdLines": 285,
|
||||
"estTokens": 2704,
|
||||
"tmplBytes": 10667,
|
||||
"descriptionLen": 163,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"guard": {
|
||||
"skill": "guard",
|
||||
"skillMdBytes": 3314,
|
||||
"skillMdLines": 91,
|
||||
"estTokens": 829,
|
||||
"tmplBytes": 3181,
|
||||
"descriptionLen": 686,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"health": {
|
||||
"skill": "health",
|
||||
"skillMdBytes": 52409,
|
||||
"skillMdLines": 1035,
|
||||
"estTokens": 13102,
|
||||
"tmplBytes": 11617,
|
||||
"descriptionLen": 184,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"investigate": {
|
||||
"skill": "investigate",
|
||||
"skillMdBytes": 54902,
|
||||
"skillMdLines": 1033,
|
||||
"estTokens": 13726,
|
||||
"tmplBytes": 11561,
|
||||
"descriptionLen": 1379,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"ios-clean": {
|
||||
"skill": "ios-clean",
|
||||
"skillMdBytes": 45540,
|
||||
"skillMdLines": 834,
|
||||
"estTokens": 11385,
|
||||
"tmplBytes": 3851,
|
||||
"descriptionLen": 252,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"ios-design-review": {
|
||||
"skill": "ios-design-review",
|
||||
"skillMdBytes": 46124,
|
||||
"skillMdLines": 836,
|
||||
"estTokens": 11531,
|
||||
"tmplBytes": 4417,
|
||||
"descriptionLen": 209,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"ios-fix": {
|
||||
"skill": "ios-fix",
|
||||
"skillMdBytes": 45253,
|
||||
"skillMdLines": 832,
|
||||
"estTokens": 11313,
|
||||
"tmplBytes": 3574,
|
||||
"descriptionLen": 187,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"ios-qa": {
|
||||
"skill": "ios-qa",
|
||||
"skillMdBytes": 51764,
|
||||
"skillMdLines": 952,
|
||||
"estTokens": 12941,
|
||||
"tmplBytes": 10090,
|
||||
"descriptionLen": 223,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"ios-sync": {
|
||||
"skill": "ios-sync",
|
||||
"skillMdBytes": 45230,
|
||||
"skillMdLines": 825,
|
||||
"estTokens": 11308,
|
||||
"tmplBytes": 3544,
|
||||
"descriptionLen": 269,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"land-and-deploy": {
|
||||
"skill": "land-and-deploy",
|
||||
"skillMdBytes": 96379,
|
||||
"skillMdLines": 1877,
|
||||
"estTokens": 24095,
|
||||
"tmplBytes": 48624,
|
||||
"descriptionLen": 160,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"landing-report": {
|
||||
"skill": "landing-report",
|
||||
"skillMdBytes": 48478,
|
||||
"skillMdLines": 895,
|
||||
"estTokens": 12120,
|
||||
"tmplBytes": 6806,
|
||||
"descriptionLen": 195,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"learn": {
|
||||
"skill": "learn",
|
||||
"skillMdBytes": 46215,
|
||||
"skillMdLines": 912,
|
||||
"estTokens": 11554,
|
||||
"tmplBytes": 5594,
|
||||
"descriptionLen": 178,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"make-pdf": {
|
||||
"skill": "make-pdf",
|
||||
"skillMdBytes": 30270,
|
||||
"skillMdLines": 673,
|
||||
"estTokens": 7568,
|
||||
"tmplBytes": 5546,
|
||||
"descriptionLen": 177,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"office-hours": {
|
||||
"skill": "office-hours",
|
||||
"skillMdBytes": 123037,
|
||||
"skillMdLines": 2200,
|
||||
"estTokens": 30759,
|
||||
"tmplBytes": 55534,
|
||||
"descriptionLen": 860,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"open-gstack-browser": {
|
||||
"skill": "open-gstack-browser",
|
||||
"skillMdBytes": 50624,
|
||||
"skillMdLines": 975,
|
||||
"estTokens": 12656,
|
||||
"tmplBytes": 7702,
|
||||
"descriptionLen": 204,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"pair-agent": {
|
||||
"skill": "pair-agent",
|
||||
"skillMdBytes": 51432,
|
||||
"skillMdLines": 1031,
|
||||
"estTokens": 12858,
|
||||
"tmplBytes": 8548,
|
||||
"descriptionLen": 167,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"plan-ceo-review": {
|
||||
"skill": "plan-ceo-review",
|
||||
"skillMdBytes": 144411,
|
||||
"skillMdLines": 2349,
|
||||
"estTokens": 36103,
|
||||
"tmplBytes": 63461,
|
||||
"descriptionLen": 794,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
"plan-design-review": {
|
||||
"skill": "plan-design-review",
|
||||
"skillMdBytes": 118532,
|
||||
"skillMdLines": 2073,
|
||||
"estTokens": 29633,
|
||||
"tmplBytes": 28717,
|
||||
"descriptionLen": 218,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
"plan-devex-review": {
|
||||
"skill": "plan-devex-review",
|
||||
"skillMdBytes": 117907,
|
||||
"skillMdLines": 2277,
|
||||
"estTokens": 29477,
|
||||
"tmplBytes": 35773,
|
||||
"descriptionLen": 250,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
"plan-eng-review": {
|
||||
"skill": "plan-eng-review",
|
||||
"skillMdBytes": 114209,
|
||||
"skillMdLines": 1906,
|
||||
"estTokens": 28552,
|
||||
"tmplBytes": 26302,
|
||||
"descriptionLen": 231,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
"plan-tune": {
|
||||
"skill": "plan-tune",
|
||||
"skillMdBytes": 67548,
|
||||
"skillMdLines": 1372,
|
||||
"estTokens": 16887,
|
||||
"tmplBytes": 26922,
|
||||
"descriptionLen": 325,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"qa": {
|
||||
"skill": "qa",
|
||||
"skillMdBytes": 78356,
|
||||
"skillMdLines": 1643,
|
||||
"estTokens": 19589,
|
||||
"tmplBytes": 12701,
|
||||
"descriptionLen": 218,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"qa-only": {
|
||||
"skill": "qa-only",
|
||||
"skillMdBytes": 60914,
|
||||
"skillMdLines": 1215,
|
||||
"estTokens": 15229,
|
||||
"tmplBytes": 3851,
|
||||
"descriptionLen": 165,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"retro": {
|
||||
"skill": "retro",
|
||||
"skillMdBytes": 87382,
|
||||
"skillMdLines": 1771,
|
||||
"estTokens": 21846,
|
||||
"tmplBytes": 42427,
|
||||
"descriptionLen": 648,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"review": {
|
||||
"skill": "review",
|
||||
"skillMdBytes": 99573,
|
||||
"skillMdLines": 1787,
|
||||
"estTokens": 24893,
|
||||
"tmplBytes": 14099,
|
||||
"descriptionLen": 205,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"scrape": {
|
||||
"skill": "scrape",
|
||||
"skillMdBytes": 48134,
|
||||
"skillMdLines": 908,
|
||||
"estTokens": 12034,
|
||||
"tmplBytes": 5220,
|
||||
"descriptionLen": 167,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"setup-browser-cookies": {
|
||||
"skill": "setup-browser-cookies",
|
||||
"skillMdBytes": 26998,
|
||||
"skillMdLines": 597,
|
||||
"estTokens": 6750,
|
||||
"tmplBytes": 2724,
|
||||
"descriptionLen": 222,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"setup-deploy": {
|
||||
"skill": "setup-deploy",
|
||||
"skillMdBytes": 48420,
|
||||
"skillMdLines": 940,
|
||||
"estTokens": 12105,
|
||||
"tmplBytes": 7780,
|
||||
"descriptionLen": 197,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"setup-gbrain": {
|
||||
"skill": "setup-gbrain",
|
||||
"skillMdBytes": 85495,
|
||||
"skillMdLines": 1794,
|
||||
"estTokens": 21374,
|
||||
"tmplBytes": 44851,
|
||||
"descriptionLen": 323,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"ship": {
|
||||
"skill": "ship",
|
||||
"skillMdBytes": 174407,
|
||||
"skillMdLines": 3137,
|
||||
"estTokens": 43602,
|
||||
"tmplBytes": 53240,
|
||||
"descriptionLen": 291,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": true
|
||||
},
|
||||
"skillify": {
|
||||
"skill": "skillify",
|
||||
"skillMdBytes": 58027,
|
||||
"skillMdLines": 1189,
|
||||
"estTokens": 14507,
|
||||
"tmplBytes": 15107,
|
||||
"descriptionLen": 233,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"spec": {
|
||||
"skill": "spec",
|
||||
"skillMdBytes": 117382,
|
||||
"skillMdLines": 2276,
|
||||
"estTokens": 29346,
|
||||
"tmplBytes": 30590,
|
||||
"descriptionLen": 282,
|
||||
"hasGateEval": true,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"sync-gbrain": {
|
||||
"skill": "sync-gbrain",
|
||||
"skillMdBytes": 62977,
|
||||
"skillMdLines": 1191,
|
||||
"estTokens": 15744,
|
||||
"tmplBytes": 16077,
|
||||
"descriptionLen": 299,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
},
|
||||
"unfreeze": {
|
||||
"skill": "unfreeze",
|
||||
"skillMdBytes": 1504,
|
||||
"skillMdLines": 49,
|
||||
"estTokens": 376,
|
||||
"tmplBytes": 1386,
|
||||
"descriptionLen": 199,
|
||||
"hasGateEval": false,
|
||||
"hasPeriodicEval": false
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
# Plan: User Dashboard Page
|
||||
|
||||
## Context
|
||||
We're shipping a new user dashboard at `/dashboard` showing recent activity,
|
||||
notifications panel, and quick-action buttons. Users land here after login.
|
||||
|
||||
## UI Scope
|
||||
- New React page component `UserDashboard.tsx` at `src/pages/`
|
||||
- Three new sub-components: `ActivityFeed`, `NotificationsPanel`, `QuickActions`
|
||||
- Tailwind CSS for layout, mobile-first responsive (breakpoints: sm/md/lg)
|
||||
- Empty state, loading skeleton, error state for each panel
|
||||
- Hover states + focus-visible outlines on every interactive element
|
||||
- Modal dialog for "Mark all as read" on notifications panel
|
||||
- Toast notification system for action feedback
|
||||
|
||||
## Backend
|
||||
- New REST endpoint `GET /api/dashboard` returns `{ activity, notifications, quickActions }`
|
||||
- Backed by existing PostgreSQL tables; no schema changes
|
||||
|
||||
## Out of scope
|
||||
- Dark mode (separate plan)
|
||||
- Personalization / customization (separate plan)
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"fixture": "qa-eval-checkout.html",
|
||||
"bugs": [
|
||||
{
|
||||
"id": "broken-email-regex",
|
||||
"category": "functional",
|
||||
"severity": "high",
|
||||
"description": "Email validation accepts 'user@' as valid — regex pattern [^@]+@[^@] is missing domain requirement",
|
||||
"detection_hint": "email|regex|validation|accepts|invalid|user@|pattern"
|
||||
},
|
||||
{
|
||||
"id": "nan-total",
|
||||
"category": "functional",
|
||||
"severity": "high",
|
||||
"description": "Clearing the quantity field shows 'Total: $NaN' — parseInt on empty string returns NaN with no fallback",
|
||||
"detection_hint": "NaN|total|quantity|empty|price|calculation|clear"
|
||||
},
|
||||
{
|
||||
"id": "cc-field-overflow",
|
||||
"category": "visual",
|
||||
"severity": "medium",
|
||||
"description": "Credit card input has no maxlength attribute — entering >20 characters causes text to overflow the container",
|
||||
"detection_hint": "credit card|maxlength|overflow|cc|input|long|container"
|
||||
},
|
||||
{
|
||||
"id": "missing-required-zip",
|
||||
"category": "functional",
|
||||
"severity": "medium",
|
||||
"description": "Zip code field has no 'required' attribute — form can be submitted without a zip code",
|
||||
"detection_hint": "zip|required|missing|form|submit|shipping|postal"
|
||||
},
|
||||
{
|
||||
"id": "stripe-not-defined",
|
||||
"category": "console",
|
||||
"severity": "high",
|
||||
"description": "Form submit triggers 'Uncaught ReferenceError: stripe is not defined' — payment SDK not loaded",
|
||||
"detection_hint": "stripe|ReferenceError|not defined|console|error|submit|payment"
|
||||
}
|
||||
],
|
||||
"total_bugs": 5,
|
||||
"minimum_detection": 2,
|
||||
"max_false_positives": 5
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"fixture": "qa-eval.html",
|
||||
"bugs": [
|
||||
{
|
||||
"id": "broken-link",
|
||||
"category": "functional",
|
||||
"severity": "medium",
|
||||
"description": "Navigation link 'Resources' points to /nonexistent-404-page which returns 404",
|
||||
"detection_hint": "link|404|broken|dead|nonexistent|Resources"
|
||||
},
|
||||
{
|
||||
"id": "disabled-submit",
|
||||
"category": "functional",
|
||||
"severity": "high",
|
||||
"description": "Contact form submit button has 'disabled' attribute permanently — form can never be submitted",
|
||||
"detection_hint": "disabled|submit|button|form|cannot submit|contact"
|
||||
},
|
||||
{
|
||||
"id": "content-overflow",
|
||||
"category": "visual",
|
||||
"severity": "medium",
|
||||
"description": "Statistics text is clipped by overflow:hidden container — content wider than 400px container",
|
||||
"detection_hint": "overflow|clipped|truncated|hidden|text cut|statistics"
|
||||
},
|
||||
{
|
||||
"id": "missing-alt",
|
||||
"category": "accessibility",
|
||||
"severity": "medium",
|
||||
"description": "Logo image (<img src='/logo.png'>) has no alt attribute",
|
||||
"detection_hint": "alt|accessibility|image|a11y|missing alt|logo"
|
||||
},
|
||||
{
|
||||
"id": "console-error",
|
||||
"category": "console",
|
||||
"severity": "high",
|
||||
"description": "TypeError on page load: Cannot read properties of undefined (reading 'map')",
|
||||
"detection_hint": "console|error|TypeError|undefined|map"
|
||||
}
|
||||
],
|
||||
"total_bugs": 5,
|
||||
"minimum_detection": 2,
|
||||
"max_false_positives": 5
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"fixture": "qa-eval-spa.html",
|
||||
"bugs": [
|
||||
{
|
||||
"id": "broken-route",
|
||||
"category": "functional",
|
||||
"severity": "high",
|
||||
"description": "Products nav link points to #/prodcts (typo) instead of #/products — shows 'Page not found'",
|
||||
"detection_hint": "route|prodcts|typo|products|not found|broken link|navigation"
|
||||
},
|
||||
{
|
||||
"id": "stale-cart-state",
|
||||
"category": "functional",
|
||||
"severity": "medium",
|
||||
"description": "Cart count persists across route changes — never resets when navigating away from products",
|
||||
"detection_hint": "cart|count|state|persist|reset|stale|navigation"
|
||||
},
|
||||
{
|
||||
"id": "async-fetch-error",
|
||||
"category": "functional",
|
||||
"severity": "high",
|
||||
"description": "Product list briefly loads then shows 'Error: Failed to fetch products from API' after 1 second",
|
||||
"detection_hint": "error|fetch|products|API|loading|failed|async"
|
||||
},
|
||||
{
|
||||
"id": "missing-aria-current",
|
||||
"category": "accessibility",
|
||||
"severity": "medium",
|
||||
"description": "Navigation links have no aria-current attribute to indicate the active route",
|
||||
"detection_hint": "aria|current|active|navigation|accessibility|a11y"
|
||||
},
|
||||
{
|
||||
"id": "console-warn-leak",
|
||||
"category": "console",
|
||||
"severity": "medium",
|
||||
"description": "console.warn fires on every route change: 'Possible memory leak detected: 11 event listeners'",
|
||||
"detection_hint": "console|warn|memory leak|listener|event|warning"
|
||||
}
|
||||
],
|
||||
"total_bugs": 5,
|
||||
"minimum_detection": 2,
|
||||
"max_false_positives": 5
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
-- Migration: Drop user email column
|
||||
-- WARNING: This migration is intentionally unsafe for testing
|
||||
ALTER TABLE users DROP COLUMN email;
|
||||
ALTER TABLE users DROP COLUMN phone_number;
|
||||
-- No backfill, no reversibility check, no data preservation
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
# N+1 query example — intentionally bad for testing
|
||||
class PostsController
|
||||
def index
|
||||
@posts = Post.all
|
||||
@posts.each do |post|
|
||||
# N+1: queries Author table for every post
|
||||
puts post.author.name
|
||||
# N+1: queries Comments table for every post
|
||||
puts post.comments.count
|
||||
end
|
||||
end
|
||||
end
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
/* Planted design anti-patterns for E2E eval — 7 issues */
|
||||
|
||||
/* Issue 1: [HIGH] Blacklisted font (Papyrus) */
|
||||
/* Issue 2: [HIGH] Body text < 16px (14px) */
|
||||
body {
|
||||
font-family: 'Papyrus', sans-serif;
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Issue 5: [MEDIUM] Purple/violet gradient background */
|
||||
.hero {
|
||||
background: linear-gradient(135deg, #6366f1, #8b5cf6);
|
||||
text-align: center;
|
||||
padding: 80px 20px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
text-align: center;
|
||||
font-size: 48px;
|
||||
}
|
||||
|
||||
.hero p {
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
/* Issue 7: [LOW] 3-column feature grid with icon circles */
|
||||
.features {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 24px;
|
||||
padding: 60px 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
border-radius: 24px;
|
||||
padding: 32px;
|
||||
text-align: center;
|
||||
background: #f9fafb;
|
||||
}
|
||||
|
||||
/* Icon in colored circle — AI slop pattern */
|
||||
.icon-circle {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
background: #ede9fe;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto 16px;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
/* Issue 3: [HIGH] outline: none without replacement */
|
||||
button {
|
||||
outline: none;
|
||||
background: #6366f1;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 24px;
|
||||
border-radius: 24px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.small-link {
|
||||
font-size: 11px;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
/* Issue 4: [HIGH] !important usage */
|
||||
.override {
|
||||
color: red !important;
|
||||
margin-left: 10px !important;
|
||||
}
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
background: #1e1b4b;
|
||||
color: white;
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<title>Our Platform</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Issue 6: [MEDIUM] Generic hero copy ("Welcome to...", "all-in-one solution") -->
|
||||
<div class="hero">
|
||||
<h1>Welcome to Our Platform</h1>
|
||||
<p>Your all-in-one solution for everything you need</p>
|
||||
<button>Get Started</button>
|
||||
</div>
|
||||
|
||||
<!-- Issue 7: [LOW] 3-column feature grid with icon-in-circle + title + description -->
|
||||
<div class="features">
|
||||
<div class="feature-card">
|
||||
<div class="icon-circle">★</div>
|
||||
<h3>Feature One</h3>
|
||||
<p>A short description of this amazing feature that will change your life.</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="icon-circle">⚡</div>
|
||||
<h3>Feature Two</h3>
|
||||
<p>Another incredible capability that sets us apart from the competition.</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="icon-circle">⚙</div>
|
||||
<h3>Feature Three</h3>
|
||||
<p>Yet another powerful tool to streamline your workflow effortlessly.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p class="override">Unlock the power of our platform today</p>
|
||||
<a href="#" class="small-link">Terms of Service</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
# Feature branch version: adds "returned" status but misses consumers
|
||||
class Order < ApplicationRecord
|
||||
STATUSES = %w[pending processing shipped delivered returned].freeze
|
||||
|
||||
validates :status, inclusion: { in: STATUSES }
|
||||
|
||||
def display_status
|
||||
case status
|
||||
when 'pending' then 'Awaiting processing'
|
||||
when 'processing' then 'Being prepared'
|
||||
when 'shipped' then 'On the way'
|
||||
when 'delivered' then 'Delivered'
|
||||
# BUG: 'returned' not handled — falls through to nil
|
||||
end
|
||||
end
|
||||
|
||||
def can_cancel?
|
||||
# BUG: should 'returned' be cancellable? Not considered.
|
||||
%w[pending processing].include?(status)
|
||||
end
|
||||
|
||||
def notify_customer
|
||||
case status
|
||||
when 'pending' then OrderMailer.confirmation(self).deliver_later
|
||||
when 'shipped' then OrderMailer.shipped(self).deliver_later
|
||||
when 'delivered' then OrderMailer.delivered(self).deliver_later
|
||||
# BUG: 'returned' has no notification — customer won't know return was received
|
||||
end
|
||||
end
|
||||
end
|
||||
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
# Existing file on main: order model with status handling
|
||||
class Order < ApplicationRecord
|
||||
STATUSES = %w[pending processing shipped delivered].freeze
|
||||
|
||||
validates :status, inclusion: { in: STATUSES }
|
||||
|
||||
def display_status
|
||||
case status
|
||||
when 'pending' then 'Awaiting processing'
|
||||
when 'processing' then 'Being prepared'
|
||||
when 'shipped' then 'On the way'
|
||||
when 'delivered' then 'Delivered'
|
||||
end
|
||||
end
|
||||
|
||||
def can_cancel?
|
||||
%w[pending processing].include?(status)
|
||||
end
|
||||
|
||||
def notify_customer
|
||||
case status
|
||||
when 'pending' then OrderMailer.confirmation(self).deliver_later
|
||||
when 'shipped' then OrderMailer.shipped(self).deliver_later
|
||||
when 'delivered' then OrderMailer.delivered(self).deliver_later
|
||||
end
|
||||
end
|
||||
end
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
class UserController < ApplicationController
|
||||
def show
|
||||
# SQL injection — interpolating user input directly into query
|
||||
@user = User.where("id = #{params[:id]}").first
|
||||
render json: @user
|
||||
end
|
||||
|
||||
def promote
|
||||
# Bypasses ActiveRecord validations — update_column skips callbacks + validation
|
||||
@user = User.find(params[:id])
|
||||
@user.update_column(:role, 'admin')
|
||||
head :ok
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user