@event4u/agent-config 1.21.0 → 1.23.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/.agent-src/commands/agents/cleanup.md +31 -17
- package/.agent-src/commands/bug-fix.md +1 -0
- package/.agent-src/commands/bug-investigate.md +1 -0
- package/.agent-src/commands/challenge-me/vision.md +348 -0
- package/.agent-src/commands/challenge-me/with-docs.md +333 -0
- package/.agent-src/commands/challenge-me.md +61 -0
- package/.agent-src/commands/commit/in-chunks.md +30 -10
- package/.agent-src/commands/commit.md +46 -6
- package/.agent-src/commands/compress.md +19 -13
- package/.agent-src/commands/cost-report.md +120 -0
- package/.agent-src/commands/council/default.md +64 -17
- package/.agent-src/commands/create-pr/description-only.md +8 -0
- package/.agent-src/commands/create-pr.md +99 -80
- package/.agent-src/commands/feature/plan.md +13 -7
- package/.agent-src/commands/grill-me.md +38 -0
- package/.agent-src/commands/judge/steps.md +1 -1
- package/.agent-src/commands/memory/add.md +16 -8
- package/.agent-src/commands/memory/promote.md +17 -9
- package/.agent-src/commands/optimize/rtk.md +16 -11
- package/.agent-src/commands/prepare-for-review.md +12 -6
- package/.agent-src/commands/project-analyze.md +31 -20
- package/.agent-src/commands/review-changes.md +24 -15
- package/.agent-src/commands/roadmap/ai-council.md +183 -0
- package/.agent-src/commands/roadmap/create.md +20 -10
- package/.agent-src/commands/roadmap/process-full.md +58 -0
- package/.agent-src/commands/roadmap/process-phase.md +69 -0
- package/.agent-src/commands/roadmap/process-step.md +57 -0
- package/.agent-src/commands/roadmap.md +44 -16
- package/.agent-src/commands/threat-model.md +1 -0
- package/.agent-src/contexts/augment-infrastructure.md +1 -1
- package/.agent-src/contexts/communication/rules-auto/slash-command-routing-policy-mechanics.md +53 -18
- package/.agent-src/contexts/contracts/frugality-charter.md +57 -0
- package/.agent-src/contexts/execution/roadmap-process-loop.md +125 -0
- package/.agent-src/contexts/skills-and-commands.md +1 -1
- package/.agent-src/rules/architecture.md +9 -0
- package/.agent-src/rules/ask-when-uncertain.md +3 -13
- package/.agent-src/rules/caveman-speak.md +78 -0
- package/.agent-src/rules/direct-answers.md +5 -14
- package/.agent-src/rules/improve-before-implement.md +1 -0
- package/.agent-src/rules/invite-challenge.md +71 -0
- package/.agent-src/rules/markdown-safe-codeblocks.md +6 -7
- package/.agent-src/rules/no-cheap-questions.md +4 -14
- package/.agent-src/rules/token-efficiency.md +5 -7
- package/.agent-src/skills/adr-create/SKILL.md +197 -0
- package/.agent-src/skills/adversarial-review/SKILL.md +1 -0
- package/.agent-src/skills/agent-docs-writing/SKILL.md +23 -1
- package/.agent-src/skills/ai-council/SKILL.md +132 -8
- package/.agent-src/skills/bug-analyzer/SKILL.md +1 -0
- package/.agent-src/skills/command-writing/SKILL.md +23 -0
- package/.agent-src/skills/context-authoring/SKILL.md +23 -0
- package/.agent-src/skills/conventional-commits-writing/SKILL.md +23 -0
- package/.agent-src/skills/guideline-writing/SKILL.md +22 -0
- package/.agent-src/skills/persona-writing/SKILL.md +153 -0
- package/.agent-src/skills/readme-writing/SKILL.md +20 -0
- package/.agent-src/skills/readme-writing-package/SKILL.md +19 -0
- package/.agent-src/skills/roadmap-management/SKILL.md +7 -7
- package/.agent-src/skills/roadmap-writing/SKILL.md +157 -0
- package/.agent-src/skills/rule-writing/SKILL.md +22 -0
- package/.agent-src/skills/script-writing/SKILL.md +226 -0
- package/.agent-src/skills/skill-writing/SKILL.md +23 -0
- package/.agent-src/skills/systematic-debugging/SKILL.md +22 -2
- package/.agent-src/skills/technical-specification/SKILL.md +58 -1
- package/.agent-src/skills/test-driven-development/SKILL.md +24 -0
- package/.agent-src/skills/threat-modeling/SKILL.md +1 -0
- package/.agent-src/templates/agent-settings.md +87 -3
- package/.agent-src/templates/command.md +30 -9
- package/.agent-src/templates/roadmaps.md +10 -2
- package/.agent-src/templates/rule.md +8 -0
- package/.agent-src/templates/skill.md +49 -0
- package/.claude-plugin/marketplace.json +14 -2
- package/AGENTS.md +3 -3
- package/CHANGELOG.md +73 -0
- package/README.md +5 -5
- package/config/agent-settings.template.yml +22 -0
- package/docs/architecture.md +4 -4
- package/docs/contracts/command-clusters.md +45 -1
- package/docs/customization.md +72 -0
- package/docs/decisions/ADR-003-flat-cluster-subs-and-colon-syntax.md +126 -0
- package/docs/decisions/INDEX.md +15 -0
- package/docs/getting-started.md +2 -2
- package/docs/guidelines/agent-infra/asking-and-brevity-examples.md +27 -19
- package/docs/guidelines/agent-infra/carve-out-predicates.md +17 -0
- package/docs/guidelines/agent-infra/mcp-request-signing.md +199 -0
- package/docs/guidelines/agent-infra/naming.md +1 -1
- package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +11 -4
- package/package.json +1 -1
- package/scripts/_lib/__init__.py +5 -0
- package/scripts/_lib/script_output.py +140 -0
- package/scripts/_phase2_shim_helper.py +1 -1
- package/scripts/adr/regenerate_index.py +79 -0
- package/scripts/ai_council/one_off_archive/2026-05/_one_off_add_quiet.py +149 -0
- package/scripts/ai_council/one_off_archive/2026-05/_one_off_inject_quiet_flag.py +33 -0
- package/scripts/ai_council/one_off_archive/2026-05/_one_off_measure_v2.sh +36 -0
- package/scripts/ai_council/one_off_archive/2026-05/_one_off_measure_verbosity.sh +26 -0
- package/scripts/ai_council/one_off_archive/2026-05/_one_off_per_task.sh +41 -0
- package/scripts/ai_council/one_off_archive/2026-05/_one_off_silent_taskfiles.py +98 -0
- package/scripts/check_augmentignore.py +4 -1
- package/scripts/check_command_count_messaging.py +4 -1
- package/scripts/check_compressed_paths.py +4 -1
- package/scripts/check_council_layout.py +4 -1
- package/scripts/check_council_references.py +4 -1
- package/scripts/check_iron_law_prominence.py +3 -1
- package/scripts/check_md_language.py +3 -1
- package/scripts/check_memory_proposal.py +3 -1
- package/scripts/check_public_catalog_links.py +4 -1
- package/scripts/check_reply_consistency.py +8 -2
- package/scripts/check_roadmap_trackable.py +4 -1
- package/scripts/compile_router.py +27 -0
- package/scripts/compress.py +33 -19
- package/scripts/cost/budget.mjs +152 -0
- package/scripts/cost/track.mjs +144 -0
- package/scripts/council_cli.py +127 -10
- package/scripts/first-run.sh +3 -9
- package/scripts/install-hooks.sh +19 -1
- package/scripts/install.py +17 -12
- package/scripts/install.sh +19 -8
- package/scripts/lint_examples.py +6 -2
- package/scripts/lint_handoffs.py +4 -1
- package/scripts/lint_load_context.py +4 -1
- package/scripts/lint_roadmap_complexity.py +6 -2
- package/scripts/lint_rule_interactions.py +4 -1
- package/scripts/lint_rule_tiers.py +4 -1
- package/scripts/measure_frugality_savings.py +164 -0
- package/scripts/migrate_command_suggestions.py +2 -2
- package/scripts/runtime_dispatcher.py +11 -0
- package/scripts/schemas/command.schema.json +5 -0
- package/scripts/schemas/rule.schema.json +5 -0
- package/scripts/schemas/skill.schema.json +5 -0
- package/scripts/skill_linter.py +208 -3
- package/.agent-src/commands/roadmap/execute.md +0 -109
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// cost-budget — set / get / check the project's cost budget against
|
|
3
|
+
// accumulated session spend in agents/cost-tracking/sessions.jsonl.
|
|
4
|
+
//
|
|
5
|
+
// Forked from ruvnet/ruflo plugins/ruflo-cost-tracker/scripts/budget.mjs.
|
|
6
|
+
// Local-JSONL swap replaces the upstream `mcp__claude-flow__memory_store`
|
|
7
|
+
// dependency. Budget config lives next to the sessions store as budget.json.
|
|
8
|
+
//
|
|
9
|
+
// Usage: node scripts/cost/budget.mjs {set <usd>|get|check}
|
|
10
|
+
// Env: BUDGET_STORE, BUDGET_CONFIG, BUDGET_PERIOD={today|week|month|all}, BUDGET_QUIET=1
|
|
11
|
+
|
|
12
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
13
|
+
import { dirname } from 'node:path';
|
|
14
|
+
|
|
15
|
+
const STORE = process.env.BUDGET_STORE || 'agents/cost-tracking/sessions.jsonl';
|
|
16
|
+
const CONFIG = process.env.BUDGET_CONFIG || 'agents/cost-tracking/budget.json';
|
|
17
|
+
|
|
18
|
+
function loadConfig() {
|
|
19
|
+
if (!existsSync(CONFIG)) return null;
|
|
20
|
+
try { return JSON.parse(readFileSync(CONFIG, 'utf-8')); } catch { return null; }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function saveConfig(cfg) {
|
|
24
|
+
mkdirSync(dirname(CONFIG), { recursive: true });
|
|
25
|
+
writeFileSync(CONFIG, JSON.stringify(cfg, null, 2));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function loadSessions() {
|
|
29
|
+
if (!existsSync(STORE)) return [];
|
|
30
|
+
const out = [];
|
|
31
|
+
for (const line of readFileSync(STORE, 'utf-8').split('\n')) {
|
|
32
|
+
if (!line.trim()) continue;
|
|
33
|
+
try { out.push(JSON.parse(line)); } catch { /* skip malformed line */ }
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function periodFilter(period) {
|
|
39
|
+
const now = Date.now();
|
|
40
|
+
const day = 24 * 3600 * 1000;
|
|
41
|
+
if (period === 'today') return (ts) => ts && new Date(ts).toDateString() === new Date().toDateString();
|
|
42
|
+
if (period === 'week') return (ts) => ts && (now - new Date(ts).getTime()) < 7 * day;
|
|
43
|
+
if (period === 'month') return (ts) => ts && (now - new Date(ts).getTime()) < 30 * day;
|
|
44
|
+
return () => true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function alertLevel(u) {
|
|
48
|
+
if (u >= 1.00) return { level: 'HARD_STOP', emoji: '🛑', threshold: 100 };
|
|
49
|
+
if (u >= 0.90) return { level: 'CRITICAL', emoji: '🔴', threshold: 90 };
|
|
50
|
+
if (u >= 0.75) return { level: 'WARNING', emoji: '🟠', threshold: 75 };
|
|
51
|
+
if (u >= 0.50) return { level: 'INFO', emoji: '🟡', threshold: 50 };
|
|
52
|
+
return { level: 'OK', emoji: '🟢', threshold: 0 };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function recommendedAction(level) {
|
|
56
|
+
return ({
|
|
57
|
+
OK: 'within budget — no action.',
|
|
58
|
+
INFO: '50% consumed — log notification, no UX disruption.',
|
|
59
|
+
WARNING: '75% consumed — suggest /set-cost-profile balanced→minimal.',
|
|
60
|
+
CRITICAL: '90% consumed — recommend model downgrades, consider /set-cost-profile minimal.',
|
|
61
|
+
HARD_STOP: '100% consumed — halt non-essential work; review /cost:report before continuing.',
|
|
62
|
+
}[level]);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function cmdSet(args) {
|
|
66
|
+
const amount = parseFloat(args[0]);
|
|
67
|
+
if (!Number.isFinite(amount) || amount <= 0) {
|
|
68
|
+
console.error('usage: budget.mjs set <usd-amount> (positive number)');
|
|
69
|
+
process.exit(2);
|
|
70
|
+
}
|
|
71
|
+
const config = {
|
|
72
|
+
budget_usd: amount,
|
|
73
|
+
setAt: new Date().toISOString(),
|
|
74
|
+
thresholds: { info: 0.50, warning: 0.75, critical: 0.90, hard_stop: 1.00 },
|
|
75
|
+
};
|
|
76
|
+
saveConfig(config);
|
|
77
|
+
if (process.env.BUDGET_QUIET === '1') {
|
|
78
|
+
console.log(JSON.stringify(config));
|
|
79
|
+
} else {
|
|
80
|
+
console.log(`✓ Budget set: $${amount.toFixed(2)} (config: ${CONFIG})`);
|
|
81
|
+
console.log(' Alerts: 50% INFO · 75% WARNING · 90% CRITICAL · 100% HARD_STOP');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function cmdGet() {
|
|
86
|
+
const cfg = loadConfig();
|
|
87
|
+
if (process.env.BUDGET_QUIET === '1') {
|
|
88
|
+
console.log(JSON.stringify(cfg || { error: 'no budget configured' }));
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (!cfg) {
|
|
92
|
+
console.log(`No budget configured (config: ${CONFIG}).`);
|
|
93
|
+
console.log('Set one with: node scripts/cost/budget.mjs set <usd>');
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
console.log(`Budget: $${cfg.budget_usd?.toFixed(2)} (set ${cfg.setAt})`);
|
|
97
|
+
console.log('Thresholds: 50/75/90/100%');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function cmdCheck() {
|
|
101
|
+
const cfg = loadConfig();
|
|
102
|
+
const period = process.env.BUDGET_PERIOD || 'all';
|
|
103
|
+
const filt = periodFilter(period);
|
|
104
|
+
const filtered = loadSessions().filter((r) => filt(r.capturedAt || r.endedAt));
|
|
105
|
+
const totalSpend = filtered.reduce((s, r) => s + (r.total_cost_usd || 0), 0);
|
|
106
|
+
if (!cfg || !Number.isFinite(cfg.budget_usd)) {
|
|
107
|
+
const out = { period, totalSpend, recordCount: filtered.length, error: 'no budget configured' };
|
|
108
|
+
if (process.env.BUDGET_QUIET === '1') return console.log(JSON.stringify(out));
|
|
109
|
+
console.log(`Period: ${period}`);
|
|
110
|
+
console.log(`Spent so far: $${totalSpend.toFixed(2)} across ${filtered.length} sessions`);
|
|
111
|
+
console.log('No budget set — run `node scripts/cost/budget.mjs set <usd>` to enable alerts.');
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const utilization = totalSpend / cfg.budget_usd;
|
|
115
|
+
const alert = alertLevel(utilization);
|
|
116
|
+
const out = {
|
|
117
|
+
period,
|
|
118
|
+
budget_usd: cfg.budget_usd,
|
|
119
|
+
spent_usd: totalSpend,
|
|
120
|
+
remaining_usd: Math.max(0, cfg.budget_usd - totalSpend),
|
|
121
|
+
utilization_pct: utilization * 100,
|
|
122
|
+
level: alert.level,
|
|
123
|
+
threshold: alert.threshold,
|
|
124
|
+
recommended_action: recommendedAction(alert.level),
|
|
125
|
+
sessionCount: filtered.length,
|
|
126
|
+
};
|
|
127
|
+
if (process.env.BUDGET_QUIET === '1') return console.log(JSON.stringify(out));
|
|
128
|
+
console.log(`# Budget check (period: ${period})\n`);
|
|
129
|
+
console.log('| Metric | Value |\n|---|---:|');
|
|
130
|
+
console.log(`| Budget | $${cfg.budget_usd.toFixed(2)} |`);
|
|
131
|
+
console.log(`| Spent | $${totalSpend.toFixed(2)} |`);
|
|
132
|
+
console.log(`| Remaining | $${out.remaining_usd.toFixed(2)} |`);
|
|
133
|
+
console.log(`| Utilization | ${out.utilization_pct.toFixed(1)}% |`);
|
|
134
|
+
console.log(`| Sessions counted | ${filtered.length} |`);
|
|
135
|
+
console.log(`| **Alert** | **${alert.emoji} ${alert.level}** |`);
|
|
136
|
+
console.log(`\nAction: ${out.recommended_action}`);
|
|
137
|
+
if (alert.level === 'HARD_STOP') process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function main() {
|
|
141
|
+
const [cmd, ...rest] = process.argv.slice(2);
|
|
142
|
+
switch (cmd) {
|
|
143
|
+
case 'set': return cmdSet(rest);
|
|
144
|
+
case 'get': return cmdGet();
|
|
145
|
+
case 'check': return cmdCheck();
|
|
146
|
+
default:
|
|
147
|
+
console.error('usage: budget.mjs {set <usd>|get|check}');
|
|
148
|
+
process.exit(2);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
main();
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// cost-track — auto-capture token usage from a Claude Code session jsonl
|
|
3
|
+
// and append a structured record to agents/cost-tracking/sessions.jsonl.
|
|
4
|
+
//
|
|
5
|
+
// Forked from ruvnet/ruflo plugins/ruflo-cost-tracker/scripts/track.mjs.
|
|
6
|
+
// Local-JSONL swap replaces the upstream `mcp__claude-flow__memory_store`
|
|
7
|
+
// dependency. Pricing constants are kept in sync with REFERENCE.md.
|
|
8
|
+
//
|
|
9
|
+
// Env:
|
|
10
|
+
// TRACK_CWD=<path> override which project's sessions to scan
|
|
11
|
+
// TRACK_SESSION=<file> pin to a specific session jsonl
|
|
12
|
+
// TRACK_OUT=<path> also write the JSON summary to this path
|
|
13
|
+
// TRACK_DRY_RUN=1 skip the JSONL append
|
|
14
|
+
// TRACK_QUIET=1 suppress markdown summary
|
|
15
|
+
// TRACK_STORE=<path> override (default: agents/cost-tracking/sessions.jsonl)
|
|
16
|
+
|
|
17
|
+
import { readFileSync, writeFileSync, readdirSync, statSync, existsSync, mkdirSync, appendFileSync } from 'node:fs';
|
|
18
|
+
import { join, dirname } from 'node:path';
|
|
19
|
+
import { homedir } from 'node:os';
|
|
20
|
+
|
|
21
|
+
const PROJECTS_DIR = join(homedir(), '.claude', 'projects');
|
|
22
|
+
const DEFAULT_STORE = 'agents/cost-tracking/sessions.jsonl';
|
|
23
|
+
|
|
24
|
+
// USD per 1M tokens.
|
|
25
|
+
const PRICING = {
|
|
26
|
+
haiku: { input: 0.25, output: 1.25, cache_write: 0.30, cache_read: 0.03 },
|
|
27
|
+
sonnet: { input: 3.00, output: 15.00, cache_write: 3.75, cache_read: 0.30 },
|
|
28
|
+
opus: { input: 15.00, output: 75.00, cache_write: 18.75, cache_read: 1.50 },
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function modelTier(model) {
|
|
32
|
+
if (!model) return 'unknown';
|
|
33
|
+
const m = String(model).toLowerCase();
|
|
34
|
+
if (m.includes('haiku')) return 'haiku';
|
|
35
|
+
if (m.includes('sonnet')) return 'sonnet';
|
|
36
|
+
if (m.includes('opus')) return 'opus';
|
|
37
|
+
return 'unknown';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function costForUsage(tier, u) {
|
|
41
|
+
const p = PRICING[tier];
|
|
42
|
+
if (!p || !u) return 0;
|
|
43
|
+
return (u.input_tokens || 0) / 1e6 * p.input
|
|
44
|
+
+ (u.output_tokens || 0) / 1e6 * p.output
|
|
45
|
+
+ (u.cache_creation_input_tokens || 0) / 1e6 * p.cache_write
|
|
46
|
+
+ (u.cache_read_input_tokens || 0) / 1e6 * p.cache_read;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function encodeProjectPath(cwd) { return cwd.replace(/\//g, '-'); }
|
|
50
|
+
|
|
51
|
+
function findProjectDir(cwd) {
|
|
52
|
+
const c = join(PROJECTS_DIR, encodeProjectPath(cwd));
|
|
53
|
+
return existsSync(c) ? c : null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function findActiveSession(dir) {
|
|
57
|
+
const e = readdirSync(dir).filter((f) => f.endsWith('.jsonl'))
|
|
58
|
+
.map((f) => ({ f, mtime: statSync(join(dir, f)).mtimeMs }))
|
|
59
|
+
.sort((a, b) => b.mtime - a.mtime);
|
|
60
|
+
return e[0] ? join(dir, e[0].f) : null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function summarizeSession(jsonlPath) {
|
|
64
|
+
const lines = readFileSync(jsonlPath, 'utf-8').split('\n').filter(Boolean);
|
|
65
|
+
const byModel = {};
|
|
66
|
+
const byTier = { haiku: 0, sonnet: 0, opus: 0, unknown: 0 };
|
|
67
|
+
let messageCount = 0, totalCost = 0, firstTs = null, lastTs = null;
|
|
68
|
+
let sessionId = null, cwd = null;
|
|
69
|
+
for (const line of lines) {
|
|
70
|
+
let m; try { m = JSON.parse(line); } catch { continue; }
|
|
71
|
+
if (!sessionId && m.sessionId) sessionId = m.sessionId;
|
|
72
|
+
if (!cwd && m.cwd) cwd = m.cwd;
|
|
73
|
+
if (m.timestamp) {
|
|
74
|
+
if (!firstTs || m.timestamp < firstTs) firstTs = m.timestamp;
|
|
75
|
+
if (!lastTs || m.timestamp > lastTs) lastTs = m.timestamp;
|
|
76
|
+
}
|
|
77
|
+
if (m.type !== 'assistant' || !m.message?.usage) continue;
|
|
78
|
+
messageCount++;
|
|
79
|
+
const model = m.message.model || 'unknown';
|
|
80
|
+
const tier = modelTier(model);
|
|
81
|
+
const u = m.message.usage;
|
|
82
|
+
const cost = costForUsage(tier, u);
|
|
83
|
+
const slot = byModel[model] || { tier, input_tokens: 0, output_tokens: 0,
|
|
84
|
+
cache_creation_input_tokens: 0, cache_read_input_tokens: 0, messages: 0, cost_usd: 0 };
|
|
85
|
+
slot.input_tokens += u.input_tokens || 0;
|
|
86
|
+
slot.output_tokens += u.output_tokens || 0;
|
|
87
|
+
slot.cache_creation_input_tokens += u.cache_creation_input_tokens || 0;
|
|
88
|
+
slot.cache_read_input_tokens += u.cache_read_input_tokens || 0;
|
|
89
|
+
slot.messages++; slot.cost_usd += cost;
|
|
90
|
+
byModel[model] = slot; byTier[tier] += cost; totalCost += cost;
|
|
91
|
+
}
|
|
92
|
+
return { sessionId, cwd, startedAt: firstTs, endedAt: lastTs, messageCount,
|
|
93
|
+
byModel, byTier, total_cost_usd: totalCost, capturedAt: new Date().toISOString() };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function persistJsonl(summary, store) {
|
|
97
|
+
mkdirSync(dirname(store), { recursive: true });
|
|
98
|
+
appendFileSync(store, JSON.stringify(summary) + '\n');
|
|
99
|
+
return { ok: true, path: store };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function main() {
|
|
103
|
+
const targetCwd = process.env.TRACK_CWD || process.cwd();
|
|
104
|
+
const projectDir = findProjectDir(targetCwd);
|
|
105
|
+
if (!projectDir) {
|
|
106
|
+
console.error(`cost-track: no Claude Code project dir for cwd=${targetCwd}`);
|
|
107
|
+
console.error(`looked under ${PROJECTS_DIR}/${encodeProjectPath(targetCwd)}`);
|
|
108
|
+
process.exit(2);
|
|
109
|
+
}
|
|
110
|
+
const sessionPath = process.env.TRACK_SESSION || findActiveSession(projectDir);
|
|
111
|
+
if (!sessionPath || !existsSync(sessionPath)) {
|
|
112
|
+
console.error(`cost-track: no session jsonl in ${projectDir}`); process.exit(2);
|
|
113
|
+
}
|
|
114
|
+
const summary = summarizeSession(sessionPath);
|
|
115
|
+
if (process.env.TRACK_OUT) writeFileSync(process.env.TRACK_OUT, JSON.stringify(summary, null, 2));
|
|
116
|
+
const store = process.env.TRACK_STORE || DEFAULT_STORE;
|
|
117
|
+
let res = { ok: false, reason: 'dry-run' };
|
|
118
|
+
if (process.env.TRACK_DRY_RUN !== '1') res = persistJsonl(summary, store);
|
|
119
|
+
if (process.env.TRACK_QUIET === '1') return;
|
|
120
|
+
|
|
121
|
+
console.log(`# cost-track — session ${(summary.sessionId || '').slice(0, 8) || 'unknown'}`);
|
|
122
|
+
console.log('');
|
|
123
|
+
console.log('| Metric | Value |\n|---|---:|');
|
|
124
|
+
console.log(`| Session ID | \`${summary.sessionId}\` |`);
|
|
125
|
+
console.log(`| Project | \`${summary.cwd}\` |`);
|
|
126
|
+
console.log(`| First message | ${summary.startedAt} |`);
|
|
127
|
+
console.log(`| Last message | ${summary.endedAt} |`);
|
|
128
|
+
console.log(`| Assistant messages | ${summary.messageCount} |`);
|
|
129
|
+
console.log(`| **Total cost** | **$${summary.total_cost_usd.toFixed(6)}** |`);
|
|
130
|
+
console.log(`| Persisted | ${res.ok ? `\`${res.path}\`` : `**FAILED** (${res.reason})`} |`);
|
|
131
|
+
console.log('\n## Per-model breakdown\n');
|
|
132
|
+
console.log('| Model | Tier | Messages | Input | Output | Cache write | Cache read | Cost |');
|
|
133
|
+
console.log('|---|---|---:|---:|---:|---:|---:|---:|');
|
|
134
|
+
for (const [m, s] of Object.entries(summary.byModel).sort((a, b) => b[1].cost_usd - a[1].cost_usd)) {
|
|
135
|
+
console.log(`| \`${m}\` | ${s.tier} | ${s.messages} | ${s.input_tokens} | ${s.output_tokens} | ${s.cache_creation_input_tokens} | ${s.cache_read_input_tokens} | $${s.cost_usd.toFixed(6)} |`);
|
|
136
|
+
}
|
|
137
|
+
console.log('\n## Per-tier breakdown\n');
|
|
138
|
+
console.log('| Tier | Cost |\n|---|---:|');
|
|
139
|
+
for (const [t, c] of Object.entries(summary.byTier).sort((a, b) => b[1] - a[1])) {
|
|
140
|
+
if (c > 0) console.log(`| ${t} | $${c.toFixed(6)} |`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
main();
|
package/scripts/council_cli.py
CHANGED
|
@@ -63,6 +63,7 @@ def build_members(
|
|
|
63
63
|
*,
|
|
64
64
|
invocation_mode: str | None = None,
|
|
65
65
|
model_overrides: dict[str, str] | None = None,
|
|
66
|
+
siblings_overrides: dict[str, list[str]] | None = None,
|
|
66
67
|
) -> list[ExternalAIClient]:
|
|
67
68
|
"""Construct enabled council members from settings.
|
|
68
69
|
|
|
@@ -73,6 +74,13 @@ def build_members(
|
|
|
73
74
|
`model_overrides` is a per-invocation `{member_name: model_id}`
|
|
74
75
|
map that wins over the per-member `model` in settings. Members not
|
|
75
76
|
listed fall back to the settings value, then the per-client default.
|
|
77
|
+
|
|
78
|
+
`siblings_overrides` is a per-invocation `{member_name: [model, ...]}`
|
|
79
|
+
map that fans the named provider out to multiple sibling models in
|
|
80
|
+
one run (e.g. claude-sonnet-4-5 + claude-opus-4-1). Each model
|
|
81
|
+
becomes its own billable member with independent cost tracking.
|
|
82
|
+
Mutually exclusive with `model_overrides` for the same provider;
|
|
83
|
+
requires `mode=api`; provider must be enabled in settings.
|
|
76
84
|
"""
|
|
77
85
|
ai = (settings.get("ai_council") or {}) if isinstance(settings, dict) else {}
|
|
78
86
|
if not ai.get("enabled"):
|
|
@@ -83,16 +91,34 @@ def build_members(
|
|
|
83
91
|
members_cfg = ai.get("members") or {}
|
|
84
92
|
global_mode = ai.get("mode")
|
|
85
93
|
overrides = model_overrides or {}
|
|
94
|
+
siblings = siblings_overrides or {}
|
|
86
95
|
unknown = set(overrides) - set(members_cfg)
|
|
87
96
|
if unknown:
|
|
88
97
|
raise CouncilDisabledError(
|
|
89
98
|
f"--model targets unknown member(s) {sorted(unknown)!r}; "
|
|
90
99
|
f"known members: {sorted(members_cfg)!r}."
|
|
91
100
|
)
|
|
101
|
+
unknown_sib = set(siblings) - set(members_cfg)
|
|
102
|
+
if unknown_sib:
|
|
103
|
+
raise CouncilDisabledError(
|
|
104
|
+
f"--siblings targets unknown member(s) {sorted(unknown_sib)!r}; "
|
|
105
|
+
f"known members: {sorted(members_cfg)!r}."
|
|
106
|
+
)
|
|
107
|
+
conflict = set(overrides) & set(siblings)
|
|
108
|
+
if conflict:
|
|
109
|
+
raise CouncilDisabledError(
|
|
110
|
+
f"--model and --siblings target the same member(s) {sorted(conflict)!r}; "
|
|
111
|
+
f"pick one per provider per invocation."
|
|
112
|
+
)
|
|
92
113
|
members: list[ExternalAIClient] = []
|
|
93
114
|
for name, cfg in members_cfg.items():
|
|
94
115
|
cfg = cfg or {}
|
|
95
116
|
if not cfg.get("enabled"):
|
|
117
|
+
if name in siblings:
|
|
118
|
+
raise CouncilDisabledError(
|
|
119
|
+
f"--siblings targets member {name!r} but it is not "
|
|
120
|
+
f"enabled in .agent-settings.yml (ai_council.members.{name}.enabled)."
|
|
121
|
+
)
|
|
96
122
|
continue
|
|
97
123
|
mode = resolve_mode(
|
|
98
124
|
name,
|
|
@@ -100,13 +126,17 @@ def build_members(
|
|
|
100
126
|
member_settings=cfg,
|
|
101
127
|
global_mode=global_mode,
|
|
102
128
|
)
|
|
129
|
+
if name in siblings:
|
|
130
|
+
if mode != "api":
|
|
131
|
+
raise CouncilDisabledError(
|
|
132
|
+
f"--siblings requires mode=api for member {name!r} (got {mode!r})."
|
|
133
|
+
)
|
|
134
|
+
for sib_model in siblings[name]:
|
|
135
|
+
members.append(_construct_api_member(name, sib_model))
|
|
136
|
+
continue
|
|
103
137
|
model = overrides.get(name) or cfg.get("model")
|
|
104
|
-
if mode == "api" and name
|
|
105
|
-
members.append(
|
|
106
|
-
api_key=load_anthropic_key()))
|
|
107
|
-
elif mode == "api" and name == "openai":
|
|
108
|
-
members.append(OpenAIClient(model=model or "gpt-4o",
|
|
109
|
-
api_key=load_openai_key()))
|
|
138
|
+
if mode == "api" and name in {"anthropic", "openai"}:
|
|
139
|
+
members.append(_construct_api_member(name, model))
|
|
110
140
|
elif mode == "manual":
|
|
111
141
|
members.append(ManualClient(name=name, model=model or "manual"))
|
|
112
142
|
elif mode == "playwright":
|
|
@@ -125,6 +155,19 @@ def build_members(
|
|
|
125
155
|
return members
|
|
126
156
|
|
|
127
157
|
|
|
158
|
+
def _construct_api_member(name: str, model: str | None) -> ExternalAIClient:
|
|
159
|
+
"""Build an api-mode client for a known provider name."""
|
|
160
|
+
if name == "anthropic":
|
|
161
|
+
return AnthropicClient(model=model or "claude-sonnet-4-5",
|
|
162
|
+
api_key=load_anthropic_key())
|
|
163
|
+
if name == "openai":
|
|
164
|
+
return OpenAIClient(model=model or "gpt-4o",
|
|
165
|
+
api_key=load_openai_key())
|
|
166
|
+
raise CouncilDisabledError(
|
|
167
|
+
f"member {name!r} has no api transport (known: anthropic, openai)."
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
128
171
|
def build_question(
|
|
129
172
|
*,
|
|
130
173
|
input_path: Path,
|
|
@@ -164,6 +207,29 @@ def format_estimate_table(
|
|
|
164
207
|
# ── subcommands ─────────────────────────────────────────────────────
|
|
165
208
|
|
|
166
209
|
|
|
210
|
+
def _resolve_rounds(args: argparse.Namespace, ai_cfg: dict[str, Any]) -> int:
|
|
211
|
+
"""Resolve effective debate round count from CLI args + settings.
|
|
212
|
+
|
|
213
|
+
Resolution chain (highest priority first):
|
|
214
|
+
1. ``--rounds N`` — explicit user override, any value.
|
|
215
|
+
2. ``--depth deep`` — uses ``ai_council.deep_min_rounds``,
|
|
216
|
+
floored at ``min_rounds`` so the deep tier is monotonic.
|
|
217
|
+
3. ``ai_council.min_rounds`` — default 2.
|
|
218
|
+
|
|
219
|
+
Sub-commands (rule/skill/command) declare ``council_depth: deep``
|
|
220
|
+
in their frontmatter; the host agent reads that and translates it
|
|
221
|
+
to ``--depth deep`` on the CLI invocation. The CLI itself stays
|
|
222
|
+
unaware of frontmatter — the contract is the flag.
|
|
223
|
+
"""
|
|
224
|
+
if getattr(args, "rounds", None) is not None:
|
|
225
|
+
return int(args.rounds)
|
|
226
|
+
min_rounds = int(ai_cfg.get("min_rounds", 2))
|
|
227
|
+
if getattr(args, "depth", "standard") == "deep":
|
|
228
|
+
deep = int(ai_cfg.get("deep_min_rounds", min_rounds))
|
|
229
|
+
return max(deep, min_rounds)
|
|
230
|
+
return min_rounds
|
|
231
|
+
|
|
232
|
+
|
|
167
233
|
def cmd_estimate(
|
|
168
234
|
args: argparse.Namespace,
|
|
169
235
|
*,
|
|
@@ -179,6 +245,7 @@ def cmd_estimate(
|
|
|
179
245
|
settings,
|
|
180
246
|
invocation_mode=args.mode_override,
|
|
181
247
|
model_overrides=_parse_model_overrides(getattr(args, "model", None)),
|
|
248
|
+
siblings_overrides=_parse_siblings_overrides(getattr(args, "siblings", None)),
|
|
182
249
|
)
|
|
183
250
|
if table is None:
|
|
184
251
|
table = load_prices()
|
|
@@ -239,6 +306,7 @@ def cmd_run(
|
|
|
239
306
|
settings,
|
|
240
307
|
invocation_mode=args.mode_override,
|
|
241
308
|
model_overrides=_parse_model_overrides(getattr(args, "model", None)),
|
|
309
|
+
siblings_overrides=_parse_siblings_overrides(getattr(args, "siblings", None)),
|
|
242
310
|
)
|
|
243
311
|
if table is None:
|
|
244
312
|
table = load_prices()
|
|
@@ -271,7 +339,7 @@ def cmd_run(
|
|
|
271
339
|
max_calls=int(cost_cfg.get("max_calls", 10)),
|
|
272
340
|
max_total_usd=float(cost_cfg.get("max_total_usd", 0.0) or 0.0),
|
|
273
341
|
)
|
|
274
|
-
rounds = args
|
|
342
|
+
rounds = _resolve_rounds(args, ai_cfg)
|
|
275
343
|
responses = consult(
|
|
276
344
|
members, question, budget,
|
|
277
345
|
table=table, project=project,
|
|
@@ -339,6 +407,38 @@ def _parse_model_overrides(items: list[str] | None) -> dict[str, str]:
|
|
|
339
407
|
return out
|
|
340
408
|
|
|
341
409
|
|
|
410
|
+
def _parse_siblings_overrides(items: list[str] | None) -> dict[str, list[str]]:
|
|
411
|
+
"""Parse repeated `--siblings name=model1,model2[,...]` flags.
|
|
412
|
+
|
|
413
|
+
Requires ≥ 2 distinct, non-empty models per provider — sibling
|
|
414
|
+
mode without diversity has no purpose. Repeating the same provider
|
|
415
|
+
flag is rejected as ambiguous.
|
|
416
|
+
"""
|
|
417
|
+
out: dict[str, list[str]] = {}
|
|
418
|
+
for raw in items or []:
|
|
419
|
+
if "=" not in raw:
|
|
420
|
+
raise argparse.ArgumentTypeError(
|
|
421
|
+
f"--siblings expects '<member>=<model1>,<model2>[,...]', got {raw!r}."
|
|
422
|
+
)
|
|
423
|
+
name, models_csv = raw.split("=", 1)
|
|
424
|
+
name = name.strip()
|
|
425
|
+
models = [m.strip() for m in models_csv.split(",") if m.strip()]
|
|
426
|
+
if not name or not models:
|
|
427
|
+
raise argparse.ArgumentTypeError(
|
|
428
|
+
f"--siblings member and model list must both be non-empty: {raw!r}."
|
|
429
|
+
)
|
|
430
|
+
if len(set(models)) < 2:
|
|
431
|
+
raise argparse.ArgumentTypeError(
|
|
432
|
+
f"--siblings requires ≥ 2 distinct models for {name!r}, got {models!r}."
|
|
433
|
+
)
|
|
434
|
+
if name in out:
|
|
435
|
+
raise argparse.ArgumentTypeError(
|
|
436
|
+
f"--siblings repeated for member {name!r}; combine into one flag."
|
|
437
|
+
)
|
|
438
|
+
out[name] = models
|
|
439
|
+
return out
|
|
440
|
+
|
|
441
|
+
|
|
342
442
|
def _add_common_input_args(p: argparse.ArgumentParser) -> None:
|
|
343
443
|
p.add_argument("question", type=str,
|
|
344
444
|
help="Path to the question file (text or roadmap).")
|
|
@@ -356,6 +456,16 @@ def _add_common_input_args(p: argparse.ArgumentParser) -> None:
|
|
|
356
456
|
"Wins over `ai_council.members.<name>.model` in "
|
|
357
457
|
".agent-settings.yml; the settings file is not "
|
|
358
458
|
"modified.")
|
|
459
|
+
p.add_argument("--siblings", action="append", default=None, dest="siblings",
|
|
460
|
+
metavar="MEMBER=MODEL1,MODEL2[,...]",
|
|
461
|
+
help="Fan one provider out to ≥ 2 sibling models in a "
|
|
462
|
+
"single run, e.g. --siblings anthropic=claude-sonnet-4-5,"
|
|
463
|
+
"claude-opus-4-1. Each model becomes its own billable "
|
|
464
|
+
"member with independent cost tracking. Mutually "
|
|
465
|
+
"exclusive with --model for the same provider; "
|
|
466
|
+
"requires the provider to be enabled with mode=api. "
|
|
467
|
+
"Single-provider degraded-run strategy per ai-council "
|
|
468
|
+
"skill.")
|
|
359
469
|
p.add_argument("--original-ask", default="",
|
|
360
470
|
help="The user's framing sentence (flows into handoff).")
|
|
361
471
|
|
|
@@ -377,9 +487,16 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
377
487
|
p_run.add_argument("--confirm", action="store_true",
|
|
378
488
|
help="Required to actually invoke the council.")
|
|
379
489
|
p_run.add_argument("--rounds", type=int, default=None,
|
|
380
|
-
help="Number of debate rounds (1-3).
|
|
381
|
-
"
|
|
382
|
-
"(or 2 if unset).")
|
|
490
|
+
help="Number of debate rounds (1-3). Explicit override; "
|
|
491
|
+
"wins over --depth. Defaults to ai_council.min_rounds "
|
|
492
|
+
"in .agent-settings.yml (or 2 if unset).")
|
|
493
|
+
p_run.add_argument("--depth", choices=["standard", "deep"], default="standard",
|
|
494
|
+
help="Reasoning-depth tier. 'deep' floors rounds at "
|
|
495
|
+
"ai_council.deep_min_rounds (max'd with min_rounds) "
|
|
496
|
+
"for architecture, refactoring, or bug-diagnosis "
|
|
497
|
+
"artefacts. Set by the host agent when the consuming "
|
|
498
|
+
"rule/skill/command declares council_depth: deep. "
|
|
499
|
+
"Overridden by explicit --rounds.")
|
|
383
500
|
|
|
384
501
|
p_ren = sub.add_parser("render", help="Re-render a saved responses JSON.")
|
|
385
502
|
p_ren.add_argument("responses",
|
package/scripts/first-run.sh
CHANGED
|
@@ -5,9 +5,7 @@ SETTINGS_FILE=".agent-settings.yml"
|
|
|
5
5
|
LEGACY_SETTINGS_FILE=".agent-settings"
|
|
6
6
|
|
|
7
7
|
echo ""
|
|
8
|
-
echo "
|
|
9
|
-
echo " Agent Config — First Run"
|
|
10
|
-
echo "========================================"
|
|
8
|
+
echo "Agent Config — First Run"
|
|
11
9
|
echo ""
|
|
12
10
|
|
|
13
11
|
# --- Profile detection ---
|
|
@@ -63,9 +61,7 @@ echo " ✅ Zero token overhead in minimal mode"
|
|
|
63
61
|
echo ""
|
|
64
62
|
|
|
65
63
|
# --- 3 test prompts ---
|
|
66
|
-
echo "
|
|
67
|
-
echo " Try these 3 prompts now"
|
|
68
|
-
echo "========================================"
|
|
64
|
+
echo "Try these 3 prompts now:"
|
|
69
65
|
echo ""
|
|
70
66
|
|
|
71
67
|
echo "1️⃣ Refactoring check"
|
|
@@ -94,9 +90,7 @@ echo " → Agent challenges weak requirements"
|
|
|
94
90
|
echo ""
|
|
95
91
|
|
|
96
92
|
# --- Next steps ---
|
|
97
|
-
echo "
|
|
98
|
-
echo " Next steps"
|
|
99
|
-
echo "========================================"
|
|
93
|
+
echo "Next steps:"
|
|
100
94
|
echo ""
|
|
101
95
|
echo "Cost profiles:"
|
|
102
96
|
echo " minimal rules, skills, commands only"
|
package/scripts/install-hooks.sh
CHANGED
|
@@ -39,7 +39,9 @@ echo "✅ Pre-push hook installed."
|
|
|
39
39
|
cat > "$HOOKS_DIR/pre-commit" << 'EOF'
|
|
40
40
|
#!/usr/bin/env bash
|
|
41
41
|
# Pre-commit hook: verify .claude-plugin/marketplace.json lists every skill
|
|
42
|
-
# that exists on disk under .claude/skills
|
|
42
|
+
# that exists on disk under .claude/skills/, AND verify
|
|
43
|
+
# agents/roadmaps-progress.md is in sync with the current state of
|
|
44
|
+
# agents/roadmaps/ (roadmap-progress-sync Iron Law).
|
|
43
45
|
|
|
44
46
|
python3 scripts/lint_marketplace.py
|
|
45
47
|
status=$?
|
|
@@ -52,6 +54,22 @@ if [ $status -ne 0 ]; then
|
|
|
52
54
|
echo " git commit --no-verify"
|
|
53
55
|
exit 1
|
|
54
56
|
fi
|
|
57
|
+
|
|
58
|
+
# Roadmap dashboard sync — only fires when staged changes touch a roadmap
|
|
59
|
+
# file or the dashboard itself, so unrelated commits stay fast.
|
|
60
|
+
if git diff --cached --name-only | grep -qE '^agents/roadmaps(-progress\.md|/)'; then
|
|
61
|
+
python3 .augment/scripts/update_roadmap_progress.py --check
|
|
62
|
+
rstatus=$?
|
|
63
|
+
if [ $rstatus -ne 0 ]; then
|
|
64
|
+
echo ""
|
|
65
|
+
echo "❌ Commit blocked — agents/roadmaps-progress.md is stale."
|
|
66
|
+
echo " Run './agent-config roadmap:progress' (or"
|
|
67
|
+
echo " 'python3 .augment/scripts/update_roadmap_progress.py'),"
|
|
68
|
+
echo " stage agents/roadmaps-progress.md, then re-commit."
|
|
69
|
+
echo " To bypass for an unrelated WIP commit: git commit --no-verify"
|
|
70
|
+
exit 1
|
|
71
|
+
fi
|
|
72
|
+
fi
|
|
55
73
|
EOF
|
|
56
74
|
|
|
57
75
|
chmod +x "$HOOKS_DIR/pre-commit"
|
package/scripts/install.py
CHANGED
|
@@ -1280,6 +1280,7 @@ def main(argv: list[str]) -> int:
|
|
|
1280
1280
|
fail(f"Unsupported profile: {opts.profile}. Supported: {', '.join(SUPPORTED_PROFILES)}")
|
|
1281
1281
|
|
|
1282
1282
|
project_root = Path(opts.project or os.environ.get("PROJECT_ROOT") or os.getcwd()).resolve()
|
|
1283
|
+
is_first_run = not (project_root / SETTINGS_FILE).exists()
|
|
1283
1284
|
|
|
1284
1285
|
if opts.package:
|
|
1285
1286
|
package_root = Path(opts.package).resolve()
|
|
@@ -1335,18 +1336,22 @@ def main(argv: list[str]) -> int:
|
|
|
1335
1336
|
if not QUIET:
|
|
1336
1337
|
print()
|
|
1337
1338
|
success("Done.")
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1339
|
+
if is_first_run:
|
|
1340
|
+
print()
|
|
1341
|
+
print(" Try these 3 prompts with your agent:")
|
|
1342
|
+
print(' 1. "Refactor this function" → agent analyzes first')
|
|
1343
|
+
print(' 2. "Add caching to this" → agent asks instead of guessing')
|
|
1344
|
+
print(' 3. "Implement this feature" → agent respects your codebase')
|
|
1345
|
+
print()
|
|
1346
|
+
print(" Next steps:")
|
|
1347
|
+
print(" • Commit .agent-settings.yml and bridge files to your repo")
|
|
1348
|
+
print(" • New team members just run composer install / npm install — done")
|
|
1349
|
+
print(" • Inspect hook coverage: ./agent-config hooks:status")
|
|
1350
|
+
print(" • Full walkthrough: https://github.com/event4u-app/agent-config/blob/main/docs/getting-started.md")
|
|
1351
|
+
print()
|
|
1352
|
+
else:
|
|
1353
|
+
print(" Re-run complete. Walkthrough: https://github.com/event4u-app/agent-config/blob/main/docs/getting-started.md")
|
|
1354
|
+
print()
|
|
1350
1355
|
return 0
|
|
1351
1356
|
|
|
1352
1357
|
|