@event4u/agent-config 9.35.0 → 9.36.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/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +61 -165
- package/dist/agent-src/commands/analyze/reference-repo.md +135 -42
- package/dist/agent-src/contexts/augment-infrastructure.md +1 -1
- package/dist/agent-src/rules/design-review-after-ui-write.md +53 -6
- package/dist/agent-src/rules/improve-before-implement.md +1 -1
- package/dist/agent-src/rules/ui-audit-gate.md +35 -0
- package/dist/agent-src/skills/design-system-capture/SKILL.md +2 -2
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +1 -1
- package/dist/agent-src/skills/fe-design/SKILL.md +84 -12
- package/dist/agent-src/skills/fe-design/evals/triggers.json +11 -3
- package/dist/agent-src/skills/prediction-pool-optimizer/SKILL.md +5 -5
- package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +1 -1
- package/dist/agent-src/skills/skill-writing/SKILL.md +24 -0
- package/dist/agent-src/skills/subagent-orchestration/prompts/README.md +29 -0
- package/dist/agent-src/templates/AGENTS.md +1 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli-delegate/{chunk-ZAXH3ZWU.js → chunk-JP53LNVT.js} +9 -0
- package/dist/cli-delegate/{chunk-736JCWLM.js → chunk-M2DPGFZ4.js} +10 -0
- package/dist/cli-delegate/{chunk-H6F2CQFI.js → chunk-NR7HSN3P.js} +2 -2
- package/dist/cli-delegate/{chunk-2TGMGLBL.js → chunk-SFGARMAB.js} +11 -1
- package/dist/cli-delegate/{chunk-DS6BLDMG.js → chunk-U7CMAIOC.js} +7 -2
- package/dist/cli-delegate/cmd_conformance.js +3 -3
- package/dist/cli-delegate/cmd_doctor.js +2 -2
- package/dist/cli-delegate/cmd_export.js +3 -3
- package/dist/cli-delegate/cmd_migrate.js +1 -1
- package/dist/cli-delegate/cmd_prune.js +3 -3
- package/dist/cli-delegate/cmd_refresh.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +8 -1
- package/dist/cli-delegate/cmd_settings_migrate.js +3 -3
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +3 -3
- package/dist/cli-delegate/cmd_uninstall.js +3 -3
- package/dist/cli-delegate/cmd_upgrade.js +1 -1
- package/dist/cli-delegate/cmd_validate.js +3 -3
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +12 -12
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/dispatch.js +530 -323
- package/dist/install/install.mjs +18 -4
- package/dist/mcp/registry-manifest.json +1 -1
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +25 -5
- package/dist/router.json +1 -1
- package/dist/server/schemas/settings.js +5 -0
- package/dist/server/schemas/settings.js.map +1 -1
- package/docs/contracts/settings-classes.md +4 -2
- package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +46 -4
- package/package.json +2 -2
- package/src/agent-src/templates/AGENTS.md +1 -1
- package/src/config/agent-settings.template.yml +32 -0
- package/src/config/gate-coverage.yml +12 -0
- package/src/scripts/_cli/cmd_doctor.ts +32 -0
- package/src/scripts/_cli/cmd_migrate.ts +26 -3
- package/src/scripts/_cli/cmd_refresh.ts +29 -0
- package/src/scripts/_cli/cmd_session_recycle.ts +40 -2
- package/src/scripts/_lib/hook_settings.ts +79 -0
- package/src/scripts/_lib/ui_surface.ts +97 -0
- package/src/scripts/capture_skill_catalogue.ts +483 -0
- package/src/scripts/hook_manifest.yaml +18 -3
- package/src/scripts/hooks/concern_registry.ts +2 -0
- package/src/scripts/hooks/design_slop_hook.ts +1 -1
- package/src/scripts/hooks/ui_route_nudge_hook.ts +250 -0
- package/src/scripts/lint_roadmap_complexity.ts +60 -0
- package/src/scripts/lint_rule_skill_pack_reach.ts +263 -0
- package/src/scripts/report_consultation_rate.ts +358 -0
- package/src/scripts/report_skill_activation.ts +5 -1
- package/src/server/schemas/settings.ts +9 -0
- /package/dist/agent-src/skills/design-system-capture/{reference → references}/design-system-json.md +0 -0
- /package/dist/agent-src/skills/prediction-pool-optimizer/{reference → references}/ev-fixtures.md +0 -0
- /package/dist/agent-src/skills/prediction-pool-optimizer/{reference → references}/odds-and-bonus.md +0 -0
- /package/dist/agent-src/skills/react-shadcn-ui/{reference → references}/registry.md +0 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* report_consultation_rate — the consultation half of the pre-registered
|
|
4
|
+
* frontend-skill metrics, computed from transcripts.
|
|
5
|
+
*
|
|
6
|
+
* WHAT IT MEASURES, AND WHY ONLY HALF.
|
|
7
|
+
* `agents/settings/contexts/skill-catalogue-baseline.md` pre-registers two
|
|
8
|
+
* rates. Exactly one of them is mechanically computable, and the reason the
|
|
9
|
+
* other is not is a roadmap non-goal rather than an oversight:
|
|
10
|
+
*
|
|
11
|
+
* CONSULTATION RATE — share of UI-write turns in a session that happened
|
|
12
|
+
* AFTER the session opened a design surface. Every input is a tool call:
|
|
13
|
+
* the write, its path, the prior read. Computable, and computed here.
|
|
14
|
+
*
|
|
15
|
+
* DISCHARGE RATE — share of UI-write turns followed by a review verdict
|
|
16
|
+
* that is "render-scoped or explicitly static-scoped". A verdict is PROSE.
|
|
17
|
+
* Deciding whether a paragraph is a scoped verdict means matching prose,
|
|
18
|
+
* which the roadmap's non-goals forbid ("no prose-matching missed-activation
|
|
19
|
+
* detector", the FC-8 boundary the round-6 phase drew). So this script does
|
|
20
|
+
* NOT compute it, and does not approximate it under its name.
|
|
21
|
+
*
|
|
22
|
+
* What it offers instead is a labelled PROXY: how often a UI-write turn is
|
|
23
|
+
* followed, in the same session, by opening `design-review`. That is a
|
|
24
|
+
* consultation-shaped fact about the review skill, not a fact about a verdict —
|
|
25
|
+
* an agent can open the skill and still write "looks good". Reported under its
|
|
26
|
+
* own name so it can never be quoted as the discharge rate.
|
|
27
|
+
*
|
|
28
|
+
* ONE EVENT STREAM, NOT TWO. The UI-write and consultation predicates are
|
|
29
|
+
* imported from the `ui-route-nudge` concern rather than restated. A metric
|
|
30
|
+
* whose denominator drifted from the nudge's trigger would make any later A/B
|
|
31
|
+
* uninterpretable — the two would be measuring different populations while
|
|
32
|
+
* appearing to measure one.
|
|
33
|
+
*
|
|
34
|
+
* PRIVACY, stated at the precision the code actually holds. The text report is
|
|
35
|
+
* counts and rates only. The `--json` form additionally echoes the SCAN ROOT
|
|
36
|
+
* (`store`) — the directory the caller pointed at, which is a local path and is
|
|
37
|
+
* the one thing a reader needs to know what was measured. Nothing else crosses:
|
|
38
|
+
* no transcript filename, no session id, no path from inside a session, no
|
|
39
|
+
* prompt or file content. The measurement reads those and keeps none of them.
|
|
40
|
+
*
|
|
41
|
+
* Exit: 0 always, except a usage/IO error (1). Advisory — it gates nothing.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
import * as fs from 'node:fs';
|
|
45
|
+
import * as os from 'node:os';
|
|
46
|
+
import * as path from 'node:path';
|
|
47
|
+
import { fileURLToPath } from 'node:url';
|
|
48
|
+
|
|
49
|
+
import {
|
|
50
|
+
isConsultation,
|
|
51
|
+
isUiWrite,
|
|
52
|
+
type ToolEvent,
|
|
53
|
+
} from './hooks/ui_route_nudge_hook.js';
|
|
54
|
+
|
|
55
|
+
const _HERE = fileURLToPath(import.meta.url);
|
|
56
|
+
|
|
57
|
+
/** Tool names that propose new file content on this host. */
|
|
58
|
+
const WRITE_TOOLS = new Set(['Write', 'Edit', 'NotebookEdit', 'MultiEdit']);
|
|
59
|
+
|
|
60
|
+
/** The skill whose opening the discharge PROXY counts. */
|
|
61
|
+
const REVIEW_SURFACE = 'design-review';
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* One transcript tool call, tagged with the assistant turn it belongs to.
|
|
65
|
+
*
|
|
66
|
+
* The turn index is what makes the published unit honest. The pre-registered
|
|
67
|
+
* metric is a share of UI *turns*; an earlier version counted `tool_use` parts
|
|
68
|
+
* and published the count as turns, which inflates any turn that writes two
|
|
69
|
+
* files and mixes a per-session numerator with a per-call denominator.
|
|
70
|
+
*/
|
|
71
|
+
export interface TimedEvent {
|
|
72
|
+
event: ToolEvent;
|
|
73
|
+
turn: number;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface SessionMeasurement {
|
|
77
|
+
/** Assistant turns containing at least one UI write. */
|
|
78
|
+
uiWriteTurns: number;
|
|
79
|
+
/** Those turns that followed a consultation in the same session. */
|
|
80
|
+
consulted: number;
|
|
81
|
+
/** Those turns followed later by opening the review skill. */
|
|
82
|
+
reviewOpenedAfter: number;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface RateReport {
|
|
86
|
+
store: string;
|
|
87
|
+
/** False when the store directory does not exist — never the same as empty. */
|
|
88
|
+
storeExists: boolean;
|
|
89
|
+
/** Transcripts read. Capped by --limit; the cap is reported when it bites. */
|
|
90
|
+
sessions: number;
|
|
91
|
+
/** True when --limit truncated the store rather than reading all of it. */
|
|
92
|
+
truncated: boolean;
|
|
93
|
+
sessionsWithUiWrite: number;
|
|
94
|
+
/** Assistant turns containing at least one UI write — the denominator. */
|
|
95
|
+
uiWriteTurns: number;
|
|
96
|
+
consulted: number;
|
|
97
|
+
reviewOpenedAfter: number;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Default transcript store for a project directory.
|
|
102
|
+
*
|
|
103
|
+
* The host flattens the path by replacing BOTH separators and dots — a worktree
|
|
104
|
+
* at `<repo>/.claude/worktrees/x` lands in `…-agent-config--claude-worktrees-x`,
|
|
105
|
+
* with two dashes where `/.` was. Replacing only `/` (the shape the older census
|
|
106
|
+
* helper carries) produces a directory that does not exist, and the caller then
|
|
107
|
+
* reads a clean zero out of a store it never found.
|
|
108
|
+
*/
|
|
109
|
+
export function defaultStore(cwd: string): string {
|
|
110
|
+
return path.join(os.homedir(), '.claude', 'projects', cwd.replace(/[/.]/g, '-'));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Map one transcript `tool_use` part onto the event shape the nudge decides
|
|
115
|
+
* over. Returns null for parts that carry no path — they cannot be either a
|
|
116
|
+
* UI write or a consultation.
|
|
117
|
+
*/
|
|
118
|
+
export function toolUseToEvent(part: Record<string, unknown>): ToolEvent | null {
|
|
119
|
+
const name = part['name'];
|
|
120
|
+
const input = part['input'];
|
|
121
|
+
if (typeof name !== 'string' || input === null || typeof input !== 'object') return null;
|
|
122
|
+
const ti = input as Record<string, unknown>;
|
|
123
|
+
const fileVal = ti['file_path'] ?? ti['path'] ?? ti['filePath'] ?? ti['notebook_path'];
|
|
124
|
+
if (typeof fileVal !== 'string' || fileVal === '') return null;
|
|
125
|
+
return { file: fileVal, isWrite: WRITE_TOOLS.has(name) };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Walk one session's tool events in order and measure it.
|
|
130
|
+
*
|
|
131
|
+
* Shares the nudge's PREDICATES (`isUiWrite`, `isConsultation`) but not its
|
|
132
|
+
* `decide`, deliberately: `decide` stops warning after `MAX_NUDGES`, which is
|
|
133
|
+
* correct for a reminder and wrong for a denominator — measuring through it
|
|
134
|
+
* would silently drop every UI write past the second one in a session.
|
|
135
|
+
*
|
|
136
|
+
* Ordering is load-bearing. A consultation counts only for writes that come
|
|
137
|
+
* after it; a session that opens `fe-design` at the end does not retroactively
|
|
138
|
+
* consult for the file it already wrote.
|
|
139
|
+
*/
|
|
140
|
+
export function measureSession(timed: readonly TimedEvent[]): SessionMeasurement {
|
|
141
|
+
const out: SessionMeasurement = { uiWriteTurns: 0, consulted: 0, reviewOpenedAfter: 0 };
|
|
142
|
+
let consulted = false;
|
|
143
|
+
/** turn -> was the session already consulted when that turn first wrote UI */
|
|
144
|
+
const writeTurns = new Map<number, boolean>();
|
|
145
|
+
let lastReviewOpenAt = -1;
|
|
146
|
+
const reviewOpenPositions: number[] = [];
|
|
147
|
+
const writePositions: Array<{ position: number; turn: number }> = [];
|
|
148
|
+
|
|
149
|
+
timed.forEach(({ event, turn }, position) => {
|
|
150
|
+
if (isUiWrite(event)) {
|
|
151
|
+
if (!writeTurns.has(turn)) {
|
|
152
|
+
writeTurns.set(turn, consulted);
|
|
153
|
+
writePositions.push({ position, turn });
|
|
154
|
+
}
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (isConsultation(event)) {
|
|
158
|
+
consulted = true;
|
|
159
|
+
if (event.file.replace(/\\/g, '/').toLowerCase().includes(`skills/${REVIEW_SURFACE}/`)) {
|
|
160
|
+
reviewOpenPositions.push(position);
|
|
161
|
+
lastReviewOpenAt = position;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
out.uiWriteTurns = writeTurns.size;
|
|
167
|
+
for (const wasConsulted of writeTurns.values()) if (wasConsulted) out.consulted += 1;
|
|
168
|
+
// A single max comparison, not a scan per write: the proxy asks only whether
|
|
169
|
+
// ANY review-open follows, so the last one decides every earlier write.
|
|
170
|
+
void reviewOpenPositions;
|
|
171
|
+
for (const { position } of writePositions) {
|
|
172
|
+
if (lastReviewOpenAt > position) out.reviewOpenedAfter += 1;
|
|
173
|
+
}
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Read every `tool_use` part of one transcript file, in order. */
|
|
178
|
+
export function readSessionEvents(file: string): TimedEvent[] {
|
|
179
|
+
const events: TimedEvent[] = [];
|
|
180
|
+
let turn = -1;
|
|
181
|
+
for (const line of fs.readFileSync(file, 'utf8').split('\n')) {
|
|
182
|
+
if (!line.trim()) continue;
|
|
183
|
+
let entry: Record<string, unknown>;
|
|
184
|
+
try {
|
|
185
|
+
entry = JSON.parse(line) as Record<string, unknown>;
|
|
186
|
+
} catch {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (entry['type'] !== 'assistant') continue;
|
|
190
|
+
turn += 1;
|
|
191
|
+
const msg = entry['message'];
|
|
192
|
+
const content =
|
|
193
|
+
msg !== null && typeof msg === 'object'
|
|
194
|
+
? (msg as Record<string, unknown>)['content']
|
|
195
|
+
: null;
|
|
196
|
+
if (!Array.isArray(content)) continue;
|
|
197
|
+
for (const part of content) {
|
|
198
|
+
if (part === null || typeof part !== 'object') continue;
|
|
199
|
+
const p = part as Record<string, unknown>;
|
|
200
|
+
if (p['type'] !== 'tool_use') continue;
|
|
201
|
+
const event = toolUseToEvent(p);
|
|
202
|
+
if (event) events.push({ event, turn });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return events;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function measureStore(store: string, limit: number): RateReport {
|
|
209
|
+
const report: RateReport = {
|
|
210
|
+
store,
|
|
211
|
+
storeExists: fs.existsSync(store),
|
|
212
|
+
sessions: 0,
|
|
213
|
+
truncated: false,
|
|
214
|
+
sessionsWithUiWrite: 0,
|
|
215
|
+
uiWriteTurns: 0,
|
|
216
|
+
consulted: 0,
|
|
217
|
+
reviewOpenedAfter: 0,
|
|
218
|
+
};
|
|
219
|
+
// A store that is not there and a store with nothing in it are different
|
|
220
|
+
// findings, and only one of them is about the agent. Collapsing them into
|
|
221
|
+
// one clean zero is the failure this repo has recorded four times.
|
|
222
|
+
if (!report.storeExists) return report;
|
|
223
|
+
|
|
224
|
+
// mtime is read ONCE per file, not inside the comparator: a comparator that
|
|
225
|
+
// stats is O(n log n) syscalls and throws ENOENT if a live store rotates a
|
|
226
|
+
// transcript mid-sort — a measurement must not crash on the store being used.
|
|
227
|
+
const all = fs
|
|
228
|
+
.readdirSync(store)
|
|
229
|
+
.filter((f) => f.endsWith('.jsonl'))
|
|
230
|
+
.map((f) => path.join(store, f))
|
|
231
|
+
.map((file) => {
|
|
232
|
+
let mtimeMs = 0;
|
|
233
|
+
try {
|
|
234
|
+
mtimeMs = fs.statSync(file).mtimeMs;
|
|
235
|
+
} catch {
|
|
236
|
+
/* vanished between readdir and stat — sort it last, skip it below */
|
|
237
|
+
}
|
|
238
|
+
return { file, mtimeMs };
|
|
239
|
+
})
|
|
240
|
+
.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
241
|
+
|
|
242
|
+
const files = all.slice(0, limit).map((f) => f.file);
|
|
243
|
+
report.truncated = all.length > files.length;
|
|
244
|
+
report.sessions = files.length;
|
|
245
|
+
for (const file of files) {
|
|
246
|
+
let measured: SessionMeasurement;
|
|
247
|
+
try {
|
|
248
|
+
measured = measureSession(readSessionEvents(file));
|
|
249
|
+
} catch {
|
|
250
|
+
continue; // rotated or unreadable transcript — skip, never crash
|
|
251
|
+
}
|
|
252
|
+
if (measured.uiWriteTurns === 0) continue;
|
|
253
|
+
report.sessionsWithUiWrite += 1;
|
|
254
|
+
report.uiWriteTurns += measured.uiWriteTurns;
|
|
255
|
+
report.consulted += measured.consulted;
|
|
256
|
+
report.reviewOpenedAfter += measured.reviewOpenedAfter;
|
|
257
|
+
}
|
|
258
|
+
return report;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function pct(n: number, d: number): string {
|
|
262
|
+
return d === 0 ? 'n/a' : `${((100 * n) / d).toFixed(1)}%`;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export function render(report: RateReport, threshold: number): string {
|
|
266
|
+
const lines: string[] = [];
|
|
267
|
+
lines.push('consultation rate — advisory, gates on nothing');
|
|
268
|
+
lines.push('');
|
|
269
|
+
if (!report.storeExists) {
|
|
270
|
+
lines.push(' ❌ transcript store not found — nothing was measured.');
|
|
271
|
+
lines.push(' This is not a rate of zero. Point --store at a real store;');
|
|
272
|
+
lines.push(' `ls ~/.claude/projects` lists them.');
|
|
273
|
+
return lines.join('\n');
|
|
274
|
+
}
|
|
275
|
+
lines.push(
|
|
276
|
+
` sessions scanned ${report.sessions}${report.truncated ? ' (TRUNCATED by --limit)' : ''}`,
|
|
277
|
+
);
|
|
278
|
+
lines.push(` sessions with a UI write ${report.sessionsWithUiWrite}`);
|
|
279
|
+
lines.push(` UI-write turns ${report.uiWriteTurns}`);
|
|
280
|
+
lines.push('');
|
|
281
|
+
lines.push(
|
|
282
|
+
` CONSULTATION RATE ${pct(report.consulted, report.uiWriteTurns)} (${report.consulted}/${report.uiWriteTurns})`,
|
|
283
|
+
);
|
|
284
|
+
lines.push(
|
|
285
|
+
` discharge PROXY (review opened) ${pct(report.reviewOpenedAfter, report.uiWriteTurns)} (${report.reviewOpenedAfter}/${report.uiWriteTurns})`,
|
|
286
|
+
);
|
|
287
|
+
lines.push('');
|
|
288
|
+
|
|
289
|
+
if (report.sessionsWithUiWrite < threshold) {
|
|
290
|
+
lines.push(
|
|
291
|
+
` ⚠ ${report.sessionsWithUiWrite} session(s) with a UI write — below the pre-registered floor of ${threshold}.`,
|
|
292
|
+
);
|
|
293
|
+
lines.push(' Report the rate as provisional; it is not yet a baseline.');
|
|
294
|
+
lines.push('');
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
lines.push(' The PROXY is not the discharge rate. It counts a UI-write turn followed by');
|
|
298
|
+
lines.push(' opening `design-review` in the same session. A verdict is prose, and matching');
|
|
299
|
+
lines.push(' prose is a roadmap non-goal, so the discharge rate stays unmeasured rather');
|
|
300
|
+
lines.push(' than approximated under its own name.');
|
|
301
|
+
return lines.join('\n');
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Value of `flag`, or `fallback` when it is absent.
|
|
306
|
+
*
|
|
307
|
+
* Throws when the flag is present with no value after it. The earlier version
|
|
308
|
+
* returned the fallback there, so `--store` as the last argv element silently
|
|
309
|
+
* measured the DEFAULT store — and since the text report never prints the scan
|
|
310
|
+
* root, the substitution was invisible in exactly the output a reader would
|
|
311
|
+
* publish. A usage error is the only honest answer to "you asked for a store
|
|
312
|
+
* and named none".
|
|
313
|
+
*/
|
|
314
|
+
function argValue(flag: string, fallback: string): string {
|
|
315
|
+
const index = process.argv.indexOf(flag);
|
|
316
|
+
if (index === -1) return fallback;
|
|
317
|
+
const value = process.argv[index + 1];
|
|
318
|
+
if (value === undefined || value.startsWith('--')) {
|
|
319
|
+
throw new Error(`${flag} requires a value`);
|
|
320
|
+
}
|
|
321
|
+
return value;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** Parse a positive-integer flag, or fail loudly. Never silently NaN. */
|
|
325
|
+
function intArg(flag: string, fallback: string): number {
|
|
326
|
+
const raw = argValue(flag, fallback);
|
|
327
|
+
const parsed = Number.parseInt(raw, 10);
|
|
328
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
329
|
+
throw new Error(`${flag} must be a positive integer (got ${JSON.stringify(raw)})`);
|
|
330
|
+
}
|
|
331
|
+
return parsed;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function main(): number {
|
|
335
|
+
const store = argValue('--store', defaultStore(process.cwd()));
|
|
336
|
+
const limit = intArg('--limit', '200');
|
|
337
|
+
// Validated for the same reason --limit is, and it was not: a NaN threshold
|
|
338
|
+
// makes `sessionsWithUiWrite < NaN` false, which SUPPRESSES the provisional
|
|
339
|
+
// warning — the run then reads as a settled baseline over any corpus.
|
|
340
|
+
const threshold = intArg('--min-sessions', '20');
|
|
341
|
+
|
|
342
|
+
const report = measureStore(store, limit);
|
|
343
|
+
if (process.argv.includes('--json')) {
|
|
344
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
345
|
+
} else {
|
|
346
|
+
process.stdout.write(`${render(report, threshold)}\n`);
|
|
347
|
+
}
|
|
348
|
+
return 0;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(_HERE)) {
|
|
352
|
+
try {
|
|
353
|
+
process.exit(main());
|
|
354
|
+
} catch (error) {
|
|
355
|
+
process.stderr.write(`❌ ${(error as Error).message}\n`);
|
|
356
|
+
process.exit(1);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
@@ -114,7 +114,11 @@ export interface UsageReport {
|
|
|
114
114
|
|
|
115
115
|
/** Default store for the current working directory, mangled the way the host does. */
|
|
116
116
|
export function defaultStore(cwd: string): string {
|
|
117
|
-
|
|
117
|
+
// Dots are flattened too, not only separators: a worktree at
|
|
118
|
+
// `<repo>/.claude/worktrees/x` lands in `…-agent-config--claude-worktrees-x`.
|
|
119
|
+
// Replacing only `/` returns a directory that does not exist, and the caller
|
|
120
|
+
// then reads a clean zero out of a store it never found.
|
|
121
|
+
return path.join(os.homedir(), '.claude', 'projects', cwd.replace(/[/.]/g, '-'));
|
|
118
122
|
}
|
|
119
123
|
|
|
120
124
|
export function measureUsage(store: string, limit: number): UsageReport {
|
|
@@ -36,6 +36,7 @@ const modelAutoSwitch = z.enum(['auto', 'suggest', 'off']);
|
|
|
36
36
|
const leanProjectionMode = z.enum(['eager-all', 'thin']);
|
|
37
37
|
const projectionMode = z.enum(['legacy-all', 'scoped']);
|
|
38
38
|
const memoryCadence = z.enum(['auto', 'always', 'never']);
|
|
39
|
+
const projectAudience = z.enum(['self', 'internal', 'client', 'public']);
|
|
39
40
|
|
|
40
41
|
export const settingsSchema = z.object({
|
|
41
42
|
agent_config_version: z.string().default('').describe(
|
|
@@ -142,6 +143,9 @@ export const settingsSchema = z.object({
|
|
|
142
143
|
improvement_pr_branch_prefix: z.string().default('improve/agent-').describe(
|
|
143
144
|
'Branch-name prefix for improvement PRs the agent opens against project.upstream_repo (e.g. "improve/agent-add-react-skill"). Pick a prefix your repo conventions allow.',
|
|
144
145
|
),
|
|
146
|
+
audience: projectAudience.default('public').describe(
|
|
147
|
+
'Who this project is built for — read by the demand gate (§ 8-pre of docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md), whose L0-L4 ladder measures MARKET demand and is meaningless where no market is intended. self = a tool its maintainer builds for themselves; the gate is inert and work is classified L-self (build) instead of being deferred for lack of a user population nobody wants. internal = a team tool; only "what breaks without it?" survives. client = built for a named client, who is the requester rather than a market segment. public (default) = a product with an intended market; full three-question gate, behaviour unchanged from before this key existed.',
|
|
148
|
+
),
|
|
145
149
|
}),
|
|
146
150
|
github: z.object({
|
|
147
151
|
pr_reply_method: replyMethod.default('create_review_comment').describe(
|
|
@@ -515,6 +519,11 @@ export const settingsSchema = z.object({
|
|
|
515
519
|
'PreToolUse anti-slop nudge (road-to-anti-slop-detector Phase 3). Default off. When on, runs the lint_design_slop registry against about-to-be-written UI content and WARNS (never blocks) on P0/P1 aesthetic tells (side-stripe, gradient-text, magic z-index, …). Flags are rebuttable via DESIGN.md / design-slop-disable. Anti-loop: a file::rule signature surfaced 3x goes silent. Host-limited convenience layer; the universal gate is the lint_design_slop linter/CI.',
|
|
516
520
|
),
|
|
517
521
|
}).default({}),
|
|
522
|
+
ui_route_nudge: z.object({
|
|
523
|
+
enabled: z.boolean().default(false).describe(
|
|
524
|
+
'PreToolUse UI-route nudge (road-to-frontend-skill-application Phase 4). Default off. When on, a Write/Edit to a UI surface with no design consultation latched this session WARNS (never blocks) naming the route — run existing-ui-audit, then the fe-design loop. A read or search touching fe-design / existing-ui-audit / design-review / design-intelligence latches consultation and silences it for the session. Anti-loop: at most 2 nudges per session. It does not read the rules: the UI-surface decision comes from _lib/ui_surface.ts and no code parses rule frontmatter, so this runs parallel to the two UI rules rather than consuming their triggers, and a test keeps the sets from drifting. It is a nudge, so their enforced_by: none stays accurate.',
|
|
525
|
+
),
|
|
526
|
+
}).default({}),
|
|
518
527
|
code_graph: z.object({
|
|
519
528
|
enabled: z.boolean().default(false).describe(
|
|
520
529
|
'PreToolUse code-graph nudge (ADR-124 Phase 4). Default off. When on AND a native code-graph cache or a consumer-shipped graph.json/SCIP index is present, warns once per session (never blocks) as the agent is about to Grep/Glob or Read a source file — query the graph first for who-calls/where-used/impact questions (or rebuild if stale, build if absent). Source G’s strict block-first-read mode is deliberately un-ported.',
|
/package/dist/agent-src/skills/design-system-capture/{reference → references}/design-system-json.md
RENAMED
|
File without changes
|
/package/dist/agent-src/skills/prediction-pool-optimizer/{reference → references}/ev-fixtures.md
RENAMED
|
File without changes
|
/package/dist/agent-src/skills/prediction-pool-optimizer/{reference → references}/odds-and-bonus.md
RENAMED
|
File without changes
|
|
File without changes
|