@fro.bot/systematic 3.4.0 → 3.5.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/HARNESSES.md +9 -2
- package/README.md +2 -2
- package/agents/review/code-simplicity-reviewer.md +1 -1
- package/dist/cli.js +1420 -41
- package/dist/{index-0vm17gwv.js → index-1mb4baxr.js} +115 -4
- package/dist/index.js +109 -18
- package/dist/lib/config-schema.d.ts +66 -0
- package/dist/lib/config.d.ts +16 -2
- package/dist/lib/pi-subagents-export.d.ts +143 -0
- package/dist/lib/pi-subagents-personas.d.ts +64 -0
- package/dist/lib/receipt-readback.d.ts +10 -0
- package/dist/schemas/systematic-config.schema.json +168 -3
- package/package.json +5 -4
- package/skills/using-systematic/references/pi-profile.md +28 -2
|
@@ -16206,6 +16206,58 @@ var CategoryOverlaySchema = exports_external.object({
|
|
|
16206
16206
|
description: "Per-category configuration overlay (same fields as agent minus disable)",
|
|
16207
16207
|
examples: [{ model: "anthropic/claude-opus-4-7", temperature: 0.1 }]
|
|
16208
16208
|
});
|
|
16209
|
+
var piSubagentsThinkingSchema = exports_external.enum(["off", "minimal", "low", "medium", "high", "xhigh", "max"]).meta({
|
|
16210
|
+
description: "pi-subagents reasoning effort level for exported persona frontmatter",
|
|
16211
|
+
examples: ["off", "medium", "high"]
|
|
16212
|
+
});
|
|
16213
|
+
var piSubagentsMaxTurnsSchema = exports_external.number().int().nonnegative().meta({
|
|
16214
|
+
description: "pi-subagents maximum turns for a delegated persona (0 = unlimited)",
|
|
16215
|
+
examples: [0, 10, 25]
|
|
16216
|
+
});
|
|
16217
|
+
var piSubagentsToolsSchema = exports_external.string().min(1).meta({
|
|
16218
|
+
description: "pi-subagents comma-selector tool string (built-ins, */all/none, or extension selectors)",
|
|
16219
|
+
examples: ["*", "read,grep,glob", "none"]
|
|
16220
|
+
});
|
|
16221
|
+
var piSubagentsSkillsSchema = exports_external.union([exports_external.literal(true), exports_external.string().min(1)]).meta({
|
|
16222
|
+
description: "pi-subagents skills selector: true (all) or a comma-separated list of skill names",
|
|
16223
|
+
examples: [true, "ce:plan,ce:review"]
|
|
16224
|
+
});
|
|
16225
|
+
var PiSubagentsAgentOverlaySchema = exports_external.object({
|
|
16226
|
+
thinking: trustProtected(piSubagentsThinkingSchema).optional(),
|
|
16227
|
+
max_turns: trustAny(piSubagentsMaxTurnsSchema).optional(),
|
|
16228
|
+
tools: trustProtected(piSubagentsToolsSchema).optional(),
|
|
16229
|
+
skills: trustProtected(piSubagentsSkillsSchema).optional()
|
|
16230
|
+
}).strict().meta({
|
|
16231
|
+
description: "Per-agent pi-subagents export overlay (Pi-native fields only; no model)",
|
|
16232
|
+
examples: [{ thinking: "high", max_turns: 10 }]
|
|
16233
|
+
});
|
|
16234
|
+
var PiSubagentsCategoryOverlaySchema = PiSubagentsAgentOverlaySchema.meta({
|
|
16235
|
+
description: "Per-category pi-subagents export overlay (Pi-native fields only; no model)",
|
|
16236
|
+
examples: [{ thinking: "medium" }]
|
|
16237
|
+
});
|
|
16238
|
+
var PiSubagentsSchema = exports_external.object({
|
|
16239
|
+
categories: exports_external.record(exports_external.string(), PiSubagentsCategoryOverlaySchema).default({}).meta({
|
|
16240
|
+
description: "Per-category pi-subagents export overlays keyed by category name",
|
|
16241
|
+
examples: [{ research: { thinking: "high" } }, {}]
|
|
16242
|
+
}),
|
|
16243
|
+
agents: exports_external.record(exports_external.string(), PiSubagentsAgentOverlaySchema).default({}).meta({
|
|
16244
|
+
description: "Per-agent pi-subagents export overlays keyed by bundled agent name",
|
|
16245
|
+
examples: [{ "repo-research-analyst": { max_turns: 10 } }, {}]
|
|
16246
|
+
})
|
|
16247
|
+
}).strict().default({ categories: {}, agents: {} }).meta({
|
|
16248
|
+
description: "Pi-native pi-subagents export field overlays (thinking, max_turns, tools, skills). Category values apply first; per-agent values override. No model field \u2014 model stays in the categories/agents overlay.",
|
|
16249
|
+
examples: [
|
|
16250
|
+
{
|
|
16251
|
+
categories: { research: { thinking: "high" } },
|
|
16252
|
+
agents: { "repo-research-analyst": { max_turns: 10 } }
|
|
16253
|
+
}
|
|
16254
|
+
]
|
|
16255
|
+
});
|
|
16256
|
+
var PI_SUBAGENTS_PROTECTED_FIELDS = [
|
|
16257
|
+
"thinking",
|
|
16258
|
+
"tools",
|
|
16259
|
+
"skills"
|
|
16260
|
+
];
|
|
16209
16261
|
var BootstrapSchema = exports_external.object({
|
|
16210
16262
|
enabled: exports_external.boolean().default(true).meta({
|
|
16211
16263
|
description: "Enable bootstrap prompt injection into every conversation",
|
|
@@ -16276,6 +16328,7 @@ function createSystematicConfigSchema(opts) {
|
|
|
16276
16328
|
]
|
|
16277
16329
|
}),
|
|
16278
16330
|
workflow_guard: WorkflowGuardSchema,
|
|
16331
|
+
pi_subagents: PiSubagentsSchema,
|
|
16279
16332
|
skills_as_commands: exports_external.boolean().default(true).meta({
|
|
16280
16333
|
description: "Register skills discovered from user/project skill directories (OpenCode config and other agent-harness-standard locations) as slash commands. Default true.",
|
|
16281
16334
|
examples: [true, false]
|
|
@@ -16315,6 +16368,7 @@ var DEFAULT_CONFIG = {
|
|
|
16315
16368
|
},
|
|
16316
16369
|
agents: {},
|
|
16317
16370
|
categories: {},
|
|
16371
|
+
pi_subagents: { categories: {}, agents: {} },
|
|
16318
16372
|
skills_as_commands: true
|
|
16319
16373
|
};
|
|
16320
16374
|
var SECURITY_OVERLAY_FIELDS2 = new Set(SECURITY_OVERLAY_FIELDS);
|
|
@@ -16473,16 +16527,18 @@ function mergeArraysUnique(arr1, arr2) {
|
|
|
16473
16527
|
set2.add(item);
|
|
16474
16528
|
return Array.from(set2);
|
|
16475
16529
|
}
|
|
16476
|
-
function loadConfig(projectDir) {
|
|
16477
|
-
return loadConfigWithSources(projectDir).config;
|
|
16530
|
+
function loadConfig(projectDir, options) {
|
|
16531
|
+
return loadConfigWithSources(projectDir, options).config;
|
|
16478
16532
|
}
|
|
16479
|
-
function loadConfigWithSources(projectDir) {
|
|
16533
|
+
function loadConfigWithSources(projectDir, options) {
|
|
16534
|
+
const includeProject = options?.includeProject ?? true;
|
|
16480
16535
|
const paths = getConfigPaths(projectDir);
|
|
16481
16536
|
const userSource = loadConfigSource(paths.userConfig, "user");
|
|
16482
|
-
const projectSource = loadConfigSource(paths.projectConfig, "project");
|
|
16537
|
+
const projectSource = includeProject ? loadConfigSource(paths.projectConfig, "project") : null;
|
|
16483
16538
|
const customSource = paths.customConfig ? loadConfigSource(paths.customConfig, "custom") : null;
|
|
16484
16539
|
const sources = [userSource, projectSource, customSource].filter((source) => source !== null);
|
|
16485
16540
|
const mergedOverlays = mergeOverlaySources(sources);
|
|
16541
|
+
const mergedPiSubagentsOverlays = mergePiSubagentsOverlaySources(sources);
|
|
16486
16542
|
const droppedCategories = Object.keys(mergedOverlays.categories).filter((name) => REMOVED_AGENT_CATEGORIES_SET.has(name));
|
|
16487
16543
|
const warned = new Set;
|
|
16488
16544
|
warnDroppedNames(droppedCategories, "categories", warned, "v3.0.0");
|
|
@@ -16511,6 +16567,10 @@ function loadConfigWithSources(projectDir) {
|
|
|
16511
16567
|
},
|
|
16512
16568
|
agents: overlayValues(overlays.agents),
|
|
16513
16569
|
categories: overlayValues(overlays.categories),
|
|
16570
|
+
pi_subagents: {
|
|
16571
|
+
categories: overlayValues(mergedPiSubagentsOverlays.categories),
|
|
16572
|
+
agents: overlayValues(mergedPiSubagentsOverlays.agents)
|
|
16573
|
+
},
|
|
16514
16574
|
skills_as_commands: customConfig?.skills_as_commands ?? projectConfig?.skills_as_commands ?? userConfig?.skills_as_commands ?? DEFAULT_CONFIG.skills_as_commands
|
|
16515
16575
|
};
|
|
16516
16576
|
const droppedSkills = computeDroppedNames(result.disabled_skills, CURRENT_SKILL_NAMES_SET);
|
|
@@ -16577,6 +16637,57 @@ function preserveSecurityFields(previous, next) {
|
|
|
16577
16637
|
}
|
|
16578
16638
|
return result;
|
|
16579
16639
|
}
|
|
16640
|
+
var PI_SUBAGENTS_PROTECTED_FIELD_SET = new Set(PI_SUBAGENTS_PROTECTED_FIELDS);
|
|
16641
|
+
function mergePiSubagentsOverlaySources(sources) {
|
|
16642
|
+
const result = {
|
|
16643
|
+
agents: {},
|
|
16644
|
+
categories: {}
|
|
16645
|
+
};
|
|
16646
|
+
for (const source of sources) {
|
|
16647
|
+
mergePiSubagentsOverlayMap(result.agents, source, "agents");
|
|
16648
|
+
mergePiSubagentsOverlayMap(result.categories, source, "categories");
|
|
16649
|
+
}
|
|
16650
|
+
return result;
|
|
16651
|
+
}
|
|
16652
|
+
function stripPiSubagentsProtectedFields(value) {
|
|
16653
|
+
const result = {};
|
|
16654
|
+
for (const [field, fieldValue] of Object.entries(value)) {
|
|
16655
|
+
if (PI_SUBAGENTS_PROTECTED_FIELD_SET.has(field))
|
|
16656
|
+
continue;
|
|
16657
|
+
result[field] = fieldValue;
|
|
16658
|
+
}
|
|
16659
|
+
return result;
|
|
16660
|
+
}
|
|
16661
|
+
function preservePiSubagentsProtectedFields(previous, next) {
|
|
16662
|
+
const result = { ...next };
|
|
16663
|
+
for (const field of PI_SUBAGENTS_PROTECTED_FIELD_SET) {
|
|
16664
|
+
if (Object.hasOwn(previous, field)) {
|
|
16665
|
+
result[field] = previous[field];
|
|
16666
|
+
}
|
|
16667
|
+
}
|
|
16668
|
+
return result;
|
|
16669
|
+
}
|
|
16670
|
+
function mergePiSubagentsOverlayMap(target, source, mapKey) {
|
|
16671
|
+
const overlayMap = source.config.pi_subagents?.[mapKey];
|
|
16672
|
+
if (overlayMap === undefined)
|
|
16673
|
+
return;
|
|
16674
|
+
if (!isRecord2(overlayMap)) {
|
|
16675
|
+
throwInvalidOverlay(source.path, `pi_subagents.${mapKey}`);
|
|
16676
|
+
}
|
|
16677
|
+
for (const [key, rawValue] of Object.entries(overlayMap)) {
|
|
16678
|
+
const keyPath = `pi_subagents.${mapKey}.${key}`;
|
|
16679
|
+
if (!isRecord2(rawValue)) {
|
|
16680
|
+
throwInvalidOverlay(source.path, keyPath);
|
|
16681
|
+
}
|
|
16682
|
+
const previous = target[key];
|
|
16683
|
+
const value = source.trust === "project" ? preservePiSubagentsProtectedFields(previous?.value ?? {}, stripPiSubagentsProtectedFields(rawValue)) : rawValue;
|
|
16684
|
+
target[key] = {
|
|
16685
|
+
value,
|
|
16686
|
+
sourcePath: source.path,
|
|
16687
|
+
keyPath
|
|
16688
|
+
};
|
|
16689
|
+
}
|
|
16690
|
+
}
|
|
16580
16691
|
function overlayValues(overlays) {
|
|
16581
16692
|
const result = {};
|
|
16582
16693
|
for (const [key, overlay] of Object.entries(overlays)) {
|
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
loadConfigWithSources,
|
|
16
16
|
parseFrontmatter,
|
|
17
17
|
walkDir
|
|
18
|
-
} from "./index-
|
|
18
|
+
} from "./index-1mb4baxr.js";
|
|
19
19
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
import { createHash as createHash5 } from "crypto";
|
|
@@ -2890,6 +2890,19 @@ function seedFromMarker(marker) {
|
|
|
2890
2890
|
capabilityFlags: marker.capabilityFlags
|
|
2891
2891
|
};
|
|
2892
2892
|
}
|
|
2893
|
+
function registrationDigestFromMarker(marker) {
|
|
2894
|
+
if (marker.kind === "mint")
|
|
2895
|
+
return marker.envelope.registrationDigest;
|
|
2896
|
+
return marker.registrationDigest;
|
|
2897
|
+
}
|
|
2898
|
+
function filterMarkersByRegistration(markers, ownRegistrationDigest) {
|
|
2899
|
+
return markers.filter((input) => {
|
|
2900
|
+
const validation = validateReceiptMarker(input);
|
|
2901
|
+
if (validation.status !== "valid")
|
|
2902
|
+
return true;
|
|
2903
|
+
return registrationDigestFromMarker(validation.marker) === ownRegistrationDigest;
|
|
2904
|
+
});
|
|
2905
|
+
}
|
|
2893
2906
|
function extractReceiptReadbackSeed(inputs) {
|
|
2894
2907
|
if (inputs.length === 0)
|
|
2895
2908
|
return { status: "empty" };
|
|
@@ -10400,6 +10413,11 @@ var statusToolSchema = exports_external.object(statusToolShape).strict();
|
|
|
10400
10413
|
function isRecord7(value) {
|
|
10401
10414
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10402
10415
|
}
|
|
10416
|
+
function decodeSessionSaltBytes(hex) {
|
|
10417
|
+
if (typeof hex !== "string" || hex.length !== 64 || !/^[0-9a-f]+$/.test(hex))
|
|
10418
|
+
return;
|
|
10419
|
+
return Uint8Array.from(Buffer.from(hex, "hex"));
|
|
10420
|
+
}
|
|
10403
10421
|
function boundedString(value, maxLength) {
|
|
10404
10422
|
return typeof value === "string" && value.length > 0 && value.length <= maxLength;
|
|
10405
10423
|
}
|
|
@@ -10979,6 +10997,46 @@ function createSessionRuntime(options) {
|
|
|
10979
10997
|
function seedAgrees(nextLedger, seed) {
|
|
10980
10998
|
return nextLedger.metadata.registrationDigest === seed.registrationDigest && JSON.stringify(nextLedger.metadata.capabilityFlags) === JSON.stringify(["workflow-guard"]);
|
|
10981
10999
|
}
|
|
11000
|
+
function candidateSeedFromMarker(input) {
|
|
11001
|
+
const validation = validateReceiptMarker(input);
|
|
11002
|
+
if (validation.status !== "valid")
|
|
11003
|
+
return;
|
|
11004
|
+
const marker = validation.marker;
|
|
11005
|
+
if (marker.kind === "mint") {
|
|
11006
|
+
const salt = decodeSessionSaltBytes(marker.sessionSalt);
|
|
11007
|
+
return salt ? { salt, digest: marker.envelope.registrationDigest } : undefined;
|
|
11008
|
+
}
|
|
11009
|
+
if (marker.kind === "control" && marker.control === "progression") {
|
|
11010
|
+
const salt = decodeSessionSaltBytes(marker.sessionSalt);
|
|
11011
|
+
return salt ? { salt, digest: marker.registrationDigest } : undefined;
|
|
11012
|
+
}
|
|
11013
|
+
return;
|
|
11014
|
+
}
|
|
11015
|
+
function candidateAgreesWithOwnIdentity(salt, digest2) {
|
|
11016
|
+
try {
|
|
11017
|
+
const probe = createReceiptLedger({
|
|
11018
|
+
capabilityFlags: ["workflow-guard"],
|
|
11019
|
+
registrationIdentity: options.registrationIdentity,
|
|
11020
|
+
sessionSalt: salt
|
|
11021
|
+
});
|
|
11022
|
+
return probe.metadata.registrationDigest === digest2;
|
|
11023
|
+
} catch {
|
|
11024
|
+
return false;
|
|
11025
|
+
}
|
|
11026
|
+
}
|
|
11027
|
+
function resolveOwnRegistrationDigest(markers) {
|
|
11028
|
+
const seenDigests = new Set;
|
|
11029
|
+
for (const input of markers) {
|
|
11030
|
+
const candidate = candidateSeedFromMarker(input);
|
|
11031
|
+
if (!candidate || seenDigests.has(candidate.digest))
|
|
11032
|
+
continue;
|
|
11033
|
+
seenDigests.add(candidate.digest);
|
|
11034
|
+
if (candidateAgreesWithOwnIdentity(candidate.salt, candidate.digest)) {
|
|
11035
|
+
return candidate.digest;
|
|
11036
|
+
}
|
|
11037
|
+
}
|
|
11038
|
+
return;
|
|
11039
|
+
}
|
|
10982
11040
|
function recoverPersistedMarkers(markers) {
|
|
10983
11041
|
const seed = extractReceiptReadbackSeed(markers);
|
|
10984
11042
|
if (seed.status !== "ready")
|
|
@@ -11017,6 +11075,39 @@ function createSessionRuntime(options) {
|
|
|
11017
11075
|
initialized = true;
|
|
11018
11076
|
return true;
|
|
11019
11077
|
}
|
|
11078
|
+
function classifyMarkersFromParts(parts2) {
|
|
11079
|
+
const allMarkers = [];
|
|
11080
|
+
for (const part of parts2)
|
|
11081
|
+
collectReceiptMarkers(part, allMarkers);
|
|
11082
|
+
if (allMarkers.length === 0)
|
|
11083
|
+
return { kind: "foreign-empty" };
|
|
11084
|
+
const ownDigest = resolveOwnRegistrationDigest(allMarkers);
|
|
11085
|
+
if (ownDigest !== undefined) {
|
|
11086
|
+
return {
|
|
11087
|
+
kind: "own",
|
|
11088
|
+
markers: filterMarkersByRegistration(allMarkers, ownDigest)
|
|
11089
|
+
};
|
|
11090
|
+
}
|
|
11091
|
+
for (const input of allMarkers) {
|
|
11092
|
+
const validation = validateReceiptMarker(input);
|
|
11093
|
+
if (validation.status !== "valid") {
|
|
11094
|
+
return { kind: "ambiguous" };
|
|
11095
|
+
}
|
|
11096
|
+
const candidate = candidateSeedFromMarker(input);
|
|
11097
|
+
if (candidate === undefined) {
|
|
11098
|
+
return { kind: "ambiguous" };
|
|
11099
|
+
}
|
|
11100
|
+
}
|
|
11101
|
+
return { kind: "foreign-empty" };
|
|
11102
|
+
}
|
|
11103
|
+
function publishEmptyMarkers(parts2, allowFresh) {
|
|
11104
|
+
if (allowFresh && !parts2.some((part) => containsGuardHistory(part))) {
|
|
11105
|
+
publishFresh();
|
|
11106
|
+
} else {
|
|
11107
|
+
retryableEmptyHistory = true;
|
|
11108
|
+
publishUnavailable();
|
|
11109
|
+
}
|
|
11110
|
+
}
|
|
11020
11111
|
async function initializeSession(sessionID, allowFresh) {
|
|
11021
11112
|
const reader = options.hostReadback?.readSessionParts;
|
|
11022
11113
|
if (!reader) {
|
|
@@ -11030,19 +11121,16 @@ function createSessionRuntime(options) {
|
|
|
11030
11121
|
publishUnavailable();
|
|
11031
11122
|
return;
|
|
11032
11123
|
}
|
|
11033
|
-
const
|
|
11034
|
-
|
|
11035
|
-
|
|
11036
|
-
if (markers.length === 0) {
|
|
11037
|
-
if (allowFresh && !parts2.some((part) => containsGuardHistory(part))) {
|
|
11038
|
-
publishFresh();
|
|
11039
|
-
} else {
|
|
11040
|
-
retryableEmptyHistory = true;
|
|
11041
|
-
publishUnavailable();
|
|
11042
|
-
}
|
|
11124
|
+
const classification = classifyMarkersFromParts(parts2);
|
|
11125
|
+
if (classification.kind === "ambiguous") {
|
|
11126
|
+
publishUnavailable();
|
|
11043
11127
|
return;
|
|
11044
11128
|
}
|
|
11045
|
-
if (
|
|
11129
|
+
if (classification.kind === "foreign-empty") {
|
|
11130
|
+
publishEmptyMarkers(parts2, allowFresh);
|
|
11131
|
+
return;
|
|
11132
|
+
}
|
|
11133
|
+
if (!recoverPersistedMarkers(classification.markers))
|
|
11046
11134
|
publishUnavailable();
|
|
11047
11135
|
}
|
|
11048
11136
|
async function recoverFromHost(sessionID, allowFresh = true) {
|
|
@@ -11080,12 +11168,15 @@ function createSessionRuntime(options) {
|
|
|
11080
11168
|
markUnavailable();
|
|
11081
11169
|
return;
|
|
11082
11170
|
}
|
|
11083
|
-
const
|
|
11084
|
-
|
|
11085
|
-
collectReceiptMarkers(part, markers);
|
|
11086
|
-
if (markers.length === 0)
|
|
11171
|
+
const classification = classifyMarkersFromParts(parts2);
|
|
11172
|
+
if (classification.kind === "foreign-empty")
|
|
11087
11173
|
return;
|
|
11088
|
-
|
|
11174
|
+
if (classification.kind === "ambiguous") {
|
|
11175
|
+
markUnavailable();
|
|
11176
|
+
return;
|
|
11177
|
+
}
|
|
11178
|
+
const ownMarkers = classification.markers;
|
|
11179
|
+
const seed = extractReceiptReadbackSeed(ownMarkers);
|
|
11089
11180
|
if (seed.status !== "ready") {
|
|
11090
11181
|
markUnavailable();
|
|
11091
11182
|
return;
|
|
@@ -11099,7 +11190,7 @@ function createSessionRuntime(options) {
|
|
|
11099
11190
|
markUnavailable();
|
|
11100
11191
|
return;
|
|
11101
11192
|
}
|
|
11102
|
-
const recovered = childLedger.recoverReadback(
|
|
11193
|
+
const recovered = childLedger.recoverReadback(ownMarkers);
|
|
11103
11194
|
if (recovered.status === "rejected") {
|
|
11104
11195
|
markUnavailable();
|
|
11105
11196
|
return;
|
|
@@ -87,6 +87,72 @@ export declare const CategoryOverlaySchema: z.ZodObject<{
|
|
|
87
87
|
deny: "deny";
|
|
88
88
|
}>>]>>>;
|
|
89
89
|
}, z.core.$strict>;
|
|
90
|
+
export declare const PiSubagentsAgentOverlaySchema: z.ZodObject<{
|
|
91
|
+
thinking: z.ZodOptional<z.ZodEnum<{
|
|
92
|
+
off: "off";
|
|
93
|
+
minimal: "minimal";
|
|
94
|
+
low: "low";
|
|
95
|
+
medium: "medium";
|
|
96
|
+
high: "high";
|
|
97
|
+
xhigh: "xhigh";
|
|
98
|
+
max: "max";
|
|
99
|
+
}>>;
|
|
100
|
+
max_turns: z.ZodOptional<z.ZodNumber>;
|
|
101
|
+
tools: z.ZodOptional<z.ZodString>;
|
|
102
|
+
skills: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<true>, z.ZodString]>>;
|
|
103
|
+
}, z.core.$strict>;
|
|
104
|
+
export declare const PiSubagentsCategoryOverlaySchema: z.ZodObject<{
|
|
105
|
+
thinking: z.ZodOptional<z.ZodEnum<{
|
|
106
|
+
off: "off";
|
|
107
|
+
minimal: "minimal";
|
|
108
|
+
low: "low";
|
|
109
|
+
medium: "medium";
|
|
110
|
+
high: "high";
|
|
111
|
+
xhigh: "xhigh";
|
|
112
|
+
max: "max";
|
|
113
|
+
}>>;
|
|
114
|
+
max_turns: z.ZodOptional<z.ZodNumber>;
|
|
115
|
+
tools: z.ZodOptional<z.ZodString>;
|
|
116
|
+
skills: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<true>, z.ZodString]>>;
|
|
117
|
+
}, z.core.$strict>;
|
|
118
|
+
export declare const PiSubagentsSchema: z.ZodDefault<z.ZodObject<{
|
|
119
|
+
categories: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
120
|
+
thinking: z.ZodOptional<z.ZodEnum<{
|
|
121
|
+
off: "off";
|
|
122
|
+
minimal: "minimal";
|
|
123
|
+
low: "low";
|
|
124
|
+
medium: "medium";
|
|
125
|
+
high: "high";
|
|
126
|
+
xhigh: "xhigh";
|
|
127
|
+
max: "max";
|
|
128
|
+
}>>;
|
|
129
|
+
max_turns: z.ZodOptional<z.ZodNumber>;
|
|
130
|
+
tools: z.ZodOptional<z.ZodString>;
|
|
131
|
+
skills: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<true>, z.ZodString]>>;
|
|
132
|
+
}, z.core.$strict>>>;
|
|
133
|
+
agents: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
134
|
+
thinking: z.ZodOptional<z.ZodEnum<{
|
|
135
|
+
off: "off";
|
|
136
|
+
minimal: "minimal";
|
|
137
|
+
low: "low";
|
|
138
|
+
medium: "medium";
|
|
139
|
+
high: "high";
|
|
140
|
+
xhigh: "xhigh";
|
|
141
|
+
max: "max";
|
|
142
|
+
}>>;
|
|
143
|
+
max_turns: z.ZodOptional<z.ZodNumber>;
|
|
144
|
+
tools: z.ZodOptional<z.ZodString>;
|
|
145
|
+
skills: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<true>, z.ZodString]>>;
|
|
146
|
+
}, z.core.$strict>>>;
|
|
147
|
+
}, z.core.$strict>>;
|
|
148
|
+
/**
|
|
149
|
+
* Fields in PiSubagentsAgentOverlaySchema/PiSubagentsCategoryOverlaySchema that
|
|
150
|
+
* require a project-or-higher trust source. Project config cannot grant
|
|
151
|
+
* `thinking`, `tools`, or `skills` to an exported persona; `max_turns` is
|
|
152
|
+
* trust-any. Mirrors the hand-coded `PI_SUBAGENTS_PROTECTED_FIELDS` set in
|
|
153
|
+
* `src/lib/config.ts`.
|
|
154
|
+
*/
|
|
155
|
+
export declare const PI_SUBAGENTS_PROTECTED_FIELDS: readonly string[];
|
|
90
156
|
export declare const BootstrapSchema: z.ZodObject<{
|
|
91
157
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
92
158
|
file: z.ZodOptional<z.ZodString>;
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -22,6 +22,10 @@ export interface SourceAwareConfigResult {
|
|
|
22
22
|
config: SystematicConfig;
|
|
23
23
|
overlays: SourcedOverlayConfigMap;
|
|
24
24
|
}
|
|
25
|
+
export interface PiSubagentsOverlayMap {
|
|
26
|
+
categories?: OverlayConfigMap;
|
|
27
|
+
agents?: OverlayConfigMap;
|
|
28
|
+
}
|
|
25
29
|
export interface SystematicConfig {
|
|
26
30
|
disabled_skills: string[];
|
|
27
31
|
disabled_agents: string[];
|
|
@@ -30,6 +34,7 @@ export interface SystematicConfig {
|
|
|
30
34
|
workflow_guard: WorkflowGuardConfig;
|
|
31
35
|
agents?: OverlayConfigMap;
|
|
32
36
|
categories?: OverlayConfigMap;
|
|
37
|
+
pi_subagents?: PiSubagentsOverlayMap;
|
|
33
38
|
skills_as_commands: boolean;
|
|
34
39
|
}
|
|
35
40
|
export declare const DEFAULT_CONFIG: SystematicConfig;
|
|
@@ -47,8 +52,17 @@ export declare function computeDroppedNames(names: readonly string[], allowedSet
|
|
|
47
52
|
* Passing a fresh set per load ensures no cross-load suppression.
|
|
48
53
|
*/
|
|
49
54
|
export declare function warnDroppedNames(dropped: string[], field: string, warned: Set<string>, removalVersion?: string): void;
|
|
50
|
-
export
|
|
51
|
-
|
|
55
|
+
export interface LoadConfigOptions {
|
|
56
|
+
/**
|
|
57
|
+
* When false, the project-level config source (`<cwd>/.opencode/systematic.json`)
|
|
58
|
+
* is not loaded at all — not merged, not trust-stripped, entirely absent from
|
|
59
|
+
* the source chain. Used by global-scoped pi-subagents export so it never
|
|
60
|
+
* absorbs cwd project overlays (plan R7/R19). Defaults to true.
|
|
61
|
+
*/
|
|
62
|
+
includeProject?: boolean;
|
|
63
|
+
}
|
|
64
|
+
export declare function loadConfig(projectDir: string, options?: LoadConfigOptions): SystematicConfig;
|
|
65
|
+
export declare function loadConfigWithSources(projectDir: string, options?: LoadConfigOptions): SourceAwareConfigResult;
|
|
52
66
|
export declare function getConfigPaths(projectDir: string): {
|
|
53
67
|
customConfig?: string | undefined;
|
|
54
68
|
customDir?: string | undefined;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pi-subagents export lifecycle: resolve, preview, export, refresh, cleanup.
|
|
3
|
+
*
|
|
4
|
+
* Writes user-chosen agents dirs ($PI_CODING_AGENT_DIR/agents or
|
|
5
|
+
* <cwd>/.pi/agents). Batch-transactional with rollback: if any file write or
|
|
6
|
+
* the manifest write fails, the operation rolls back to the pre-operation
|
|
7
|
+
* state. Rollback reports any restoration failures explicitly.
|
|
8
|
+
*
|
|
9
|
+
* Manifest tracks ownership. Malformed or hostile manifests cause every
|
|
10
|
+
* lifecycle verb to refuse before mutation. No writes from module import.
|
|
11
|
+
*/
|
|
12
|
+
export type ExportScope = 'project' | 'global';
|
|
13
|
+
/**
|
|
14
|
+
* Scope-appropriate config resolution for export/preview/refresh. When
|
|
15
|
+
* provided, the effective config (`user → project → custom` for project
|
|
16
|
+
* scope; `user → custom` for global scope — never absorbing cwd project
|
|
17
|
+
* overlays) is applied to each exported persona's frontmatter: `model`
|
|
18
|
+
* resolved from the `categories`/`agents` overlay (per-agent beats category;
|
|
19
|
+
* `model: null` omits) and Pi-native `pi_subagents` fields (`thinking`,
|
|
20
|
+
* `max_turns`, `tools`, `skills`) resolved from the `pi_subagents`
|
|
21
|
+
* namespace after trust filtering. Omitting `configOptions` preserves the
|
|
22
|
+
* model-free, config-neutral export (backward compatible default).
|
|
23
|
+
*/
|
|
24
|
+
export interface ExportConfigOptions {
|
|
25
|
+
scope: ExportScope;
|
|
26
|
+
cwd: string;
|
|
27
|
+
}
|
|
28
|
+
export declare const MANIFEST_FILENAME = ".systematic-personas.json";
|
|
29
|
+
/** Exclusive per-root mutation lock. Guards export/refresh/cleanup; preview is lock-free. */
|
|
30
|
+
export declare const LOCK_FILENAME = ".systematic-personas.lock";
|
|
31
|
+
export interface ManifestFileEntry {
|
|
32
|
+
filename: string;
|
|
33
|
+
hash: string;
|
|
34
|
+
status: 'exported' | 'exported-with-warning';
|
|
35
|
+
}
|
|
36
|
+
export interface PiSubagentsManifest {
|
|
37
|
+
generatedAt: string;
|
|
38
|
+
agentsRoot: string;
|
|
39
|
+
files: ManifestFileEntry[];
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Strict manifest read result — distinguishes absent from malformed.
|
|
43
|
+
*
|
|
44
|
+
* absent → no manifest file; operations proceed as first-export.
|
|
45
|
+
* ok → valid manifest, returned in `manifest`.
|
|
46
|
+
* malformed → manifest file exists but is invalid (bad JSON, wrong schema,
|
|
47
|
+
* duplicate filenames, unsafe filenames). Operations must refuse.
|
|
48
|
+
*/
|
|
49
|
+
export type ManifestReadResult = {
|
|
50
|
+
kind: 'absent';
|
|
51
|
+
} | {
|
|
52
|
+
kind: 'ok';
|
|
53
|
+
manifest: PiSubagentsManifest;
|
|
54
|
+
} | {
|
|
55
|
+
kind: 'malformed';
|
|
56
|
+
error: string;
|
|
57
|
+
};
|
|
58
|
+
export declare function resolveAgentsRoot(scope: ExportScope, cwd: string): string;
|
|
59
|
+
/**
|
|
60
|
+
* Resolve the safety anchor for a scope: the topmost directory whose
|
|
61
|
+
* descendants (down to agentsRoot) are walked and lstat-checked for
|
|
62
|
+
* symlinks/non-directories. Never inspects ancestors above this anchor
|
|
63
|
+
* (avoids false positives from OS-level ancestor symlinks, e.g. macOS
|
|
64
|
+
* `/var` -> `/private/var`).
|
|
65
|
+
*
|
|
66
|
+
* - project: cwd
|
|
67
|
+
* - global with PI_CODING_AGENT_DIR set: the env dir's PARENT (so the env
|
|
68
|
+
* dir itself is included in the walk and checked)
|
|
69
|
+
* - global without PI_CODING_AGENT_DIR: homedir
|
|
70
|
+
*/
|
|
71
|
+
export declare function resolveAnchor(scope: ExportScope, cwd: string): string;
|
|
72
|
+
export declare function readManifestStrict(agentsRoot: string): ManifestReadResult;
|
|
73
|
+
/**
|
|
74
|
+
* Convenience wrapper: returns the manifest for 'ok', null for 'absent', throws
|
|
75
|
+
* a structured Error for 'malformed'. Used by callers that already distinguished absent.
|
|
76
|
+
*/
|
|
77
|
+
export declare function readManifest(agentsRoot: string): PiSubagentsManifest | null;
|
|
78
|
+
export declare function writeManifest(agentsRoot: string, manifest: PiSubagentsManifest): void;
|
|
79
|
+
export interface TxResult {
|
|
80
|
+
ok: boolean;
|
|
81
|
+
error?: string;
|
|
82
|
+
rollbackFailed: string[];
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Run a sequence of filesystem operations under snapshot/rollback protection.
|
|
86
|
+
*
|
|
87
|
+
* 1. Snapshot `pathsToWatch` (current content or absent marker).
|
|
88
|
+
* 2. Execute each `op` in order. On the first throw, stop.
|
|
89
|
+
* 3. On any failure: restore all watched paths to their snapshotted state.
|
|
90
|
+
* Returns `{ ok: false, error, rollbackFailed }` — `rollbackFailed` lists
|
|
91
|
+
* paths whose restoration itself failed (partial rollback, reported honestly).
|
|
92
|
+
* 4. On full success: returns `{ ok: true, rollbackFailed: [] }`.
|
|
93
|
+
*
|
|
94
|
+
* Exported for direct testing; also used by all production commit/delete paths.
|
|
95
|
+
*/
|
|
96
|
+
export declare function runWithRollback(pathsToWatch: string[], ops: Array<() => void>): TxResult;
|
|
97
|
+
export type PlanAction = {
|
|
98
|
+
action: 'create';
|
|
99
|
+
filename: string;
|
|
100
|
+
} | {
|
|
101
|
+
action: 'update';
|
|
102
|
+
filename: string;
|
|
103
|
+
} | {
|
|
104
|
+
action: 'refuse';
|
|
105
|
+
filename: string;
|
|
106
|
+
reason: string;
|
|
107
|
+
} | {
|
|
108
|
+
action: 'remove';
|
|
109
|
+
filename: string;
|
|
110
|
+
} | {
|
|
111
|
+
action: 'skip';
|
|
112
|
+
filename: string;
|
|
113
|
+
};
|
|
114
|
+
export interface ExportPlan {
|
|
115
|
+
status: 'ok' | 'error';
|
|
116
|
+
error?: string;
|
|
117
|
+
agentsRoot: string;
|
|
118
|
+
actions: PlanAction[];
|
|
119
|
+
}
|
|
120
|
+
export declare function preview(agentsRoot: string, configOptions?: ExportConfigOptions): ExportPlan;
|
|
121
|
+
export interface ExportResult {
|
|
122
|
+
status: 'ok' | 'error';
|
|
123
|
+
written: number;
|
|
124
|
+
skipped: number;
|
|
125
|
+
refused: Array<{
|
|
126
|
+
filename: string;
|
|
127
|
+
reason: string;
|
|
128
|
+
}>;
|
|
129
|
+
error?: string;
|
|
130
|
+
}
|
|
131
|
+
export declare function exportPersonas(agentsRoot: string, configOptions?: ExportConfigOptions): ExportResult;
|
|
132
|
+
export interface RefreshResult {
|
|
133
|
+
status: 'ok' | 'error';
|
|
134
|
+
updated: number;
|
|
135
|
+
skippedUnowned: number;
|
|
136
|
+
error?: string;
|
|
137
|
+
}
|
|
138
|
+
export declare function refresh(agentsRoot: string, configOptions?: ExportConfigOptions): RefreshResult;
|
|
139
|
+
export interface CleanupResult {
|
|
140
|
+
status: 'ok' | 'error';
|
|
141
|
+
error?: string;
|
|
142
|
+
}
|
|
143
|
+
export declare function cleanup(agentsRoot: string, configOptions?: ExportConfigOptions): CleanupResult;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure persona generation logic for pi-subagents interop.
|
|
3
|
+
*
|
|
4
|
+
* Contains the curated persona list, compatibility screening, content
|
|
5
|
+
* generation, and generateAll(). Importable from both src/ and scripts/.
|
|
6
|
+
* No filesystem writes; no CLI entrypoint.
|
|
7
|
+
*/
|
|
8
|
+
/** Compatibility severity: info = fully usable, warning = may differ, critical = excluded. */
|
|
9
|
+
export type CompatibilitySeverity = 'info' | 'warning' | 'critical';
|
|
10
|
+
/** Result of classifying a persona's pi-subagents compatibility. */
|
|
11
|
+
export interface CompatibilityStatus {
|
|
12
|
+
severity: CompatibilitySeverity;
|
|
13
|
+
/** Human-readable reasons for the severity (empty for info). */
|
|
14
|
+
reasons: string[];
|
|
15
|
+
}
|
|
16
|
+
/** Per-persona entry in the manifest. */
|
|
17
|
+
export interface ManifestEntry {
|
|
18
|
+
/** Emitted filename, e.g. `systematic-best-practices-researcher.md`. */
|
|
19
|
+
filename: string;
|
|
20
|
+
/** Export status. */
|
|
21
|
+
status: 'exported' | 'exported-with-warning' | 'excluded-critical';
|
|
22
|
+
/** Repo-relative source path, e.g. `agents/research/best-practices-researcher.md`. */
|
|
23
|
+
sourceRelPath: string;
|
|
24
|
+
/** SHA-256 hex of the generated content (only for exported entries). */
|
|
25
|
+
hash: string;
|
|
26
|
+
/** Generated content (only for exported entries). */
|
|
27
|
+
content?: string;
|
|
28
|
+
/** Human-readable reason (for excluded-critical and exported-with-warning). */
|
|
29
|
+
reason?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The authoritative curated-include list with per-persona compatibility
|
|
33
|
+
* rationale. Only personas in this list are candidates for export.
|
|
34
|
+
*
|
|
35
|
+
* Exclusion rationale (not in list):
|
|
36
|
+
* - agents/workflow/systematic-implementer.md — CRITICAL: dispatched-by-parent assumption.
|
|
37
|
+
* - agents/design/design-iterator.md — CRITICAL: requires agent-browser + skill load.
|
|
38
|
+
* - agents/review/agent-native-reviewer.md — CRITICAL: Systematic/OpenCode-specific context.
|
|
39
|
+
* - agents/review/project-standards-reviewer.md — CRITICAL: requires orchestrator <standards-paths>.
|
|
40
|
+
* - agents/review/kieran-typescript-reviewer.md — WARNING: excluded by plan recommendation.
|
|
41
|
+
* - agents/research/slack-researcher.md — CRITICAL: requires Slack MCP environment.
|
|
42
|
+
* - agents/research/learnings-researcher.md — CRITICAL: references Systematic skill paths.
|
|
43
|
+
* - agents/workflow/pr-comment-resolver.md — WARNING: "Spawned by the resolve-pr-feedback skill".
|
|
44
|
+
*/
|
|
45
|
+
export interface CuratedPersonaEntry {
|
|
46
|
+
relPath: string;
|
|
47
|
+
rationale: string;
|
|
48
|
+
}
|
|
49
|
+
export declare const CURATED_PERSONAS: CuratedPersonaEntry[];
|
|
50
|
+
/**
|
|
51
|
+
* Sanitize a persona name for use as a pi-subagents filename stem.
|
|
52
|
+
* Returns empty string if no safe characters remain — callers must reject
|
|
53
|
+
* empty to avoid producing `systematic-.md`.
|
|
54
|
+
*/
|
|
55
|
+
export declare function sanitizeName(name: string): string;
|
|
56
|
+
export declare function classifyCompatibility(content: string): CompatibilityStatus;
|
|
57
|
+
export declare function generatePersonaContent(sourceRef: string, rawContent: string): string | null;
|
|
58
|
+
export declare function generatePersonaManifest(sourceRelPath: string, rawContent: string, _repoRoot: string): ManifestEntry;
|
|
59
|
+
/**
|
|
60
|
+
* Generate all curated personas from repoRoot/agents/.
|
|
61
|
+
* Throws on collision, new critical coupling, or read errors.
|
|
62
|
+
* Pure — no writes.
|
|
63
|
+
*/
|
|
64
|
+
export declare function generateAll(repoRoot: string): ManifestEntry[];
|