@warpgogol/forge 3.0.0 → 4.1.1
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/AGENTS.md +18 -3
- package/bin/cli.ts +15 -8
- package/os/adr/adr.module.ts +24 -23
- package/os/adr/index.ts +1 -1
- package/os/audit/audit.module.ts +13 -8
- package/os/audit/index.ts +1 -1
- package/os/compass/compass.module.ts +22 -19
- package/os/core/core.module.ts +842 -864
- package/os/core/handlers/package-health.ts +21 -0
- package/os/core/index.ts +1 -1
- package/os/exploration/exploration.module.ts +19 -16
- package/os/exploration/index.ts +2 -2
- package/os/mission/index.ts +1 -1
- package/os/mission/mission.module.ts +13 -8
- package/os/naming/index.ts +1 -1
- package/os/naming/naming-convention.ts +1 -0
- package/os/naming/naming.module.ts +13 -7
- package/os/notes/index.ts +2 -2
- package/os/notes/notes.module.ts +20 -17
- package/os/plan/index.ts +1 -1
- package/os/plan/plan.module.ts +13 -8
- package/os/plugin/plugin.module.ts +10 -8
- package/os/program/program.module.ts +22 -20
- package/os/rfc/handlers/implement-stamp.ts +17 -1
- package/os/rfc/index.ts +1 -1
- package/os/rfc/rfc-0000-template.md +3 -2
- package/os/rfc/rfc.module.ts +54 -84
- package/os/rfc/types.ts +1 -0
- package/os/session/handlers/metrics-aggregate.ts +208 -0
- package/os/session/handlers/metrics-rfc.ts +339 -0
- package/os/session/handlers/metrics-session.ts +209 -0
- package/os/session/handlers/save.ts +19 -0
- package/os/session/index.ts +16 -1
- package/os/session/session.module.ts +133 -107
- package/os/session/types.ts +99 -0
- package/os/spec/spec.module.ts +28 -29
- package/os/werkstatt/werkstatt.module.ts +12 -9
- package/os/workflow/index.ts +1 -1
- package/os/workflow/workflow.module.ts +18 -12
- package/package.json +3 -1
- package/src/forge-module.ts +24 -10
- package/src/index.ts +12 -12
- package/src/onboarding/scaffold.ts +6 -4
- package/src/tests/metrics-rfc-1053.test.ts +355 -0
- package/src/types/werkstatt-engine-shims.d.ts +126 -48
- package/src/types/werkstatt-shared-shims.d.ts +6 -0
package/os/rfc/rfc.module.ts
CHANGED
|
@@ -15,13 +15,8 @@
|
|
|
15
15
|
|
|
16
16
|
import type { ForgeModule } from "../../src/forge-module.ts";
|
|
17
17
|
|
|
18
|
-
export
|
|
19
|
-
|
|
20
|
-
version: "0.1.0",
|
|
21
|
-
runtime: "autonomous",
|
|
22
|
-
|
|
23
|
-
async register(registry) {
|
|
24
|
-
const {
|
|
18
|
+
export async function createForgeRfcModule(): Promise<ForgeModule> {
|
|
19
|
+
const {
|
|
25
20
|
runRfcList,
|
|
26
21
|
runRfcCreate,
|
|
27
22
|
runRfcNextId,
|
|
@@ -41,8 +36,15 @@ export const forgeRfcModule: ForgeModule = {
|
|
|
41
36
|
const { runRfcArchive } = await import("./handlers/archive.ts");
|
|
42
37
|
const { runRfcPipelineStatus } = await import("./handlers/pipeline-status.ts");
|
|
43
38
|
const { runRfcImplementStamp } = await import("./handlers/implement-stamp.ts");
|
|
44
|
-
// ── rfc.list
|
|
45
|
-
|
|
39
|
+
// ── rfc.list ─────────────────────────────────────────────────────────────;
|
|
40
|
+
return {
|
|
41
|
+
name: "rfc",
|
|
42
|
+
version: "0.1.0",
|
|
43
|
+
runtime: "autonomous",
|
|
44
|
+
|
|
45
|
+
declarations: [],
|
|
46
|
+
commands: [
|
|
47
|
+
{
|
|
46
48
|
name: "rfc.list",
|
|
47
49
|
description:
|
|
48
50
|
"List all RFCs. Filter with --status, --kind, --owner flags. " +
|
|
@@ -67,10 +69,8 @@ export const forgeRfcModule: ForgeModule = {
|
|
|
67
69
|
},
|
|
68
70
|
reads: ["docs/rfcs/**/*.md"],
|
|
69
71
|
execute: runRfcList,
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// ── rfc.create ───────────────────────────────────────────────────────────
|
|
73
|
-
registry.registerCommand({
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
74
|
name: "rfc.create",
|
|
75
75
|
description:
|
|
76
76
|
"Create a new RFC draft from the template. " +
|
|
@@ -99,10 +99,8 @@ export const forgeRfcModule: ForgeModule = {
|
|
|
99
99
|
},
|
|
100
100
|
},
|
|
101
101
|
execute: runRfcCreate,
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// ── rfc.next-id ───────────────────────────────────────────────────────────
|
|
105
|
-
registry.registerCommand({
|
|
102
|
+
},
|
|
103
|
+
{
|
|
106
104
|
name: "rfc.next-id",
|
|
107
105
|
description:
|
|
108
106
|
"Return the next free RFC number (max existing + 1) by scanning docs/rfcs/ recursively including archive/.",
|
|
@@ -110,10 +108,8 @@ export const forgeRfcModule: ForgeModule = {
|
|
|
110
108
|
flags: {},
|
|
111
109
|
reads: ["docs/rfcs/**/*.md"],
|
|
112
110
|
execute: runRfcNextId,
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// ── rfc.validate ─────────────────────────────────────────────────────────
|
|
116
|
-
registry.registerCommand({
|
|
111
|
+
},
|
|
112
|
+
{
|
|
117
113
|
name: "rfc.validate",
|
|
118
114
|
contract: "rfc",
|
|
119
115
|
rules: [],
|
|
@@ -128,10 +124,8 @@ export const forgeRfcModule: ForgeModule = {
|
|
|
128
124
|
},
|
|
129
125
|
reads: ["docs/rfcs/**/*.md"],
|
|
130
126
|
execute: runRfcValidate,
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// ── rfc.command-lifecycle.validate ───────────────────────────────────────
|
|
134
|
-
registry.registerCommand({
|
|
127
|
+
},
|
|
128
|
+
{
|
|
135
129
|
name: "rfc.command-lifecycle.validate",
|
|
136
130
|
contract: "rfc",
|
|
137
131
|
rules: [],
|
|
@@ -145,10 +139,8 @@ export const forgeRfcModule: ForgeModule = {
|
|
|
145
139
|
reads: ["docs/rfcs/**/*.md"],
|
|
146
140
|
cacheable: false,
|
|
147
141
|
execute: runRfcCommandLifecycleValidate,
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// ── rfc.check ────────────────────────────────────────────────────────────
|
|
151
|
-
registry.registerCommand({
|
|
142
|
+
},
|
|
143
|
+
{
|
|
152
144
|
name: "rfc.check",
|
|
153
145
|
contract: "rfc",
|
|
154
146
|
rules: [],
|
|
@@ -166,10 +158,8 @@ export const forgeRfcModule: ForgeModule = {
|
|
|
166
158
|
},
|
|
167
159
|
reads: ["docs/rfcs/**/*.md"],
|
|
168
160
|
execute: runRfcCheck,
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// ── rfc.index.generate ───────────────────────────────────────────────────
|
|
172
|
-
registry.registerCommand({
|
|
161
|
+
},
|
|
162
|
+
{
|
|
173
163
|
name: "rfc.index.generate",
|
|
174
164
|
description:
|
|
175
165
|
"Emit a machine-readable relationship index of all RFCs (id, status, dates, " +
|
|
@@ -186,10 +176,8 @@ export const forgeRfcModule: ForgeModule = {
|
|
|
186
176
|
reads: ["docs/rfcs/**/*.md"],
|
|
187
177
|
cacheable: false,
|
|
188
178
|
execute: runRfcIndexGenerate,
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// ── rfc.index.validate ───────────────────────────────────────────────────
|
|
192
|
-
registry.registerCommand({
|
|
179
|
+
},
|
|
180
|
+
{
|
|
193
181
|
name: "rfc.index.validate",
|
|
194
182
|
contract: "rfc",
|
|
195
183
|
rules: [],
|
|
@@ -201,10 +189,8 @@ export const forgeRfcModule: ForgeModule = {
|
|
|
201
189
|
flags: {},
|
|
202
190
|
reads: ["docs/rfcs/**/*.md", "docs/rfcs/index.yaml"],
|
|
203
191
|
execute: runRfcIndexValidate,
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// ── rfc.graph ────────────────────────────────────────────────────────────
|
|
207
|
-
registry.registerCommand({
|
|
192
|
+
},
|
|
193
|
+
{
|
|
208
194
|
name: "rfc.graph",
|
|
209
195
|
description:
|
|
210
196
|
"Print one RFC's relationship neighbours (supersedes/supersededBy/amends/" +
|
|
@@ -215,10 +201,8 @@ export const forgeRfcModule: ForgeModule = {
|
|
|
215
201
|
},
|
|
216
202
|
reads: ["docs/rfcs/**/*.md"],
|
|
217
203
|
execute: runRfcGraph,
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// ── rfc.acceptance.run ───────────────────────────────────────────────────
|
|
221
|
-
registry.registerCommand({
|
|
204
|
+
},
|
|
205
|
+
{
|
|
222
206
|
name: "rfc.acceptance.run",
|
|
223
207
|
description:
|
|
224
208
|
"RFC-0268: execute the acceptance: probes declared in an RFC's frontmatter and report " +
|
|
@@ -236,10 +220,8 @@ export const forgeRfcModule: ForgeModule = {
|
|
|
236
220
|
reads: ["docs/rfcs/**/*.md"],
|
|
237
221
|
cacheable: false,
|
|
238
222
|
execute: runRfcAcceptanceRun,
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// ── rfc.verification.emit ────────────────────────────────────────────────
|
|
242
|
-
registry.registerCommand({
|
|
223
|
+
},
|
|
224
|
+
{
|
|
243
225
|
name: "rfc.verification.emit",
|
|
244
226
|
description:
|
|
245
227
|
"RFC-0330: execute acceptance probes for target RFC(s) and write per-RFC verification " +
|
|
@@ -258,10 +240,8 @@ export const forgeRfcModule: ForgeModule = {
|
|
|
258
240
|
},
|
|
259
241
|
},
|
|
260
242
|
execute: runRfcVerificationEmit,
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// ── rfc.verification.refresh ────────────────────────────────────────────
|
|
264
|
-
registry.registerCommand({
|
|
243
|
+
},
|
|
244
|
+
{
|
|
265
245
|
name: "rfc.verification.refresh",
|
|
266
246
|
description:
|
|
267
247
|
"RFC-0999: re-run acceptance probes for implemented RFC(s) and update verification " +
|
|
@@ -285,10 +265,8 @@ export const forgeRfcModule: ForgeModule = {
|
|
|
285
265
|
},
|
|
286
266
|
},
|
|
287
267
|
execute: runRfcVerificationRefresh,
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// ── rfc.dna.trace.validate ───────────────────────────────────────────────
|
|
291
|
-
registry.registerCommand({
|
|
268
|
+
},
|
|
269
|
+
{
|
|
292
270
|
name: "rfc.dna.trace.validate",
|
|
293
271
|
contract: "rfc",
|
|
294
272
|
rules: [],
|
|
@@ -305,10 +283,8 @@ export const forgeRfcModule: ForgeModule = {
|
|
|
305
283
|
},
|
|
306
284
|
reads: ["docs/rfcs/**/*.md", "docs/architecture-dna.md"],
|
|
307
285
|
execute: runRfcDnaTraceValidate,
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// ── rfc.dna.trace.generate ───────────────────────────────────────────────
|
|
311
|
-
registry.registerCommand({
|
|
286
|
+
},
|
|
287
|
+
{
|
|
312
288
|
name: "rfc.dna.trace.generate",
|
|
313
289
|
description:
|
|
314
290
|
"RFC-0331: generate docs/rfcs/dna-trace.generated.yaml — the machine-readable " +
|
|
@@ -320,10 +296,8 @@ export const forgeRfcModule: ForgeModule = {
|
|
|
320
296
|
cacheable: false,
|
|
321
297
|
flags: {},
|
|
322
298
|
execute: runRfcDnaTraceGenerate,
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// ── rfc.decision-log.generate ────────────────────────────────────────────
|
|
326
|
-
registry.registerCommand({
|
|
299
|
+
},
|
|
300
|
+
{
|
|
327
301
|
name: "rfc.decision-log.generate",
|
|
328
302
|
description:
|
|
329
303
|
"RFC-0329: generate docs/rfcs/decision-log.generated.yaml and .md aggregating every " +
|
|
@@ -341,10 +315,8 @@ export const forgeRfcModule: ForgeModule = {
|
|
|
341
315
|
},
|
|
342
316
|
},
|
|
343
317
|
execute: runRfcDecisionLogGenerate,
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// ── rfc.supersede.propose ─────────────────────────────────────────────────
|
|
347
|
-
registry.registerCommand({
|
|
318
|
+
},
|
|
319
|
+
{
|
|
348
320
|
name: "rfc.supersede.propose",
|
|
349
321
|
description:
|
|
350
322
|
"RFC-0334: escalate a blocked implementation by creating a draft superseding RFC " +
|
|
@@ -373,10 +345,8 @@ export const forgeRfcModule: ForgeModule = {
|
|
|
373
345
|
},
|
|
374
346
|
},
|
|
375
347
|
execute: runRfcSupersedePropose,
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// ── rfc.archive ───────────────────────────────────────────────────────────
|
|
379
|
-
registry.registerCommand({
|
|
348
|
+
},
|
|
349
|
+
{
|
|
380
350
|
name: "rfc.archive",
|
|
381
351
|
description:
|
|
382
352
|
"Move terminal-status RFC files (implemented, rejected, superseded) into " +
|
|
@@ -400,10 +370,8 @@ export const forgeRfcModule: ForgeModule = {
|
|
|
400
370
|
},
|
|
401
371
|
},
|
|
402
372
|
execute: runRfcArchive,
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// ── rfc.pipeline.status ──────────────────────────────────────────────────
|
|
406
|
-
registry.registerCommand({
|
|
373
|
+
},
|
|
374
|
+
{
|
|
407
375
|
name: "rfc.pipeline.status",
|
|
408
376
|
description:
|
|
409
377
|
"Report the pipeline status of RFCs — which steps (audit, enhance, plan, implement) " +
|
|
@@ -416,10 +384,8 @@ export const forgeRfcModule: ForgeModule = {
|
|
|
416
384
|
},
|
|
417
385
|
reads: ["docs/rfcs/**/*.md"],
|
|
418
386
|
execute: runRfcPipelineStatus,
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// ── rfc.implement.stamp ──────────────────────────────────────────────────
|
|
422
|
-
registry.registerCommand({
|
|
387
|
+
},
|
|
388
|
+
{
|
|
423
389
|
name: "rfc.implement.stamp",
|
|
424
390
|
description:
|
|
425
391
|
"RFC-0476: the exclusive atomic path for accepted → implemented transitions. " +
|
|
@@ -450,6 +416,10 @@ export const forgeRfcModule: ForgeModule = {
|
|
|
450
416
|
},
|
|
451
417
|
},
|
|
452
418
|
execute: runRfcImplementStamp,
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
|
|
419
|
+
}
|
|
420
|
+
],
|
|
421
|
+
pipelines: [
|
|
422
|
+
|
|
423
|
+
]};
|
|
424
|
+
}
|
|
425
|
+
;
|
package/os/rfc/types.ts
CHANGED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>
|
|
4
|
+
RFC-1053: metrics.aggregate command handler — scans docs/metrics/ files and
|
|
5
|
+
produces per-skill statistics (invocations, avgFindings, avgFixIterations,
|
|
6
|
+
avgDurationMs). Supports --skill, --since, --until filters. Returns empty
|
|
7
|
+
result when no metrics files exist.
|
|
8
|
+
</purpose>
|
|
9
|
+
<non-goals>
|
|
10
|
+
<item>Does not write files — read-only aggregation.</item>
|
|
11
|
+
<item>Does not index metrics files — scans on each invocation.</item>
|
|
12
|
+
</non-goals>
|
|
13
|
+
</MODULE_CONTRACT>
|
|
14
|
+
<CHANGE_SUMMARY>
|
|
15
|
+
<item>RFC-1053: initial metrics.aggregate command handler.</item>
|
|
16
|
+
</CHANGE_SUMMARY>
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
20
|
+
import { join } from "node:path";
|
|
21
|
+
import { parse as yamlParse } from "yaml";
|
|
22
|
+
|
|
23
|
+
import { METRICS_DIR, type MetricsAggregateResult, type SkillAggregate, type RfcMetrics, type SessionMetrics } from "../types.ts";
|
|
24
|
+
import type { ForgeCommandInput, ForgeCommandResult, ForgeRuntimeContext } from "../../../src/types.ts";
|
|
25
|
+
|
|
26
|
+
async function readYamlFile<T>(filePath: string): Promise<T | null> {
|
|
27
|
+
try {
|
|
28
|
+
const content = await readFile(filePath, "utf-8");
|
|
29
|
+
return yamlParse(content) as T;
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function runMetricsAggregate(
|
|
36
|
+
input: ForgeCommandInput,
|
|
37
|
+
context: ForgeRuntimeContext,
|
|
38
|
+
): Promise<ForgeCommandResult<MetricsAggregateResult>> {
|
|
39
|
+
const { workspaceRoot, outputFormat } = context;
|
|
40
|
+
const skillFilter = input.flags["skill"] as string | undefined;
|
|
41
|
+
const since = input.flags["since"] as string | undefined;
|
|
42
|
+
const until = input.flags["until"] as string | undefined;
|
|
43
|
+
|
|
44
|
+
const rfcMetricsDir = join(workspaceRoot, METRICS_DIR, "rfcs");
|
|
45
|
+
const sessionMetricsDir = join(workspaceRoot, METRICS_DIR, "sessions");
|
|
46
|
+
|
|
47
|
+
let rfcFiles: string[] = [];
|
|
48
|
+
let sessionFiles: string[] = [];
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
rfcFiles = await readdir(rfcMetricsDir);
|
|
52
|
+
} catch {
|
|
53
|
+
// No RFC metrics directory yet
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
sessionFiles = await readdir(sessionMetricsDir);
|
|
58
|
+
} catch {
|
|
59
|
+
// No session metrics directory yet
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const rfcMetricsList: RfcMetrics[] = [];
|
|
63
|
+
for (const file of rfcFiles) {
|
|
64
|
+
if (!file.endsWith(".metrics.yaml")) continue;
|
|
65
|
+
const metrics = await readYamlFile<RfcMetrics>(join(rfcMetricsDir, file));
|
|
66
|
+
if (metrics) rfcMetricsList.push(metrics);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const sessionMetricsList: SessionMetrics[] = [];
|
|
70
|
+
for (const file of sessionFiles) {
|
|
71
|
+
if (!file.endsWith(".metrics.yaml")) continue;
|
|
72
|
+
const metrics = await readYamlFile<SessionMetrics>(join(sessionMetricsDir, file));
|
|
73
|
+
if (metrics) sessionMetricsList.push(metrics);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Apply date filters
|
|
77
|
+
const sinceDate = since ? new Date(since) : null;
|
|
78
|
+
const untilDate = until ? new Date(until) : null;
|
|
79
|
+
|
|
80
|
+
const filteredRfcs = rfcMetricsList.filter((m) => {
|
|
81
|
+
if (!m.generatedAt) return true;
|
|
82
|
+
const d = new Date(m.generatedAt);
|
|
83
|
+
if (isNaN(d.getTime())) return true;
|
|
84
|
+
if (sinceDate && d < sinceDate) return false;
|
|
85
|
+
if (untilDate && d > untilDate) return false;
|
|
86
|
+
return true;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const filteredSessions = sessionMetricsList.filter((m) => {
|
|
90
|
+
if (!m.generatedAt) return true;
|
|
91
|
+
const d = new Date(m.generatedAt);
|
|
92
|
+
if (isNaN(d.getTime())) return true;
|
|
93
|
+
if (sinceDate && d < sinceDate) return false;
|
|
94
|
+
if (untilDate && d > untilDate) return false;
|
|
95
|
+
return true;
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Aggregate per-skill
|
|
99
|
+
const skillData = new Map<
|
|
100
|
+
string,
|
|
101
|
+
{
|
|
102
|
+
invocations: number;
|
|
103
|
+
findingsSum: number;
|
|
104
|
+
findingsCount: number;
|
|
105
|
+
fixIterationsSum: number;
|
|
106
|
+
fixIterationsCount: number;
|
|
107
|
+
durationSum: number;
|
|
108
|
+
durationCount: number;
|
|
109
|
+
}
|
|
110
|
+
>();
|
|
111
|
+
|
|
112
|
+
function recordSkill(skill: string, data: Partial<{
|
|
113
|
+
findings: number;
|
|
114
|
+
fixIterations: number;
|
|
115
|
+
durationMs: number;
|
|
116
|
+
}>) {
|
|
117
|
+
let entry = skillData.get(skill);
|
|
118
|
+
if (!entry) {
|
|
119
|
+
entry = {
|
|
120
|
+
invocations: 0,
|
|
121
|
+
findingsSum: 0,
|
|
122
|
+
findingsCount: 0,
|
|
123
|
+
fixIterationsSum: 0,
|
|
124
|
+
fixIterationsCount: 0,
|
|
125
|
+
durationSum: 0,
|
|
126
|
+
durationCount: 0,
|
|
127
|
+
};
|
|
128
|
+
skillData.set(skill, entry);
|
|
129
|
+
}
|
|
130
|
+
entry.invocations++;
|
|
131
|
+
if (data.findings !== undefined) {
|
|
132
|
+
entry.findingsSum += data.findings;
|
|
133
|
+
entry.findingsCount++;
|
|
134
|
+
}
|
|
135
|
+
if (data.fixIterations !== undefined) {
|
|
136
|
+
entry.fixIterationsSum += data.fixIterations;
|
|
137
|
+
entry.fixIterationsCount++;
|
|
138
|
+
}
|
|
139
|
+
if (data.durationMs !== undefined) {
|
|
140
|
+
entry.durationSum += data.durationMs;
|
|
141
|
+
entry.durationCount++;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// From RFC metrics: pipeline steps (skill invocations), review findings, fix iterations
|
|
146
|
+
for (const rfc of filteredRfcs) {
|
|
147
|
+
for (const step of rfc.pipeline) {
|
|
148
|
+
if (step.skill) {
|
|
149
|
+
recordSkill(step.skill, {});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (rfc.review && rfc.review.findingsCount !== undefined) {
|
|
153
|
+
recordSkill("fo-review", { findings: rfc.review.findingsCount });
|
|
154
|
+
}
|
|
155
|
+
if (rfc.fix) {
|
|
156
|
+
recordSkill("fo-fix", { fixIterations: rfc.fix.iterations });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// From session metrics: skill invocations with durations
|
|
161
|
+
for (const session of filteredSessions) {
|
|
162
|
+
for (const inv of session.skills) {
|
|
163
|
+
recordSkill(inv.skill, {
|
|
164
|
+
durationMs: inv.approximateDurationMs ?? undefined,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
let perSkill: SkillAggregate[] = [];
|
|
170
|
+
for (const [skill, data] of skillData) {
|
|
171
|
+
if (skillFilter && skill !== skillFilter) continue;
|
|
172
|
+
perSkill.push({
|
|
173
|
+
skill,
|
|
174
|
+
invocations: data.invocations,
|
|
175
|
+
avgFindings: data.findingsCount > 0 ? data.findingsSum / data.findingsCount : null,
|
|
176
|
+
avgFixIterations: data.fixIterationsCount > 0 ? data.fixIterationsSum / data.fixIterationsCount : null,
|
|
177
|
+
avgDurationMs: data.durationCount > 0 ? data.durationSum / data.durationCount : null,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Sort by invocations descending
|
|
182
|
+
perSkill.sort((a, b) => b.invocations - a.invocations);
|
|
183
|
+
|
|
184
|
+
if (skillFilter && perSkill.length === 0) {
|
|
185
|
+
perSkill = [];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const result: MetricsAggregateResult = {
|
|
189
|
+
command: "metrics.aggregate",
|
|
190
|
+
status: "ok",
|
|
191
|
+
skill: (skillFilter as string) ?? "all",
|
|
192
|
+
since: since ?? null,
|
|
193
|
+
until: until ?? null,
|
|
194
|
+
perSkill,
|
|
195
|
+
totalRfcs: filteredRfcs.length,
|
|
196
|
+
totalSessions: filteredSessions.length,
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
if (outputFormat === "pretty") {
|
|
200
|
+
context.logger.info(`[metrics.aggregate] ${perSkill.length} skill(s), ${filteredRfcs.length} RFC(s), ${filteredSessions.length} session(s)`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
data: result,
|
|
205
|
+
exitCode: 0,
|
|
206
|
+
summary: `[metrics.aggregate] ${perSkill.length} skill(s) across ${filteredRfcs.length} RFC(s) and ${filteredSessions.length} session(s)`,
|
|
207
|
+
};
|
|
208
|
+
}
|