selftune 0.2.21 → 0.2.23
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 +15 -8
- package/apps/local-dashboard/dist/assets/index-CwOtTrUS.css +1 -0
- package/apps/local-dashboard/dist/assets/index-f1HQpbeH.js +59 -0
- package/apps/local-dashboard/dist/assets/vendor-ui-jVSaIZey.js +12 -0
- package/apps/local-dashboard/dist/index.html +3 -3
- package/cli/selftune/adapters/cline/hook.ts +167 -0
- package/cli/selftune/adapters/cline/install.ts +197 -0
- package/cli/selftune/adapters/codex/hook.ts +296 -0
- package/cli/selftune/adapters/codex/install.ts +289 -0
- package/cli/selftune/adapters/opencode/hook.ts +222 -0
- package/cli/selftune/adapters/opencode/install.ts +543 -0
- package/cli/selftune/adapters/pi/hook.ts +273 -0
- package/cli/selftune/adapters/pi/install.ts +207 -0
- package/cli/selftune/constants.ts +10 -1
- package/cli/selftune/dashboard-contract.ts +14 -0
- package/cli/selftune/evolution/engines/judge-engine.ts +96 -0
- package/cli/selftune/evolution/engines/replay-engine.ts +158 -0
- package/cli/selftune/evolution/evidence.ts +2 -6
- package/cli/selftune/evolution/evolve-body.ts +73 -20
- package/cli/selftune/evolution/validate-body.ts +78 -42
- package/cli/selftune/evolution/validate-routing.ts +45 -104
- package/cli/selftune/hooks/auto-activate.ts +43 -37
- package/cli/selftune/hooks/skill-eval.ts +2 -1
- package/cli/selftune/hooks-shared/git-metadata.ts +149 -0
- package/cli/selftune/hooks-shared/hook-output.ts +105 -0
- package/cli/selftune/hooks-shared/normalize.ts +196 -0
- package/cli/selftune/hooks-shared/session-state.ts +76 -0
- package/cli/selftune/hooks-shared/skill-paths.ts +50 -0
- package/cli/selftune/hooks-shared/stdin-dispatch.ts +59 -0
- package/cli/selftune/hooks-shared/types.ts +91 -0
- package/cli/selftune/index.ts +76 -6
- package/cli/selftune/ingestors/pi-ingest.ts +726 -0
- package/cli/selftune/init.ts +11 -1
- package/cli/selftune/localdb/direct-write.ts +85 -0
- package/cli/selftune/localdb/materialize.ts +6 -7
- package/cli/selftune/localdb/queries.ts +126 -0
- package/cli/selftune/localdb/schema.ts +38 -0
- package/cli/selftune/observability.ts +8 -1
- package/cli/selftune/orchestrate.ts +43 -0
- package/cli/selftune/registry/client.ts +74 -0
- package/cli/selftune/registry/history.ts +54 -0
- package/cli/selftune/registry/index.ts +90 -0
- package/cli/selftune/registry/install.ts +141 -0
- package/cli/selftune/registry/list.ts +44 -0
- package/cli/selftune/registry/push.ts +171 -0
- package/cli/selftune/registry/rollback.ts +49 -0
- package/cli/selftune/registry/status.ts +62 -0
- package/cli/selftune/registry/sync.ts +125 -0
- package/cli/selftune/repair/skill-usage.ts +4 -1
- package/cli/selftune/status.ts +31 -0
- package/cli/selftune/sync.ts +127 -23
- package/cli/selftune/types.ts +2 -1
- package/cli/selftune/utils/jsonl.ts +1 -30
- package/cli/selftune/utils/llm-call.ts +99 -34
- package/cli/selftune/utils/skill-discovery.ts +22 -0
- package/node_modules/@selftune/telemetry-contract/fixtures/evidence-only-push.ts +1 -1
- package/node_modules/@selftune/telemetry-contract/fixtures/golden.test.ts +0 -1
- package/node_modules/@selftune/telemetry-contract/fixtures/partial-push-unresolved-parents.ts +1 -1
- package/node_modules/@selftune/telemetry-contract/package.json +1 -1
- package/node_modules/@selftune/telemetry-contract/src/index.ts +1 -0
- package/node_modules/@selftune/telemetry-contract/src/schemas.ts +22 -4
- package/node_modules/@selftune/telemetry-contract/src/types.ts +1 -12
- package/node_modules/@selftune/telemetry-contract/tests/compatibility.test.ts +0 -1
- package/package.json +1 -1
- package/packages/telemetry-contract/fixtures/evidence-only-push.ts +1 -1
- package/packages/telemetry-contract/fixtures/golden.test.ts +0 -1
- package/packages/telemetry-contract/fixtures/partial-push-unresolved-parents.ts +1 -1
- package/packages/telemetry-contract/package.json +1 -1
- package/packages/telemetry-contract/src/index.ts +1 -0
- package/packages/telemetry-contract/src/schemas.ts +22 -4
- package/packages/telemetry-contract/src/types.ts +1 -12
- package/packages/telemetry-contract/tests/compatibility.test.ts +0 -1
- package/packages/ui/AGENTS.md +16 -0
- package/packages/ui/README.md +1 -1
- package/packages/ui/package.json +1 -1
- package/packages/ui/src/components/ActivityTimeline.tsx +152 -168
- package/packages/ui/src/components/AnalyticsCharts.tsx +344 -0
- package/packages/ui/src/components/EvidenceViewer.tsx +153 -443
- package/packages/ui/src/components/EvolutionTimeline.tsx +34 -87
- package/packages/ui/src/components/InfoTip.tsx +1 -2
- package/packages/ui/src/components/InvocationsPanel.tsx +413 -0
- package/packages/ui/src/components/JobHistoryTimeline.tsx +156 -0
- package/packages/ui/src/components/OrchestrateRunsPanel.tsx +18 -36
- package/packages/ui/src/components/OverviewPanels.tsx +652 -0
- package/packages/ui/src/components/PipelineStatusBar.tsx +65 -0
- package/packages/ui/src/components/SkillReportGuide.tsx +215 -0
- package/packages/ui/src/components/SkillReportPanels.tsx +919 -0
- package/packages/ui/src/components/SkillsLibrary.tsx +437 -0
- package/packages/ui/src/components/index.ts +56 -1
- package/packages/ui/src/components/section-cards.tsx +18 -35
- package/packages/ui/src/components/skill-health-grid.tsx +47 -37
- package/packages/ui/src/lib/constants.tsx +0 -1
- package/packages/ui/src/primitives/card.tsx +1 -1
- package/packages/ui/src/primitives/checkbox.tsx +1 -1
- package/packages/ui/src/primitives/dropdown-menu.tsx +2 -2
- package/packages/ui/src/primitives/select.tsx +2 -2
- package/packages/ui/src/types.ts +172 -4
- package/skill/SKILL.md +26 -2
- package/skill/Workflows/Ingest.md +60 -2
- package/skill/Workflows/Initialize.md +54 -9
- package/skill/Workflows/PlatformHooks.md +109 -0
- package/skill/Workflows/Registry.md +99 -0
- package/skill/Workflows/Sync.md +3 -1
- package/apps/local-dashboard/dist/assets/index-D8O-RG1I.js +0 -60
- package/apps/local-dashboard/dist/assets/index-_EcLywDg.css +0 -1
- package/apps/local-dashboard/dist/assets/vendor-ui-CGEmUayx.js +0 -12
- package/cli/selftune/utils/html.ts +0 -27
- package/packages/ui/src/components/RecentActivityFeed.tsx +0 -117
package/cli/selftune/sync.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* - Codex rollout logs
|
|
8
8
|
* - OpenCode session history
|
|
9
9
|
* - OpenClaw session history
|
|
10
|
+
* - Pi session history
|
|
10
11
|
*
|
|
11
12
|
* After syncing raw session/query/telemetry records, it rebuilds the repaired
|
|
12
13
|
* skill-usage overlay from Claude transcripts and Codex rollouts so monitoring,
|
|
@@ -25,6 +26,8 @@ import {
|
|
|
25
26
|
OPENCLAW_AGENTS_DIR,
|
|
26
27
|
OPENCLAW_INGEST_MARKER,
|
|
27
28
|
OPENCODE_INGEST_MARKER,
|
|
29
|
+
PI_INGEST_MARKER,
|
|
30
|
+
PI_SESSIONS_DIR,
|
|
28
31
|
QUERY_LOG,
|
|
29
32
|
REPAIRED_SKILL_LOG,
|
|
30
33
|
REPAIRED_SKILL_SESSIONS_MARKER,
|
|
@@ -56,7 +59,14 @@ import {
|
|
|
56
59
|
readSessionsFromSqlite,
|
|
57
60
|
writeSession as writeOpenCodeSession,
|
|
58
61
|
} from "./ingestors/opencode-ingest.js";
|
|
62
|
+
import {
|
|
63
|
+
findPiSessions,
|
|
64
|
+
findPiSkillNames,
|
|
65
|
+
parsePiSession,
|
|
66
|
+
writeSession as writePiSession,
|
|
67
|
+
} from "./ingestors/pi-ingest.js";
|
|
59
68
|
import { getDb } from "./localdb/db.js";
|
|
69
|
+
import { writeCronRunToDb } from "./localdb/direct-write.js";
|
|
60
70
|
import { querySkillUsageRecords } from "./localdb/queries.js";
|
|
61
71
|
import {
|
|
62
72
|
persistRepairedSkillUsageToDb,
|
|
@@ -91,6 +101,7 @@ export interface SyncResult {
|
|
|
91
101
|
codex: SyncStepResult;
|
|
92
102
|
opencode: SyncStepResult;
|
|
93
103
|
openclaw: SyncStepResult;
|
|
104
|
+
pi: SyncStepResult;
|
|
94
105
|
};
|
|
95
106
|
repair: {
|
|
96
107
|
ran: boolean;
|
|
@@ -113,6 +124,7 @@ export interface SyncOptions {
|
|
|
113
124
|
codexHome: string;
|
|
114
125
|
opencodeDataDir: string;
|
|
115
126
|
openclawAgentsDir: string;
|
|
127
|
+
piSessionsDir: string;
|
|
116
128
|
skillLogPath: string;
|
|
117
129
|
repairedSkillLogPath: string;
|
|
118
130
|
repairedSessionsPath: string;
|
|
@@ -123,6 +135,7 @@ export interface SyncOptions {
|
|
|
123
135
|
syncCodex: boolean;
|
|
124
136
|
syncOpenCode: boolean;
|
|
125
137
|
syncOpenClaw: boolean;
|
|
138
|
+
syncPi: boolean;
|
|
126
139
|
rebuildSkillUsage: boolean;
|
|
127
140
|
}
|
|
128
141
|
|
|
@@ -133,6 +146,7 @@ export interface SyncDeps {
|
|
|
133
146
|
syncCodex?: (options: SyncOptions) => SyncStepResult;
|
|
134
147
|
syncOpenCode?: (options: SyncOptions) => SyncStepResult;
|
|
135
148
|
syncOpenClaw?: (options: SyncOptions) => SyncStepResult;
|
|
149
|
+
syncPi?: (options: SyncOptions) => SyncStepResult;
|
|
136
150
|
rebuildSkillUsage?: (options: SyncOptions) => {
|
|
137
151
|
repairedSessions: number;
|
|
138
152
|
repairedRecords: number;
|
|
@@ -154,6 +168,7 @@ export function createDefaultSyncOptions(overrides: Partial<SyncOptions> = {}):
|
|
|
154
168
|
codexHome: DEFAULT_CODEX_HOME,
|
|
155
169
|
opencodeDataDir: DEFAULT_OPENCODE_DATA_DIR,
|
|
156
170
|
openclawAgentsDir: OPENCLAW_AGENTS_DIR,
|
|
171
|
+
piSessionsDir: PI_SESSIONS_DIR,
|
|
157
172
|
skillLogPath: SKILL_LOG,
|
|
158
173
|
repairedSkillLogPath: REPAIRED_SKILL_LOG,
|
|
159
174
|
repairedSessionsPath: REPAIRED_SKILL_SESSIONS_MARKER,
|
|
@@ -163,6 +178,7 @@ export function createDefaultSyncOptions(overrides: Partial<SyncOptions> = {}):
|
|
|
163
178
|
syncCodex: true,
|
|
164
179
|
syncOpenCode: true,
|
|
165
180
|
syncOpenClaw: true,
|
|
181
|
+
syncPi: true,
|
|
166
182
|
rebuildSkillUsage: true,
|
|
167
183
|
...overrides,
|
|
168
184
|
};
|
|
@@ -356,6 +372,45 @@ function syncOpenClawSource(
|
|
|
356
372
|
};
|
|
357
373
|
}
|
|
358
374
|
|
|
375
|
+
function syncPiSource(options: SyncOptions, onProgress?: SyncProgressCallback): SyncStepResult {
|
|
376
|
+
if (!existsSync(options.piSessionsDir)) {
|
|
377
|
+
return { available: false, scanned: 0, synced: 0, skipped: 0 };
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
onProgress?.("scanning Pi sessions...");
|
|
381
|
+
const sinceTs = options.since ? options.since.getTime() : null;
|
|
382
|
+
const allSessions = findPiSessions(options.piSessionsDir, sinceTs);
|
|
383
|
+
const skillNames = findPiSkillNames();
|
|
384
|
+
const alreadyIngested = options.force ? new Set<string>() : loadMarker(PI_INGEST_MARKER);
|
|
385
|
+
const pending = allSessions.filter((session) => !alreadyIngested.has(session.sessionId));
|
|
386
|
+
onProgress?.(`found ${allSessions.length} sessions, ${pending.length} pending`);
|
|
387
|
+
const newIngested = new Set<string>();
|
|
388
|
+
let synced = 0;
|
|
389
|
+
let skipped = 0;
|
|
390
|
+
|
|
391
|
+
for (const sessionFile of pending) {
|
|
392
|
+
const session = parsePiSession(sessionFile.filePath, skillNames);
|
|
393
|
+
if (!session.session_id || !session.timestamp) {
|
|
394
|
+
skipped += 1;
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
writePiSession(session, options.dryRun);
|
|
398
|
+
newIngested.add(sessionFile.sessionId);
|
|
399
|
+
synced += 1;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (!options.dryRun && newIngested.size > 0) {
|
|
403
|
+
saveMarker(PI_INGEST_MARKER, new Set([...alreadyIngested, ...newIngested]));
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return {
|
|
407
|
+
available: true,
|
|
408
|
+
scanned: allSessions.length,
|
|
409
|
+
synced,
|
|
410
|
+
skipped,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
359
414
|
function rebuildSkillUsageOverlay(
|
|
360
415
|
options: SyncOptions,
|
|
361
416
|
onProgress?: SyncProgressCallback,
|
|
@@ -445,6 +500,7 @@ export function syncSources(
|
|
|
445
500
|
const runCodex = deps.syncCodex;
|
|
446
501
|
const runOpenCode = deps.syncOpenCode;
|
|
447
502
|
const runOpenClaw = deps.syncOpenClaw;
|
|
503
|
+
const runPi = deps.syncPi;
|
|
448
504
|
const runRepair = deps.rebuildSkillUsage;
|
|
449
505
|
const runCreatorContributions = deps.stageCreatorContributions;
|
|
450
506
|
const db = getDb();
|
|
@@ -485,6 +541,10 @@ export function syncSources(
|
|
|
485
541
|
)
|
|
486
542
|
: disabledStep;
|
|
487
543
|
|
|
544
|
+
const pi = options.syncPi
|
|
545
|
+
? timePhase("pi", () => (runPi ? runPi(options) : syncPiSource(options, onProgress)), timings)
|
|
546
|
+
: disabledStep;
|
|
547
|
+
|
|
488
548
|
const repair = options.rebuildSkillUsage
|
|
489
549
|
? timePhase(
|
|
490
550
|
"repair",
|
|
@@ -512,10 +572,10 @@ export function syncSources(
|
|
|
512
572
|
|
|
513
573
|
const totalElapsed = Math.round(performance.now() - totalStart);
|
|
514
574
|
|
|
515
|
-
|
|
575
|
+
const syncResult: SyncResult = {
|
|
516
576
|
since: options.since ? options.since.toISOString() : null,
|
|
517
577
|
dry_run: options.dryRun,
|
|
518
|
-
sources: { claude, codex, opencode, openclaw },
|
|
578
|
+
sources: { claude, codex, opencode, openclaw, pi },
|
|
519
579
|
repair: {
|
|
520
580
|
ran: options.rebuildSkillUsage,
|
|
521
581
|
repaired_sessions: repair.repairedSessions,
|
|
@@ -526,6 +586,8 @@ export function syncSources(
|
|
|
526
586
|
timings,
|
|
527
587
|
total_elapsed_ms: totalElapsed,
|
|
528
588
|
};
|
|
589
|
+
|
|
590
|
+
return syncResult;
|
|
529
591
|
}
|
|
530
592
|
|
|
531
593
|
function formatMs(ms: number): string {
|
|
@@ -549,6 +611,7 @@ export async function cliMain(): Promise<void> {
|
|
|
549
611
|
"codex-home": { type: "string", default: DEFAULT_CODEX_HOME },
|
|
550
612
|
"opencode-data-dir": { type: "string", default: DEFAULT_OPENCODE_DATA_DIR },
|
|
551
613
|
"openclaw-agents-dir": { type: "string", default: OPENCLAW_AGENTS_DIR },
|
|
614
|
+
"pi-sessions-dir": { type: "string", default: PI_SESSIONS_DIR },
|
|
552
615
|
"skill-log": { type: "string", default: SKILL_LOG },
|
|
553
616
|
"repaired-skill-log": { type: "string", default: REPAIRED_SKILL_LOG },
|
|
554
617
|
"repaired-sessions-marker": { type: "string", default: REPAIRED_SKILL_SESSIONS_MARKER },
|
|
@@ -559,6 +622,7 @@ export async function cliMain(): Promise<void> {
|
|
|
559
622
|
"no-codex": { type: "boolean", default: false },
|
|
560
623
|
"no-opencode": { type: "boolean", default: false },
|
|
561
624
|
"no-openclaw": { type: "boolean", default: false },
|
|
625
|
+
"no-pi": { type: "boolean", default: false },
|
|
562
626
|
"no-repair": { type: "boolean", default: false },
|
|
563
627
|
json: { type: "boolean", default: false },
|
|
564
628
|
help: { type: "boolean", short: "h", default: false },
|
|
@@ -577,6 +641,7 @@ Options:
|
|
|
577
641
|
--codex-home <dir> Codex home directory (default: ~/.codex)
|
|
578
642
|
--opencode-data-dir <dir> OpenCode data directory
|
|
579
643
|
--openclaw-agents-dir <dir> OpenClaw agents directory
|
|
644
|
+
--pi-sessions-dir <dir> Pi sessions directory
|
|
580
645
|
--skill-log <path> Raw skill usage log path
|
|
581
646
|
--repaired-skill-log <path> Repaired overlay log path
|
|
582
647
|
--repaired-sessions-marker <p> Repaired session marker path
|
|
@@ -587,6 +652,7 @@ Options:
|
|
|
587
652
|
--no-codex Skip Codex rollout ingest
|
|
588
653
|
--no-opencode Skip OpenCode ingest
|
|
589
654
|
--no-openclaw Skip OpenClaw ingest
|
|
655
|
+
--no-pi Skip Pi ingest
|
|
590
656
|
--no-repair Skip rebuilt skill-usage overlay
|
|
591
657
|
--json Output raw JSON instead of human-readable summary
|
|
592
658
|
-h, --help Show this help`);
|
|
@@ -622,27 +688,64 @@ Options:
|
|
|
622
688
|
process.stderr.write(`selftune sync${flags.length ? ` ${flags.join(" ")}` : ""}\n`);
|
|
623
689
|
}
|
|
624
690
|
|
|
625
|
-
const
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
691
|
+
const syncStartedAt = new Date();
|
|
692
|
+
const syncStart = performance.now();
|
|
693
|
+
let result: SyncResult;
|
|
694
|
+
try {
|
|
695
|
+
result = syncSources(
|
|
696
|
+
createDefaultSyncOptions({
|
|
697
|
+
projectsDir: values["projects-dir"] ?? CLAUDE_CODE_PROJECTS_DIR,
|
|
698
|
+
codexHome: values["codex-home"] ?? DEFAULT_CODEX_HOME,
|
|
699
|
+
opencodeDataDir: values["opencode-data-dir"] ?? DEFAULT_OPENCODE_DATA_DIR,
|
|
700
|
+
openclawAgentsDir: values["openclaw-agents-dir"] ?? OPENCLAW_AGENTS_DIR,
|
|
701
|
+
piSessionsDir: values["pi-sessions-dir"] ?? PI_SESSIONS_DIR,
|
|
702
|
+
skillLogPath: values["skill-log"] ?? SKILL_LOG,
|
|
703
|
+
repairedSkillLogPath: values["repaired-skill-log"] ?? REPAIRED_SKILL_LOG,
|
|
704
|
+
repairedSessionsPath: values["repaired-sessions-marker"] ?? REPAIRED_SKILL_SESSIONS_MARKER,
|
|
705
|
+
since,
|
|
706
|
+
dryRun: values["dry-run"] ?? false,
|
|
707
|
+
force: values.force ?? false,
|
|
708
|
+
syncClaude: !(values["no-claude"] ?? false),
|
|
709
|
+
syncCodex: !(values["no-codex"] ?? false),
|
|
710
|
+
syncOpenCode: !(values["no-opencode"] ?? false),
|
|
711
|
+
syncOpenClaw: !(values["no-openclaw"] ?? false),
|
|
712
|
+
syncPi: !(values["no-pi"] ?? false),
|
|
713
|
+
rebuildSkillUsage: !(values["no-repair"] ?? false),
|
|
714
|
+
}),
|
|
715
|
+
{},
|
|
716
|
+
onProgress,
|
|
717
|
+
);
|
|
718
|
+
} catch (err) {
|
|
719
|
+
const syncElapsed = Math.round(performance.now() - syncStart);
|
|
720
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
721
|
+
writeCronRunToDb(getDb(), {
|
|
722
|
+
jobName: "sync",
|
|
723
|
+
startedAt: syncStartedAt.toISOString(),
|
|
724
|
+
elapsedMs: syncElapsed,
|
|
725
|
+
status: "error",
|
|
726
|
+
error: message,
|
|
727
|
+
});
|
|
728
|
+
throw err;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// Log successful sync run to unified cron_runs timeline
|
|
732
|
+
const syncElapsed = Math.round(performance.now() - syncStart);
|
|
733
|
+
const s = result.sources;
|
|
734
|
+
writeCronRunToDb(getDb(), {
|
|
735
|
+
jobName: "sync",
|
|
736
|
+
startedAt: syncStartedAt.toISOString(),
|
|
737
|
+
elapsedMs: syncElapsed,
|
|
738
|
+
status: "success",
|
|
739
|
+
metrics: {
|
|
740
|
+
total_synced:
|
|
741
|
+
s.claude.synced + s.codex.synced + s.opencode.synced + s.openclaw.synced + s.pi.synced,
|
|
742
|
+
claude_synced: s.claude.synced,
|
|
743
|
+
codex_synced: s.codex.synced,
|
|
744
|
+
opencode_synced: s.opencode.synced,
|
|
745
|
+
openclaw_synced: s.openclaw.synced,
|
|
746
|
+
pi_synced: s.pi.synced,
|
|
747
|
+
},
|
|
748
|
+
});
|
|
646
749
|
|
|
647
750
|
if (jsonOutput) {
|
|
648
751
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -662,6 +765,7 @@ Options:
|
|
|
662
765
|
process.stderr.write(
|
|
663
766
|
`${formatStepLine("OpenClaw", result.sources.openclaw, timingMap.get("openclaw"))}\n`,
|
|
664
767
|
);
|
|
768
|
+
process.stderr.write(`${formatStepLine("Pi", result.sources.pi, timingMap.get("pi"))}\n`);
|
|
665
769
|
|
|
666
770
|
if (result.repair.ran) {
|
|
667
771
|
const repairTiming = timingMap.get("repair");
|
package/cli/selftune/types.ts
CHANGED
|
@@ -34,7 +34,7 @@ export type AlphaLinkState =
|
|
|
34
34
|
| "ready";
|
|
35
35
|
|
|
36
36
|
export interface SelftuneConfig {
|
|
37
|
-
agent_type: "claude_code" | "codex" | "opencode" | "openclaw" | "unknown";
|
|
37
|
+
agent_type: "claude_code" | "codex" | "opencode" | "openclaw" | "pi" | "unknown";
|
|
38
38
|
cli_path: string;
|
|
39
39
|
llm_mode: "agent";
|
|
40
40
|
agent_cli: string | null;
|
|
@@ -738,6 +738,7 @@ export interface BodyValidationResult {
|
|
|
738
738
|
before_pass_rate?: number;
|
|
739
739
|
after_pass_rate?: number;
|
|
740
740
|
per_entry_results?: RoutingReplayEntryResult[];
|
|
741
|
+
before_entry_results?: RoutingReplayEntryResult[];
|
|
741
742
|
}
|
|
742
743
|
|
|
743
744
|
/** Configuration for which LLM model a role should use. */
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* JSONL read
|
|
2
|
+
* JSONL read utilities and marker file helpers.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
|
-
appendFileSync,
|
|
7
6
|
closeSync,
|
|
8
7
|
existsSync,
|
|
9
8
|
fstatSync,
|
|
@@ -15,10 +14,6 @@ import {
|
|
|
15
14
|
} from "node:fs";
|
|
16
15
|
import { dirname } from "node:path";
|
|
17
16
|
|
|
18
|
-
import { createLogger } from "./logging.js";
|
|
19
|
-
import type { LogType } from "./schema-validator.js";
|
|
20
|
-
import { validateRecord } from "./schema-validator.js";
|
|
21
|
-
|
|
22
17
|
/**
|
|
23
18
|
* Read a JSONL file and return parsed records.
|
|
24
19
|
* Skips blank lines and lines that fail to parse.
|
|
@@ -86,30 +81,6 @@ export function readJsonlFrom<T = Record<string, unknown>>(
|
|
|
86
81
|
}
|
|
87
82
|
}
|
|
88
83
|
|
|
89
|
-
/**
|
|
90
|
-
* Append a single record to a JSONL file. Creates parent directories if needed.
|
|
91
|
-
* When logType is provided, validates the record and logs warnings on failure
|
|
92
|
-
* but still writes the record (fail-open: hooks must never block).
|
|
93
|
-
*
|
|
94
|
-
* @deprecated Phase 3: JSONL writes removed. Retained for materializer/test utilities only.
|
|
95
|
-
*/
|
|
96
|
-
export function appendJsonl(path: string, record: unknown, logType?: LogType): void {
|
|
97
|
-
if (logType) {
|
|
98
|
-
const result = validateRecord(record, logType);
|
|
99
|
-
if (!result.valid) {
|
|
100
|
-
const logger = createLogger("jsonl");
|
|
101
|
-
for (const error of result.errors) {
|
|
102
|
-
logger.warn(`Validation warning for ${logType}: ${error}`);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
const dir = dirname(path);
|
|
107
|
-
if (!existsSync(dir)) {
|
|
108
|
-
mkdirSync(dir, { recursive: true });
|
|
109
|
-
}
|
|
110
|
-
appendFileSync(path, `${JSON.stringify(record)}\n`, "utf-8");
|
|
111
|
-
}
|
|
112
|
-
|
|
113
84
|
/**
|
|
114
85
|
* Load a marker file (JSON array of strings) for idempotent ingestion.
|
|
115
86
|
*/
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
* modules can reuse the same calling logic.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { readFileSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
10
10
|
import { tmpdir } from "node:os";
|
|
11
|
-
import { join } from "node:path";
|
|
11
|
+
import { dirname, join, resolve } from "node:path";
|
|
12
12
|
|
|
13
13
|
import { AGENT_CANDIDATES } from "../constants.js";
|
|
14
14
|
import { createLogger } from "./logging.js";
|
|
@@ -33,6 +33,40 @@ function resolveModelFlag(flag: string): string {
|
|
|
33
33
|
return CLAUDE_MODEL_ALIASES[flag] ?? flag;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Map selftune model aliases to OpenCode provider/model format.
|
|
38
|
+
* OpenCode uses "provider/model" syntax (e.g. "anthropic/claude-sonnet-4-20250514").
|
|
39
|
+
*/
|
|
40
|
+
const OPENCODE_MODEL_MAP: Record<string, string> = {
|
|
41
|
+
haiku: "anthropic/claude-haiku-4-5-20251001",
|
|
42
|
+
sonnet: "anthropic/claude-sonnet-4-20250514",
|
|
43
|
+
opus: "anthropic/claude-opus-4-20250514",
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/** Resolve a model alias to OpenCode's provider/model format. */
|
|
47
|
+
function resolveOpenCodeModel(flag: string): string {
|
|
48
|
+
return OPENCODE_MODEL_MAP[flag] ?? flag;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Bundled agent file loading (for codex inline prompt injection)
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
const BUNDLED_AGENT_DIR = resolve(dirname(import.meta.path), "..", "..", "..", "skill", "agents");
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Read the bundled agent markdown file and return its body (without frontmatter).
|
|
59
|
+
* Used by codex path to inline agent instructions into the prompt since codex
|
|
60
|
+
* has no --agent flag.
|
|
61
|
+
*/
|
|
62
|
+
function loadAgentInstructions(agentName: string): string | null {
|
|
63
|
+
const filePath = join(BUNDLED_AGENT_DIR, `${agentName}.md`);
|
|
64
|
+
if (!existsSync(filePath)) return null;
|
|
65
|
+
const content = readFileSync(filePath, "utf-8");
|
|
66
|
+
// Strip YAML frontmatter
|
|
67
|
+
return content.replace(/^---\n[\s\S]*?\n---\n*/, "").trim();
|
|
68
|
+
}
|
|
69
|
+
|
|
36
70
|
// ---------------------------------------------------------------------------
|
|
37
71
|
// Agent detection
|
|
38
72
|
// ---------------------------------------------------------------------------
|
|
@@ -155,7 +189,11 @@ export async function callViaAgent(
|
|
|
155
189
|
} else if (agent === "codex") {
|
|
156
190
|
cmd = ["codex", "exec", "--skip-git-repo-check", promptContent];
|
|
157
191
|
} else if (agent === "opencode") {
|
|
158
|
-
cmd = ["opencode", "
|
|
192
|
+
cmd = ["opencode", "run"];
|
|
193
|
+
if (modelFlag) {
|
|
194
|
+
cmd.push("--model", resolveOpenCodeModel(modelFlag));
|
|
195
|
+
}
|
|
196
|
+
cmd.push(promptContent);
|
|
159
197
|
} else {
|
|
160
198
|
throw new Error(`Unknown agent: ${agent}`);
|
|
161
199
|
}
|
|
@@ -222,9 +260,9 @@ export async function callViaAgent(
|
|
|
222
260
|
// Call LLM via named subagent (multi-turn, agentic)
|
|
223
261
|
// ---------------------------------------------------------------------------
|
|
224
262
|
|
|
225
|
-
/** Options for calling a named Claude Code
|
|
263
|
+
/** Options for calling a named subagent (Claude Code or OpenCode). */
|
|
226
264
|
export interface SubagentCallOptions {
|
|
227
|
-
/** Name of the subagent (synced into ~/.claude/agents/ by selftune init/update). */
|
|
265
|
+
/** Name of the subagent (synced into ~/.claude/agents/ or opencode.json by selftune init/update). */
|
|
228
266
|
agentName: string;
|
|
229
267
|
/** The task prompt for the subagent. */
|
|
230
268
|
prompt: string;
|
|
@@ -243,13 +281,13 @@ export interface SubagentCallOptions {
|
|
|
243
281
|
}
|
|
244
282
|
|
|
245
283
|
/**
|
|
246
|
-
* Call a named
|
|
247
|
-
*
|
|
248
|
-
*
|
|
284
|
+
* Call a named subagent in print mode. The subagent runs its multi-turn
|
|
285
|
+
* workflow (reading files, running commands, etc.) and returns the final
|
|
286
|
+
* text output.
|
|
249
287
|
*
|
|
250
|
-
*
|
|
251
|
-
* and
|
|
252
|
-
*
|
|
288
|
+
* Supports Claude Code (`claude --agent`), OpenCode (`opencode run --agent`),
|
|
289
|
+
* and Codex (`codex exec` with agent instructions inlined into the prompt).
|
|
290
|
+
* Auto-detects the available agent CLI.
|
|
253
291
|
*/
|
|
254
292
|
export async function callViaSubagent(options: SubagentCallOptions): Promise<string> {
|
|
255
293
|
const {
|
|
@@ -263,31 +301,58 @@ export async function callViaSubagent(options: SubagentCallOptions): Promise<str
|
|
|
263
301
|
allowedTools,
|
|
264
302
|
} = options;
|
|
265
303
|
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
agentName,
|
|
272
|
-
"--max-turns",
|
|
273
|
-
String(maxTurns),
|
|
274
|
-
];
|
|
275
|
-
|
|
276
|
-
if (appendSystemPrompt) {
|
|
277
|
-
cmd.push("--append-system-prompt", appendSystemPrompt);
|
|
278
|
-
}
|
|
279
|
-
if (modelFlag) {
|
|
280
|
-
const resolved = resolveModelFlag(modelFlag);
|
|
281
|
-
cmd.push("--model", resolved);
|
|
304
|
+
const agent = detectAgent();
|
|
305
|
+
if (!agent || (agent !== "claude" && agent !== "opencode" && agent !== "codex")) {
|
|
306
|
+
throw new Error(
|
|
307
|
+
`Subagent calls require 'claude', 'opencode', or 'codex' CLI in PATH (detected: ${agent ?? "none"})`,
|
|
308
|
+
);
|
|
282
309
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
if (
|
|
287
|
-
|
|
310
|
+
|
|
311
|
+
let cmd: string[];
|
|
312
|
+
|
|
313
|
+
if (agent === "opencode") {
|
|
314
|
+
// OpenCode supports --agent and --model but not allowedTools, appendSystemPrompt, or maxTurns
|
|
315
|
+
if (allowedTools?.length || appendSystemPrompt) {
|
|
316
|
+
logger.warn(
|
|
317
|
+
`Subagent '${agentName}' on opencode: allowedTools and appendSystemPrompt are not supported and will be ignored`,
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
cmd = ["opencode", "run", "--agent", agentName];
|
|
321
|
+
if (modelFlag) {
|
|
322
|
+
cmd.push("--model", resolveOpenCodeModel(modelFlag));
|
|
323
|
+
}
|
|
324
|
+
cmd.push(prompt);
|
|
325
|
+
} else if (agent === "codex") {
|
|
326
|
+
// Codex has no --agent flag; inline the agent instructions into the prompt.
|
|
327
|
+
// allowedTools, appendSystemPrompt, maxTurns, and effort are not supported.
|
|
328
|
+
if (allowedTools?.length || appendSystemPrompt) {
|
|
329
|
+
logger.warn(
|
|
330
|
+
`Subagent '${agentName}' on codex: allowedTools and appendSystemPrompt are not supported and will be ignored`,
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
const agentInstructions = loadAgentInstructions(agentName);
|
|
334
|
+
const fullPrompt = agentInstructions ? `${agentInstructions}\n\n---\n\n${prompt}` : prompt;
|
|
335
|
+
cmd = ["codex", "exec", "--skip-git-repo-check", fullPrompt];
|
|
336
|
+
} else {
|
|
337
|
+
// Claude Code
|
|
338
|
+
cmd = ["claude", "-p", prompt, "--agent", agentName, "--max-turns", String(maxTurns)];
|
|
339
|
+
|
|
340
|
+
if (appendSystemPrompt) {
|
|
341
|
+
cmd.push("--append-system-prompt", appendSystemPrompt);
|
|
342
|
+
}
|
|
343
|
+
if (modelFlag) {
|
|
344
|
+
const resolved = resolveModelFlag(modelFlag);
|
|
345
|
+
cmd.push("--model", resolved);
|
|
346
|
+
}
|
|
347
|
+
if (effort) {
|
|
348
|
+
cmd.push("--effort", effort);
|
|
349
|
+
}
|
|
350
|
+
if (allowedTools && allowedTools.length > 0) {
|
|
351
|
+
cmd.push("--allowedTools", ...allowedTools);
|
|
352
|
+
}
|
|
353
|
+
// Skip permissions since this runs non-interactively in a pipeline
|
|
354
|
+
cmd.push("--dangerously-skip-permissions");
|
|
288
355
|
}
|
|
289
|
-
// Skip permissions since this runs non-interactively in a pipeline
|
|
290
|
-
cmd.push("--dangerously-skip-permissions");
|
|
291
356
|
|
|
292
357
|
const maxRetries = retryOpts?.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
293
358
|
const initialBackoffMs = retryOpts?.initialBackoffMs ?? DEFAULT_INITIAL_BACKOFF_MS;
|
|
@@ -263,6 +263,28 @@ export function classifySkillPath(
|
|
|
263
263
|
return { skill_scope: "unknown" };
|
|
264
264
|
}
|
|
265
265
|
|
|
266
|
+
const TEST_PATH_SEGMENTS = [
|
|
267
|
+
"/tests/",
|
|
268
|
+
"/__tests__/",
|
|
269
|
+
"/test/",
|
|
270
|
+
"/fixtures/",
|
|
271
|
+
"/sandbox/",
|
|
272
|
+
"/test-data/",
|
|
273
|
+
"/testdata/",
|
|
274
|
+
"/mock/",
|
|
275
|
+
"/mocks/",
|
|
276
|
+
];
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Check if a skill path is inside a test/fixture directory.
|
|
280
|
+
* Used to prevent test fixture skills from leaking into production data.
|
|
281
|
+
*/
|
|
282
|
+
export function isTestFixturePath(skillPath: string): boolean {
|
|
283
|
+
if (!skillPath) return false;
|
|
284
|
+
const normalized = skillPath.toLowerCase();
|
|
285
|
+
return TEST_PATH_SEGMENTS.some((seg) => normalized.includes(seg));
|
|
286
|
+
}
|
|
287
|
+
|
|
266
288
|
export function extractSkillNamesFromInstructions(
|
|
267
289
|
text: string,
|
|
268
290
|
knownSkillNames?: Iterable<string>,
|
|
@@ -7,7 +7,7 @@ import type { PushPayloadV2 } from "../src/schemas.js";
|
|
|
7
7
|
export const evidenceOnlyPush: PushPayloadV2 = {
|
|
8
8
|
schema_version: "2.0",
|
|
9
9
|
client_version: "0.9.0",
|
|
10
|
-
push_id: "d4e5f6a7-b8c9-
|
|
10
|
+
push_id: "d4e5f6a7-b8c9-8123-9efa-234567890123",
|
|
11
11
|
normalizer_version: "0.2.1",
|
|
12
12
|
canonical: {
|
|
13
13
|
sessions: [],
|
package/node_modules/@selftune/telemetry-contract/fixtures/partial-push-unresolved-parents.ts
CHANGED
|
@@ -10,7 +10,7 @@ import type { PushPayloadV2 } from "../src/schemas.js";
|
|
|
10
10
|
export const partialPushUnresolvedParents: PushPayloadV2 = {
|
|
11
11
|
schema_version: "2.0",
|
|
12
12
|
client_version: "0.9.0",
|
|
13
|
-
push_id: "c3d4e5f6-a7b8-
|
|
13
|
+
push_id: "c3d4e5f6-a7b8-8012-8def-123456789012",
|
|
14
14
|
normalizer_version: "0.2.1",
|
|
15
15
|
canonical: {
|
|
16
16
|
sessions: [],
|
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
"type": "module",
|
|
14
14
|
"exports": {
|
|
15
15
|
".": "./index.ts",
|
|
16
|
-
"./schemas": "./src/schemas.ts",
|
|
17
16
|
"./types": "./src/types.ts",
|
|
18
17
|
"./validators": "./src/validators.ts",
|
|
18
|
+
"./schemas": "./src/schemas.ts",
|
|
19
19
|
"./fixtures": "./fixtures/index.ts"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|