@cobusgreyling/loop-cost 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,44 @@
1
+ # loop-cost
2
+
3
+ Estimate daily token spend for [loop engineering](https://github.com/cobusgreyling/loop-engineering) patterns by cadence and readiness level (L1–L3).
4
+
5
+ Uses cost metadata from `patterns/registry.yaml`.
6
+
7
+ ## Install & Run
8
+
9
+ ```bash
10
+ npx @cobusgreyling/loop-cost --pattern ci-sweeper --cadence 15m --level L2
11
+ npx @cobusgreyling/loop-cost --pattern daily-triage --level L1 --json
12
+ npx @cobusgreyling/loop-cost --list
13
+ ```
14
+
15
+ **From this repo:**
16
+
17
+ ```bash
18
+ cd tools/loop-cost
19
+ npm install
20
+ npm test
21
+ ```
22
+
23
+ ## Options
24
+
25
+ | Flag | Description |
26
+ |------|-------------|
27
+ | `--pattern` | Pattern id (see `--list`) |
28
+ | `--cadence` | Override cadence (e.g. `15m`, `1d`) |
29
+ | `--level` | `L1`, `L2`, or `L3` (default `L1`) |
30
+ | `--conservative` | Use slower cadence from ranges |
31
+ | `--json` | Machine-readable output |
32
+
33
+ ## Scenarios
34
+
35
+ Each estimate includes:
36
+
37
+ - **Early-exit / no-op** — empty watchlist, minimal tokens
38
+ - **Full triage** — every run does a full scan
39
+ - **Action every run** — implementer + verifier every time (worst case)
40
+ - **Realistic blend** — level-based mix (documented in output)
41
+
42
+ Pair with `loop-budget.md` (scaffolded by `loop-init`) and `loop-audit` cost observability checks.
43
+
44
+ See [docs/operating-loops.md](../../docs/operating-loops.md).
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env node
2
+ import { readFile, access } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import yaml from 'yaml';
6
+ import { estimateCost, formatEstimateHuman, } from './estimator.js';
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+ const PACKAGE_ROOT = path.resolve(__dirname, '..');
9
+ function parseArgs(argv) {
10
+ let pattern = 'daily-triage';
11
+ let cadence;
12
+ let level = 'L1';
13
+ let conservative = false;
14
+ let json = false;
15
+ let list = false;
16
+ for (let i = 0; i < argv.length; i++) {
17
+ const a = argv[i];
18
+ if (a === '--pattern' || a === '-p')
19
+ pattern = argv[++i];
20
+ else if (a === '--cadence' || a === '-c')
21
+ cadence = argv[++i];
22
+ else if (a === '--level' || a === '-l')
23
+ level = argv[++i];
24
+ else if (a === '--conservative')
25
+ conservative = true;
26
+ else if (a === '--json')
27
+ json = true;
28
+ else if (a === '--list')
29
+ list = true;
30
+ else if (a === '--help' || a === '-h')
31
+ return { help: true };
32
+ }
33
+ return { help: false, pattern, cadence, level, conservative, json, list };
34
+ }
35
+ async function loadRegistry() {
36
+ const candidates = [
37
+ path.join(PACKAGE_ROOT, 'registry.json'),
38
+ path.resolve(PACKAGE_ROOT, '../../patterns/registry.yaml'),
39
+ ];
40
+ for (const p of candidates) {
41
+ try {
42
+ await access(p);
43
+ const raw = await readFile(p, 'utf8');
44
+ if (p.endsWith('.json'))
45
+ return JSON.parse(raw);
46
+ return yaml.parse(raw);
47
+ }
48
+ catch {
49
+ /* try next */
50
+ }
51
+ }
52
+ throw new Error('Pattern registry not found. Run from loop-engineering repo or install @cobusgreyling/loop-cost.');
53
+ }
54
+ async function main() {
55
+ const args = parseArgs(process.argv.slice(2));
56
+ if (args.help) {
57
+ console.log(`loop-cost — estimate daily token spend for loop patterns
58
+
59
+ Usage:
60
+ loop-cost --pattern <id> [options]
61
+
62
+ Options:
63
+ -p, --pattern <id> Pattern id (default: daily-triage)
64
+ -c, --cadence <spec> Override cadence (e.g. 15m, 1d, 5m-15m)
65
+ -l, --level <L1|L2|L3> Readiness level (default: L1)
66
+ --conservative Use slower cadence from ranges (e.g. 15m not 5m)
67
+ --json Machine-readable output
68
+ --list List pattern ids
69
+ -h, --help This help
70
+
71
+ Examples:
72
+ loop-cost --pattern ci-sweeper --cadence 15m --level L2
73
+ loop-cost --pattern daily-triage --level L1 --json
74
+ loop-cost --list
75
+ `);
76
+ process.exit(0);
77
+ }
78
+ const registry = await loadRegistry();
79
+ if (args.list) {
80
+ for (const p of registry.patterns) {
81
+ console.log(`${p.id}\t${p.token_cost}\t${p.cadence}`);
82
+ }
83
+ return;
84
+ }
85
+ const pattern = registry.patterns.find((p) => p.id === args.pattern);
86
+ if (!pattern) {
87
+ console.error(`Unknown pattern: ${args.pattern}. Use --list for ids.`);
88
+ process.exit(1);
89
+ }
90
+ if (!pattern.cost) {
91
+ console.error(`Pattern ${args.pattern} has no cost block in registry.`);
92
+ process.exit(1);
93
+ }
94
+ const result = estimateCost({
95
+ pattern,
96
+ cadence: args.cadence,
97
+ level: args.level,
98
+ conservative: args.conservative,
99
+ });
100
+ if (args.json)
101
+ console.log(JSON.stringify(result, null, 2));
102
+ else
103
+ console.log(formatEstimateHuman(result));
104
+ }
105
+ main().catch((err) => {
106
+ const msg = err instanceof Error ? err.message : String(err);
107
+ console.error('loop-cost failed:', msg);
108
+ process.exit(1);
109
+ });
@@ -0,0 +1,64 @@
1
+ export type ReadinessLevel = 'L1' | 'L2' | 'L3';
2
+ export interface PatternCost {
3
+ tokens_noop: number;
4
+ tokens_report: number;
5
+ tokens_action: number;
6
+ suggested_daily_cap: number;
7
+ early_exit_required: boolean;
8
+ }
9
+ export interface RegistryPattern {
10
+ id: string;
11
+ name: string;
12
+ cadence: string;
13
+ token_cost: string;
14
+ cost: PatternCost;
15
+ }
16
+ export interface RegistryDoc {
17
+ patterns: RegistryPattern[];
18
+ }
19
+ export interface EstimateInput {
20
+ pattern: RegistryPattern;
21
+ cadence?: string;
22
+ level: ReadinessLevel;
23
+ conservative?: boolean;
24
+ }
25
+ export interface EstimateResult {
26
+ patternId: string;
27
+ patternName: string;
28
+ cadence: string;
29
+ level: ReadinessLevel;
30
+ runsPerDay: number;
31
+ tokenCostTier: string;
32
+ suggestedDailyCap: number;
33
+ earlyExitRequired: boolean;
34
+ scenarios: {
35
+ noop: {
36
+ tokensPerRun: number;
37
+ tokensPerDay: number;
38
+ };
39
+ report: {
40
+ tokensPerRun: number;
41
+ tokensPerDay: number;
42
+ };
43
+ action: {
44
+ tokensPerRun: number;
45
+ tokensPerDay: number;
46
+ };
47
+ realistic: {
48
+ tokensPerRun: number;
49
+ tokensPerDay: number;
50
+ assumptions: string;
51
+ };
52
+ };
53
+ warnings: string[];
54
+ }
55
+ export declare function parseInterval(token: string): number;
56
+ /** Runs per day for a single interval like 15m or 1d. */
57
+ export declare function runsPerDayForInterval(interval: string): number;
58
+ /**
59
+ * Parse pattern cadence (e.g. 5m-15m, 1d-2h) into runs/day.
60
+ * conservative=true picks the slower cadence in a range.
61
+ */
62
+ export declare function cadenceToRunsPerDay(cadence: string, conservative?: boolean): number;
63
+ export declare function estimateCost(input: EstimateInput): EstimateResult;
64
+ export declare function formatEstimateHuman(r: EstimateResult): string;
@@ -0,0 +1,135 @@
1
+ const INTERVAL_MS = {
2
+ m: 60_000,
3
+ h: 3_600_000,
4
+ d: 86_400_000,
5
+ };
6
+ export function parseInterval(token) {
7
+ const m = token.match(/^(\d+)([mhd])$/);
8
+ if (!m)
9
+ throw new Error(`Invalid cadence interval: ${token}`);
10
+ const unit = m[2];
11
+ return Number(m[1]) * INTERVAL_MS[unit];
12
+ }
13
+ /** Runs per day for a single interval like 15m or 1d. */
14
+ export function runsPerDayForInterval(interval) {
15
+ const ms = parseInterval(interval);
16
+ return Math.floor(86_400_000 / ms);
17
+ }
18
+ /**
19
+ * Parse pattern cadence (e.g. 5m-15m, 1d-2h) into runs/day.
20
+ * conservative=true picks the slower cadence in a range.
21
+ */
22
+ export function cadenceToRunsPerDay(cadence, conservative = false) {
23
+ const parts = cadence.split('-').map((p) => p.trim());
24
+ if (parts.length === 1)
25
+ return runsPerDayForInterval(parts[0]);
26
+ const runs = parts.map(runsPerDayForInterval);
27
+ return conservative ? Math.min(...runs) : Math.max(...runs);
28
+ }
29
+ function realisticMix(level, earlyExitRequired) {
30
+ if (level === 'L1') {
31
+ return {
32
+ noop: earlyExitRequired ? 0.9 : 0.6,
33
+ report: earlyExitRequired ? 0.1 : 0.4,
34
+ action: 0,
35
+ assumptions: earlyExitRequired
36
+ ? 'L1: 90% early-exit, 10% full triage'
37
+ : 'L1: 60% no-op, 40% full triage',
38
+ };
39
+ }
40
+ if (level === 'L2') {
41
+ return {
42
+ noop: earlyExitRequired ? 0.85 : 0.5,
43
+ report: earlyExitRequired ? 0.1 : 0.3,
44
+ action: earlyExitRequired ? 0.05 : 0.2,
45
+ assumptions: earlyExitRequired
46
+ ? 'L2: 85% early-exit, 10% triage, 5% implementer+verifier'
47
+ : 'L2: 50% no-op, 30% triage, 20% action',
48
+ };
49
+ }
50
+ return {
51
+ noop: 0.4,
52
+ report: 0.35,
53
+ action: 0.25,
54
+ assumptions: 'L3: 40% no-op, 35% triage, 25% action (unattended — monitor closely)',
55
+ };
56
+ }
57
+ function formatTokens(n) {
58
+ if (n >= 1_000_000)
59
+ return `${(n / 1_000_000).toFixed(1)}M`;
60
+ if (n >= 1_000)
61
+ return `${Math.round(n / 1_000)}k`;
62
+ return String(n);
63
+ }
64
+ export function estimateCost(input) {
65
+ const cadence = input.cadence ?? input.pattern.cadence;
66
+ const runsPerDay = cadenceToRunsPerDay(cadence, input.conservative);
67
+ const { cost, token_cost: tokenCostTier } = input.pattern;
68
+ const mix = realisticMix(input.level, cost.early_exit_required);
69
+ const noopDay = cost.tokens_noop * runsPerDay;
70
+ const reportDay = cost.tokens_report * runsPerDay;
71
+ const actionDay = cost.tokens_action * runsPerDay;
72
+ const realisticPerRun = cost.tokens_noop * mix.noop +
73
+ cost.tokens_report * mix.report +
74
+ cost.tokens_action * mix.action;
75
+ const realisticDay = Math.round(realisticPerRun * runsPerDay);
76
+ const warnings = [];
77
+ if (cost.early_exit_required) {
78
+ warnings.push('Early-exit triage is required — empty watchlist should exit in <5k tokens.');
79
+ }
80
+ if (actionDay > cost.suggested_daily_cap) {
81
+ warnings.push(`Worst case (action every run) exceeds suggested cap (${formatTokens(cost.suggested_daily_cap)}/day).`);
82
+ }
83
+ if (realisticDay > cost.suggested_daily_cap) {
84
+ warnings.push(`Realistic estimate exceeds suggested daily cap — slow cadence or tighten scope.`);
85
+ }
86
+ if (runsPerDay >= 96) {
87
+ warnings.push(`High cadence (${runsPerDay} runs/day) — verify early-exit is working.`);
88
+ }
89
+ return {
90
+ patternId: input.pattern.id,
91
+ patternName: input.pattern.name,
92
+ cadence,
93
+ level: input.level,
94
+ runsPerDay,
95
+ tokenCostTier,
96
+ suggestedDailyCap: cost.suggested_daily_cap,
97
+ earlyExitRequired: cost.early_exit_required,
98
+ scenarios: {
99
+ noop: { tokensPerRun: cost.tokens_noop, tokensPerDay: noopDay },
100
+ report: { tokensPerRun: cost.tokens_report, tokensPerDay: reportDay },
101
+ action: { tokensPerRun: cost.tokens_action, tokensPerDay: actionDay },
102
+ realistic: {
103
+ tokensPerRun: Math.round(realisticPerRun),
104
+ tokensPerDay: realisticDay,
105
+ assumptions: mix.assumptions,
106
+ },
107
+ },
108
+ warnings,
109
+ };
110
+ }
111
+ export function formatEstimateHuman(r) {
112
+ const lines = [];
113
+ lines.push('');
114
+ lines.push(`Loop Cost Estimate — ${r.patternName} (${r.patternId})`);
115
+ lines.push('═'.repeat(50));
116
+ lines.push(`Cadence: ${r.cadence} → ${r.runsPerDay} runs/day`);
117
+ lines.push(`Level: ${r.level} · Registry tier: ${r.tokenCostTier}`);
118
+ lines.push(`Suggested daily cap: ${formatTokens(r.suggestedDailyCap)} tokens`);
119
+ lines.push('');
120
+ lines.push('Daily token estimates:');
121
+ lines.push(` Early-exit / no-op: ${formatTokens(r.scenarios.noop.tokensPerDay)} (${formatTokens(r.scenarios.noop.tokensPerRun)}/run)`);
122
+ lines.push(` Full triage: ${formatTokens(r.scenarios.report.tokensPerDay)} (${formatTokens(r.scenarios.report.tokensPerRun)}/run)`);
123
+ lines.push(` Action every run: ${formatTokens(r.scenarios.action.tokensPerDay)} (${formatTokens(r.scenarios.action.tokensPerRun)}/run)`);
124
+ lines.push(` Realistic blend: ${formatTokens(r.scenarios.realistic.tokensPerDay)} (${r.scenarios.realistic.assumptions})`);
125
+ if (r.warnings.length) {
126
+ lines.push('');
127
+ lines.push('Warnings:');
128
+ for (const w of r.warnings)
129
+ lines.push(` ! ${w}`);
130
+ }
131
+ lines.push('');
132
+ lines.push('Docs: docs/operating-loops.md · Scaffold: npx @cobusgreyling/loop-init');
133
+ lines.push('');
134
+ return lines.join('\n');
135
+ }
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@cobusgreyling/loop-cost",
3
+ "version": "1.0.0",
4
+ "description": "Estimate daily token spend for loop engineering patterns by cadence and readiness level.",
5
+ "type": "module",
6
+ "bin": {
7
+ "loop-cost": "./dist/cli.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "registry.json",
12
+ "README.md"
13
+ ],
14
+ "scripts": {
15
+ "bundle": "node scripts/bundle-registry.mjs",
16
+ "build": "npm run bundle && tsc",
17
+ "test": "npm run build && node --test test/estimator.test.mjs",
18
+ "prepublishOnly": "npm test",
19
+ "start": "node dist/cli.js"
20
+ },
21
+ "engines": {
22
+ "node": ">=18"
23
+ },
24
+ "keywords": [
25
+ "loop-engineering",
26
+ "ai-agents",
27
+ "token-budget",
28
+ "cost-estimate"
29
+ ],
30
+ "author": "Cobus Greyling",
31
+ "license": "MIT",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/cobusgreyling/loop-engineering.git",
35
+ "directory": "tools/loop-cost"
36
+ },
37
+ "homepage": "https://cobusgreyling.github.io/loop-engineering/",
38
+ "bugs": {
39
+ "url": "https://github.com/cobusgreyling/loop-engineering/issues"
40
+ },
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
44
+ "dependencies": {
45
+ "yaml": "^2.8.0"
46
+ },
47
+ "devDependencies": {
48
+ "@types/node": "^20.0.0",
49
+ "typescript": "^5.0.0"
50
+ }
51
+ }
package/registry.json ADDED
@@ -0,0 +1,294 @@
1
+ {
2
+ "patterns": [
3
+ {
4
+ "id": "pr-babysitter",
5
+ "name": "PR Babysitter",
6
+ "file": "pr-babysitter.md",
7
+ "goal": "Shepherd PRs through review, CI, rebase, and merge",
8
+ "cadence": "5m-15m",
9
+ "risk": "medium",
10
+ "tools": [
11
+ "grok",
12
+ "claude-code",
13
+ "codex",
14
+ "github-actions"
15
+ ],
16
+ "skills": [
17
+ "pr-review-triage",
18
+ "minimal-fix",
19
+ "rebase-and-clean"
20
+ ],
21
+ "state": "pr-babysitter-state.md",
22
+ "phases": [
23
+ "discover",
24
+ "triage",
25
+ "fix",
26
+ "verify",
27
+ "notify"
28
+ ],
29
+ "human_gates": [
30
+ "security",
31
+ "payments",
32
+ "auth",
33
+ "max-fix-attempts"
34
+ ],
35
+ "starter": "starters/pr-babysitter",
36
+ "week_one_mode": "L1",
37
+ "token_cost": "high",
38
+ "cost": {
39
+ "tokens_noop": 3000,
40
+ "tokens_report": 80000,
41
+ "tokens_action": 250000,
42
+ "suggested_daily_cap": 2000000,
43
+ "early_exit_required": true
44
+ }
45
+ },
46
+ {
47
+ "id": "daily-triage",
48
+ "name": "Daily Triage",
49
+ "file": "daily-triage.md",
50
+ "goal": "Prioritized morning scan of CI, issues, commits, and chat",
51
+ "cadence": "1d-2h",
52
+ "risk": "low",
53
+ "tools": [
54
+ "grok",
55
+ "claude-code",
56
+ "codex",
57
+ "github-actions"
58
+ ],
59
+ "skills": [
60
+ "loop-triage",
61
+ "minimal-fix"
62
+ ],
63
+ "state": "STATE.md",
64
+ "phases": [
65
+ "report",
66
+ "act-small-wins",
67
+ "escalate"
68
+ ],
69
+ "human_gates": [
70
+ "design-decisions",
71
+ "multi-file-refactors"
72
+ ],
73
+ "starter": "starters/minimal-loop",
74
+ "week_one_mode": "L1",
75
+ "token_cost": "low",
76
+ "cost": {
77
+ "tokens_noop": 5000,
78
+ "tokens_report": 50000,
79
+ "tokens_action": 200000,
80
+ "suggested_daily_cap": 100000,
81
+ "early_exit_required": false
82
+ }
83
+ },
84
+ {
85
+ "id": "ci-sweeper",
86
+ "name": "CI Sweeper",
87
+ "file": "ci-sweeper.md",
88
+ "goal": "React to failing CI with minimal fixes and escalation",
89
+ "cadence": "5m-15m",
90
+ "risk": "medium",
91
+ "tools": [
92
+ "grok",
93
+ "claude-code",
94
+ "codex",
95
+ "github-actions"
96
+ ],
97
+ "skills": [
98
+ "ci-triage",
99
+ "minimal-fix"
100
+ ],
101
+ "state": "ci-sweeper-state.md",
102
+ "phases": [
103
+ "detect",
104
+ "classify",
105
+ "fix",
106
+ "verify",
107
+ "escalate"
108
+ ],
109
+ "human_gates": [
110
+ "infra-failures",
111
+ "max-attempts",
112
+ "security-tests"
113
+ ],
114
+ "starter": "starters/ci-sweeper",
115
+ "week_one_mode": "L2",
116
+ "token_cost": "very-high",
117
+ "cost": {
118
+ "tokens_noop": 5000,
119
+ "tokens_report": 50000,
120
+ "tokens_action": 200000,
121
+ "suggested_daily_cap": 1000000,
122
+ "early_exit_required": true
123
+ }
124
+ },
125
+ {
126
+ "id": "post-merge-cleanup",
127
+ "name": "Post-Merge Cleanup",
128
+ "file": "post-merge-cleanup.md",
129
+ "goal": "Follow-up tech debt and cleanup after merges to main",
130
+ "cadence": "1d-6h",
131
+ "risk": "low",
132
+ "tools": [
133
+ "grok",
134
+ "claude-code",
135
+ "codex",
136
+ "github-actions"
137
+ ],
138
+ "skills": [
139
+ "post-merge-scan",
140
+ "minimal-fix"
141
+ ],
142
+ "state": "post-merge-state.md",
143
+ "phases": [
144
+ "scan-merges",
145
+ "prioritize",
146
+ "fix-small",
147
+ "ticket-large"
148
+ ],
149
+ "human_gates": [
150
+ "architectural-debt",
151
+ "feature-flags",
152
+ "large-diffs"
153
+ ],
154
+ "starter": "starters/post-merge-cleanup",
155
+ "week_one_mode": "L1",
156
+ "token_cost": "low",
157
+ "cost": {
158
+ "tokens_noop": 5000,
159
+ "tokens_report": 40000,
160
+ "tokens_action": 150000,
161
+ "suggested_daily_cap": 200000,
162
+ "early_exit_required": false
163
+ }
164
+ },
165
+ {
166
+ "id": "dependency-sweeper",
167
+ "name": "Dependency Sweeper",
168
+ "file": "dependency-sweeper.md",
169
+ "goal": "Discover, safely apply, and verify dependency + vulnerability updates with human gates on risky changes",
170
+ "cadence": "6h-1d",
171
+ "risk": "medium",
172
+ "tools": [
173
+ "grok",
174
+ "claude-code",
175
+ "codex",
176
+ "github-actions"
177
+ ],
178
+ "skills": [
179
+ "dependency-triage",
180
+ "minimal-fix",
181
+ "loop-verifier"
182
+ ],
183
+ "state": "dependency-sweeper-state.md",
184
+ "phases": [
185
+ "scan",
186
+ "triage-risk",
187
+ "patch-safe",
188
+ "verify-worktree",
189
+ "escalate-risky"
190
+ ],
191
+ "human_gates": [
192
+ "major-bumps",
193
+ "high-sev-cve",
194
+ "denylisted-packages",
195
+ "max-attempts"
196
+ ],
197
+ "starter": "starters/dependency-sweeper",
198
+ "week_one_mode": "L2",
199
+ "token_cost": "medium",
200
+ "cost": {
201
+ "tokens_noop": 5000,
202
+ "tokens_report": 60000,
203
+ "tokens_action": 300000,
204
+ "suggested_daily_cap": 500000,
205
+ "early_exit_required": true
206
+ }
207
+ },
208
+ {
209
+ "id": "changelog-drafter",
210
+ "name": "Changelog Drafter",
211
+ "file": "changelog-drafter.md",
212
+ "goal": "Scan merged PRs and commits, draft categorized high-quality release notes or CHANGELOG entries for human review",
213
+ "cadence": "1d",
214
+ "risk": "low",
215
+ "tools": [
216
+ "grok",
217
+ "claude-code",
218
+ "codex",
219
+ "github-actions"
220
+ ],
221
+ "skills": [
222
+ "changelog-scan",
223
+ "draft-release-notes",
224
+ "loop-verifier"
225
+ ],
226
+ "state": "changelog-drafter-state.md",
227
+ "phases": [
228
+ "scan-merges",
229
+ "categorize",
230
+ "draft",
231
+ "review",
232
+ "publish"
233
+ ],
234
+ "human_gates": [
235
+ "breaking-changes",
236
+ "security",
237
+ "major-features",
238
+ "marketing-sensitive"
239
+ ],
240
+ "starter": "starters/changelog-drafter",
241
+ "week_one_mode": "L1",
242
+ "token_cost": "low",
243
+ "cost": {
244
+ "tokens_noop": 5000,
245
+ "tokens_report": 35000,
246
+ "tokens_action": 80000,
247
+ "suggested_daily_cap": 100000,
248
+ "early_exit_required": false
249
+ }
250
+ },
251
+ {
252
+ "id": "issue-triage",
253
+ "name": "Issue Triage",
254
+ "file": "issue-triage.md",
255
+ "goal": "Discover, deduplicate, prioritize and label incoming issues/discussions so the team always has a clean actionable queue. Excellent low-risk companion to Daily Triage.",
256
+ "cadence": "2h-1d",
257
+ "risk": "low",
258
+ "tools": [
259
+ "grok",
260
+ "claude-code",
261
+ "codex",
262
+ "github-actions"
263
+ ],
264
+ "skills": [
265
+ "issue-triage",
266
+ "loop-verifier"
267
+ ],
268
+ "state": "issue-triage-state.md",
269
+ "phases": [
270
+ "discover",
271
+ "dedupe",
272
+ "score",
273
+ "propose-labels",
274
+ "human-review"
275
+ ],
276
+ "human_gates": [
277
+ "security",
278
+ "p0-p1",
279
+ "ambiguous-duplicates",
280
+ "stale-closures"
281
+ ],
282
+ "starter": "starters/minimal-loop",
283
+ "week_one_mode": "L1",
284
+ "token_cost": "low",
285
+ "cost": {
286
+ "tokens_noop": 3000,
287
+ "tokens_report": 30000,
288
+ "tokens_action": 60000,
289
+ "suggested_daily_cap": 80000,
290
+ "early_exit_required": false
291
+ }
292
+ }
293
+ ]
294
+ }