@fro.bot/systematic 3.4.1 → 3.5.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/HARNESSES.md +9 -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 +165 -24
- 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/workflow-guard.d.ts +14 -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";
|
|
@@ -3320,9 +3320,20 @@ function applyUnitStart(marker, epoch, context) {
|
|
|
3320
3320
|
return;
|
|
3321
3321
|
}
|
|
3322
3322
|
if (current.unitId === marker.unitId) {
|
|
3323
|
-
if (
|
|
3323
|
+
if (current.state === "completed")
|
|
3324
|
+
return "out-of-order";
|
|
3325
|
+
if (!sameUnitDeclaration(current, marker) && unitHasMintedEvidence(current, context)) {
|
|
3326
|
+
return "out-of-order";
|
|
3327
|
+
}
|
|
3328
|
+
if (sameUnitDeclaration(current, marker) && current.transitionDigest !== marker.transitionDigest) {
|
|
3324
3329
|
return "conflicting-marker";
|
|
3325
|
-
|
|
3330
|
+
}
|
|
3331
|
+
if (!unitDeclarationExtends(current, marker))
|
|
3332
|
+
return "conflicting-marker";
|
|
3333
|
+
if (sameUnitDeclaration(current, marker))
|
|
3334
|
+
return;
|
|
3335
|
+
context.progression = { epoch, unit: snapshot };
|
|
3336
|
+
return;
|
|
3326
3337
|
}
|
|
3327
3338
|
if (current.state !== "completed")
|
|
3328
3339
|
return "out-of-order";
|
|
@@ -3346,9 +3357,26 @@ function applyUnitComplete(marker, epoch, context) {
|
|
|
3346
3357
|
};
|
|
3347
3358
|
return;
|
|
3348
3359
|
}
|
|
3360
|
+
function unitHasMintedEvidence(current, context) {
|
|
3361
|
+
return [...context.mintByReceipt.values()].some((envelope) => envelope.canonical.epochDigest === current.epochDigest && envelope.canonical.unitDigest === current.unitDigest);
|
|
3362
|
+
}
|
|
3349
3363
|
function sameUnitDeclaration(current, marker) {
|
|
3350
3364
|
return current.epochDigest === marker.epochDigest && current.unitDigest === marker.unitDigest && current.family === marker.family && JSON.stringify(current.requiredOperations) === JSON.stringify(marker.requiredOperations) && JSON.stringify(current.resourceScopes) === JSON.stringify(marker.resourceScopes);
|
|
3351
3365
|
}
|
|
3366
|
+
function unitDeclarationExtends(current, marker) {
|
|
3367
|
+
if (current.epochDigest !== marker.epochDigest || current.unitDigest !== marker.unitDigest || current.family !== marker.family) {
|
|
3368
|
+
return false;
|
|
3369
|
+
}
|
|
3370
|
+
const nextOperations = new Set(marker.requiredOperations);
|
|
3371
|
+
if (current.requiredOperations.some((operation) => !nextOperations.has(operation))) {
|
|
3372
|
+
return false;
|
|
3373
|
+
}
|
|
3374
|
+
const nextScopes = new Map(marker.resourceScopes.map((scope) => [
|
|
3375
|
+
scope.operation,
|
|
3376
|
+
scope.resourceIdentity
|
|
3377
|
+
]));
|
|
3378
|
+
return [...current.resourceScopes].every((scope) => nextScopes.get(scope.operation) === scope.resourceIdentity);
|
|
3379
|
+
}
|
|
3352
3380
|
function foldProgressionMarker(marker, context) {
|
|
3353
3381
|
return marker.target === "epoch" ? applyEpochProgression(marker, context) : applyUnitProgression(marker, context);
|
|
3354
3382
|
}
|
|
@@ -8473,7 +8501,11 @@ function cloneUnit(unit) {
|
|
|
8473
8501
|
requiredOperations: unit.requiredOperations,
|
|
8474
8502
|
requiredResourceOperations: Object.freeze([
|
|
8475
8503
|
...unit.declaredResourceOperations
|
|
8476
|
-
])
|
|
8504
|
+
]),
|
|
8505
|
+
resourceScopes: Object.freeze([...unit.resourceScopes].map(([operation, resourceIdentity]) => ({
|
|
8506
|
+
operation,
|
|
8507
|
+
resourceIdentity
|
|
8508
|
+
})))
|
|
8477
8509
|
});
|
|
8478
8510
|
}
|
|
8479
8511
|
function familyForSkill(skill) {
|
|
@@ -8900,6 +8932,13 @@ function createWorkflowGuard(options) {
|
|
|
8900
8932
|
return "receipt-mismatch";
|
|
8901
8933
|
return context.worktreeIdentity !== currentWorktreeIdentity ? "receipt-mismatch" : undefined;
|
|
8902
8934
|
}
|
|
8935
|
+
function currentOperationContext() {
|
|
8936
|
+
return Object.freeze({
|
|
8937
|
+
workspaceIdentity: currentWorkspaceIdentity,
|
|
8938
|
+
...currentRepositoryIdentity === undefined ? {} : { repositoryIdentity: currentRepositoryIdentity },
|
|
8939
|
+
...currentWorktreeIdentity === undefined ? {} : { worktreeIdentity: currentWorktreeIdentity }
|
|
8940
|
+
});
|
|
8941
|
+
}
|
|
8903
8942
|
function resourceBeforeReason(input, unit) {
|
|
8904
8943
|
if (!operationUsesResource(input.operation))
|
|
8905
8944
|
return;
|
|
@@ -9591,9 +9630,10 @@ function createWorkflowGuard(options) {
|
|
|
9591
9630
|
}
|
|
9592
9631
|
return;
|
|
9593
9632
|
}
|
|
9594
|
-
function mergeResourceScopes(trustedPolicy, modelScopes) {
|
|
9595
|
-
const result = new Map(runtimeScopes);
|
|
9596
|
-
|
|
9633
|
+
function mergeResourceScopes(trustedPolicy, modelScopes, existingScopes) {
|
|
9634
|
+
const result = new Map(existingScopes ?? runtimeScopes);
|
|
9635
|
+
const scopesToMerge = existingScopes ? [runtimeScopes, trustedPolicy.resourceScopes, modelScopes] : [trustedPolicy.resourceScopes, modelScopes];
|
|
9636
|
+
for (const scopes of scopesToMerge) {
|
|
9597
9637
|
for (const [operation, resource] of scopes) {
|
|
9598
9638
|
const existing = result.get(operation);
|
|
9599
9639
|
if (existing && existing !== resource)
|
|
@@ -9603,22 +9643,71 @@ function createWorkflowGuard(options) {
|
|
|
9603
9643
|
}
|
|
9604
9644
|
return result;
|
|
9605
9645
|
}
|
|
9606
|
-
function requiredOperationsFor(trustedPolicy, model, resourceScopes) {
|
|
9646
|
+
function requiredOperationsFor(trustedPolicy, model, resourceScopes, existingOperations = []) {
|
|
9607
9647
|
return Object.freeze([
|
|
9608
9648
|
...new Set([
|
|
9609
9649
|
...MANDATORY_OPERATIONS,
|
|
9610
9650
|
...runtimeRequired,
|
|
9651
|
+
...existingOperations,
|
|
9611
9652
|
...trustedPolicy.expectedOperations,
|
|
9612
9653
|
...model.expectedOperations,
|
|
9613
9654
|
...resourceScopes.keys()
|
|
9614
9655
|
])
|
|
9615
9656
|
]);
|
|
9616
9657
|
}
|
|
9658
|
+
function resourceIdentitiesMatchUnit(unit) {
|
|
9659
|
+
const expected = new Map(runtimeScopes);
|
|
9660
|
+
for (const [operation, resource] of unit.resourceScopes) {
|
|
9661
|
+
expected.set(operation, resource);
|
|
9662
|
+
}
|
|
9663
|
+
if (currentResourceIdentities.size !== expected.size)
|
|
9664
|
+
return false;
|
|
9665
|
+
for (const [operation, resource] of expected) {
|
|
9666
|
+
if (currentResourceIdentities.get(operation) !== resource)
|
|
9667
|
+
return false;
|
|
9668
|
+
}
|
|
9669
|
+
return true;
|
|
9670
|
+
}
|
|
9671
|
+
function pristineActiveUnit(unit) {
|
|
9672
|
+
return unit.evidence.size === 0 && unit.issues.size === 0 && unit.staleReceiptIds.size === 0 && unit.recoveredReceiptIds.size === 0 && unit.operationStates.size === 0 && unit.ledgerContexts.size === 0 && globalIssue === undefined && transitionsByCall.size === 0 && terminalOperationCalls.size === 0 && currentResourceRevisionIdentities.size === 0 && currentPullRequestFingerprint === undefined && currentWorkspaceIdentity === initialWorkspaceIdentity && currentRepositoryIdentity === initialRepositoryIdentity && currentWorktreeIdentity === initialWorktreeIdentity && resourceIdentitiesMatchUnit(unit);
|
|
9673
|
+
}
|
|
9674
|
+
function declarationChanged(unit, requiredOperations, resourceScopes) {
|
|
9675
|
+
if (JSON.stringify(unit.requiredOperations) !== JSON.stringify(requiredOperations)) {
|
|
9676
|
+
return true;
|
|
9677
|
+
}
|
|
9678
|
+
if (unit.resourceScopes.size !== resourceScopes.size)
|
|
9679
|
+
return true;
|
|
9680
|
+
for (const [operation, resource] of resourceScopes) {
|
|
9681
|
+
if (unit.resourceScopes.get(operation) !== resource)
|
|
9682
|
+
return true;
|
|
9683
|
+
}
|
|
9684
|
+
return false;
|
|
9685
|
+
}
|
|
9686
|
+
function startActiveUnit(parsed, trustedPolicy, unit) {
|
|
9687
|
+
if (!pristineActiveUnit(unit)) {
|
|
9688
|
+
return { status: "rejected", reasonCode: "unit-active" };
|
|
9689
|
+
}
|
|
9690
|
+
const resourceScopes = mergeResourceScopes(trustedPolicy, parsed.resourceScopes, unit.resourceScopes);
|
|
9691
|
+
if (!resourceScopes) {
|
|
9692
|
+
return { status: "rejected", reasonCode: "runtime-scope-conflict" };
|
|
9693
|
+
}
|
|
9694
|
+
const requiredOperations = requiredOperationsFor(trustedPolicy, parsed, resourceScopes, unit.requiredOperations);
|
|
9695
|
+
if (!declarationChanged(unit, requiredOperations, resourceScopes)) {
|
|
9696
|
+
return { status: "rejected", reasonCode: "unit-active" };
|
|
9697
|
+
}
|
|
9698
|
+
unit.requiredOperations = requiredOperations;
|
|
9699
|
+
unit.declaredResourceOperations = Object.freeze([...resourceScopes.keys()]);
|
|
9700
|
+
unit.resourceScopes = resourceScopes;
|
|
9701
|
+
for (const [operation, resource] of resourceScopes) {
|
|
9702
|
+
currentResourceIdentities.set(operation, resource);
|
|
9703
|
+
}
|
|
9704
|
+
return { status: "started", unit: cloneUnit(unit) };
|
|
9705
|
+
}
|
|
9617
9706
|
function startParsedUnit(parsed, trustedPolicy) {
|
|
9618
9707
|
if (!epoch)
|
|
9619
9708
|
return { status: "rejected", reasonCode: "no-active-epoch" };
|
|
9620
9709
|
if (epoch.unit?.status === "active") {
|
|
9621
|
-
return
|
|
9710
|
+
return startActiveUnit(parsed, trustedPolicy, epoch.unit);
|
|
9622
9711
|
}
|
|
9623
9712
|
const resourceScopes = mergeResourceScopes(trustedPolicy, parsed.resourceScopes);
|
|
9624
9713
|
if (!resourceScopes) {
|
|
@@ -9706,7 +9795,7 @@ function createWorkflowGuard(options) {
|
|
|
9706
9795
|
reasonCode: existing.state === "abandoned" ? "abandoned-transition" : "transition-terminal"
|
|
9707
9796
|
};
|
|
9708
9797
|
}
|
|
9709
|
-
async function processOperation(input, parsed, unit) {
|
|
9798
|
+
async function processOperation(input, parsed, unit, trustedClassification) {
|
|
9710
9799
|
if (!unit.requiredOperations.includes(parsed.operation)) {
|
|
9711
9800
|
markTerminalOperation(parsed, input);
|
|
9712
9801
|
return { status: "rejected", reasonCode: "operation-not-required" };
|
|
@@ -9725,7 +9814,7 @@ function createWorkflowGuard(options) {
|
|
|
9725
9814
|
if (prepared.status !== "prepared") {
|
|
9726
9815
|
return prepared.reasonCode === "call-context-conflict" ? { status: "rejected", reasonCode: "call-context-conflict" } : { status: "rejected", reasonCode: "rejected-operation" };
|
|
9727
9816
|
}
|
|
9728
|
-
const classification = await classifyPreparedOperation(parsed, input, unit);
|
|
9817
|
+
const classification = trustedClassification ?? await classifyPreparedOperation(parsed, input, unit);
|
|
9729
9818
|
return classification ? finalizeOperation(parsed, input, unit, classification) : { status: "rejected", reasonCode: "guard-unavailable" };
|
|
9730
9819
|
}
|
|
9731
9820
|
function rejectParsedOperation(parsed, input, unit, reasonCode) {
|
|
@@ -10231,12 +10320,44 @@ function createWorkflowGuard(options) {
|
|
|
10231
10320
|
}
|
|
10232
10321
|
return processOperation(input, parsed, epoch.unit);
|
|
10233
10322
|
},
|
|
10323
|
+
async observeTrustedRecoveredOperation(input) {
|
|
10324
|
+
const modeResult = evidenceModeResult();
|
|
10325
|
+
if (modeResult)
|
|
10326
|
+
return modeResult;
|
|
10327
|
+
if (!epoch)
|
|
10328
|
+
return { status: "rejected", reasonCode: "no-active-epoch" };
|
|
10329
|
+
if (!epoch.unit)
|
|
10330
|
+
return { status: "rejected", reasonCode: "no-active-unit" };
|
|
10331
|
+
if (epoch.unit.status === "completed") {
|
|
10332
|
+
return { status: "rejected", reasonCode: "unit-completed" };
|
|
10333
|
+
}
|
|
10334
|
+
const terminalResult = terminalOperationResult(input);
|
|
10335
|
+
if (terminalResult)
|
|
10336
|
+
return terminalResult;
|
|
10337
|
+
const parsed = parseReceiptOperationObservation(input);
|
|
10338
|
+
if (!parsed) {
|
|
10339
|
+
markTerminalOperation(undefined, input);
|
|
10340
|
+
return recordGlobalEvidenceIssue("invalid-receipt");
|
|
10341
|
+
}
|
|
10342
|
+
const classification = {
|
|
10343
|
+
outcome: "accepted",
|
|
10344
|
+
category: parsed.operation,
|
|
10345
|
+
attribution: "runtime-verified",
|
|
10346
|
+
result: "success",
|
|
10347
|
+
sideEffect: parsed.operation === "verification" || parsed.operation === "check-readback" || parsed.operation === "review-readback" ? "not-required" : "required",
|
|
10348
|
+
reasonCode: "recognized-command"
|
|
10349
|
+
};
|
|
10350
|
+
return processOperation(input, parsed, epoch.unit, classification);
|
|
10351
|
+
},
|
|
10234
10352
|
observeReadback(input) {
|
|
10235
10353
|
const parsed = parseReadback(input);
|
|
10236
10354
|
if (!parsed)
|
|
10237
10355
|
return { status: "rejected", reasonCode: "invalid-receipt" };
|
|
10238
10356
|
return observeRevision(parsed, epoch?.unit);
|
|
10239
10357
|
},
|
|
10358
|
+
currentOperationContext() {
|
|
10359
|
+
return currentOperationContext();
|
|
10360
|
+
},
|
|
10240
10361
|
status() {
|
|
10241
10362
|
return projection();
|
|
10242
10363
|
},
|
|
@@ -11156,6 +11277,7 @@ function createSessionRuntime(options) {
|
|
|
11156
11277
|
const rollupKey = `${host.sessionID}:${host.callID}:${childSessionID}`;
|
|
11157
11278
|
if (rolledUpChildren.has(rollupKey))
|
|
11158
11279
|
return;
|
|
11280
|
+
const parentBefore = guard.currentOperationContext();
|
|
11159
11281
|
const children = await options.hostReadback.listChildren(host.sessionID);
|
|
11160
11282
|
if (!children.some((child) => child.sessionId === childSessionID && child.parentID === host.sessionID)) {
|
|
11161
11283
|
markUnavailable();
|
|
@@ -11203,18 +11325,27 @@ function createSessionRuntime(options) {
|
|
|
11203
11325
|
const currentStatus = guard.status();
|
|
11204
11326
|
if (!currentStatus.epoch || !currentStatus.unit)
|
|
11205
11327
|
return;
|
|
11206
|
-
const expectedWorkspace =
|
|
11207
|
-
const expectedRepository =
|
|
11208
|
-
const expectedWorktree =
|
|
11209
|
-
|
|
11328
|
+
const expectedWorkspace = childLedger.digestIdentity("workspace", parentBefore.workspaceIdentity);
|
|
11329
|
+
const expectedRepository = childLedger.digestIdentity("repository", current.snapshot.repositoryRevisionDigest);
|
|
11330
|
+
const expectedWorktree = childLedger.digestIdentity("worktree", current.snapshot.worktreeRevisionDigest);
|
|
11331
|
+
const candidates = [];
|
|
11210
11332
|
for (const childReceipt of recovered.receipts) {
|
|
11211
11333
|
const operation = childReceipt.canonical.operation;
|
|
11212
11334
|
if (!localOperation(operation))
|
|
11213
11335
|
continue;
|
|
11214
|
-
if (childReceipt.canonical.workspaceDigest !== expectedWorkspace
|
|
11336
|
+
if (childReceipt.canonical.workspaceDigest !== expectedWorkspace) {
|
|
11215
11337
|
markUnavailable();
|
|
11216
11338
|
return;
|
|
11217
11339
|
}
|
|
11340
|
+
if (childReceipt.canonical.repositoryDigest !== expectedRepository || childReceipt.canonical.worktreeDigest !== expectedWorktree) {
|
|
11341
|
+
continue;
|
|
11342
|
+
}
|
|
11343
|
+
candidates.push(childReceipt);
|
|
11344
|
+
}
|
|
11345
|
+
let minted = false;
|
|
11346
|
+
for (const childReceipt of candidates) {
|
|
11347
|
+
const operation = childReceipt.canonical.operation;
|
|
11348
|
+
const parentContext = guard.currentOperationContext();
|
|
11218
11349
|
const callID = `task-${host.callID}-${childReceipt.canonical.receiptId}`;
|
|
11219
11350
|
const observation = {
|
|
11220
11351
|
callId: callID,
|
|
@@ -11223,12 +11354,12 @@ function createSessionRuntime(options) {
|
|
|
11223
11354
|
context: {
|
|
11224
11355
|
epochId: currentStatus.epoch.epochId,
|
|
11225
11356
|
unitId: currentStatus.unit.unitId,
|
|
11226
|
-
workspaceIdentity:
|
|
11227
|
-
repositoryIdentity
|
|
11228
|
-
worktreeIdentity
|
|
11357
|
+
workspaceIdentity: parentContext.workspaceIdentity,
|
|
11358
|
+
...parentContext.repositoryIdentity ? { repositoryIdentity: parentContext.repositoryIdentity } : {},
|
|
11359
|
+
...parentContext.worktreeIdentity ? { worktreeIdentity: parentContext.worktreeIdentity } : {}
|
|
11229
11360
|
},
|
|
11230
11361
|
after: {
|
|
11231
|
-
workspaceIdentity:
|
|
11362
|
+
workspaceIdentity: parentContext.workspaceIdentity,
|
|
11232
11363
|
repositoryIdentity: current.snapshot.repositoryRevisionDigest,
|
|
11233
11364
|
worktreeIdentity: current.snapshot.worktreeRevisionDigest
|
|
11234
11365
|
},
|
|
@@ -11238,7 +11369,7 @@ function createSessionRuntime(options) {
|
|
|
11238
11369
|
noOp: false
|
|
11239
11370
|
}
|
|
11240
11371
|
};
|
|
11241
|
-
const result = await guard.
|
|
11372
|
+
const result = await guard.observeTrustedRecoveredOperation(observation);
|
|
11242
11373
|
if (result.status === "accepted") {
|
|
11243
11374
|
const receipt = receiptForOperation(callID, operation);
|
|
11244
11375
|
if (receipt)
|
|
@@ -11433,13 +11564,21 @@ function createSessionRuntime(options) {
|
|
|
11433
11564
|
}
|
|
11434
11565
|
return result;
|
|
11435
11566
|
}
|
|
11567
|
+
function progressionResourceScopes(unit) {
|
|
11568
|
+
return [...unit.resourceScopes].sort((first, second) => first.operation.localeCompare(second.operation)).map((scope) => ({
|
|
11569
|
+
operation: scope.operation,
|
|
11570
|
+
resourceIdentity: ledger.digestIdentity("resource", scope.resourceIdentity)
|
|
11571
|
+
}));
|
|
11572
|
+
}
|
|
11436
11573
|
function writeStartResult(output, result) {
|
|
11437
11574
|
if (!isRecord7(output))
|
|
11438
11575
|
return;
|
|
11576
|
+
const existingMetadata = isRecord7(output.metadata) ? output.metadata : {};
|
|
11439
11577
|
if (result.status === "started") {
|
|
11440
11578
|
output.title = "Workflow unit started";
|
|
11441
11579
|
output.output = JSON.stringify({ status: "started" });
|
|
11442
11580
|
output.metadata = {
|
|
11581
|
+
...existingMetadata,
|
|
11443
11582
|
...metadata2(),
|
|
11444
11583
|
workflowGuard: { status: "started" }
|
|
11445
11584
|
};
|
|
@@ -11452,6 +11591,7 @@ function createSessionRuntime(options) {
|
|
|
11452
11591
|
reasonCode
|
|
11453
11592
|
});
|
|
11454
11593
|
output.metadata = {
|
|
11594
|
+
...existingMetadata,
|
|
11455
11595
|
...metadata2(),
|
|
11456
11596
|
workflowGuard: {
|
|
11457
11597
|
status: "rejected",
|
|
@@ -11783,7 +11923,7 @@ function createSessionRuntime(options) {
|
|
|
11783
11923
|
unitId: status.unit.unitId,
|
|
11784
11924
|
family: status.epoch.family,
|
|
11785
11925
|
requiredOperations: status.unit.requiredOperations,
|
|
11786
|
-
resourceScopes:
|
|
11926
|
+
resourceScopes: progressionResourceScopes(status.unit),
|
|
11787
11927
|
state: "started",
|
|
11788
11928
|
transitionDigest: ledger.digestIdentity("call", host.callID)
|
|
11789
11929
|
}));
|
|
@@ -11840,7 +11980,7 @@ function createSessionRuntime(options) {
|
|
|
11840
11980
|
unitId: unit.unitId,
|
|
11841
11981
|
family: epoch.family,
|
|
11842
11982
|
requiredOperations: unit.requiredOperations,
|
|
11843
|
-
resourceScopes:
|
|
11983
|
+
resourceScopes: progressionResourceScopes(unit),
|
|
11844
11984
|
state: "started",
|
|
11845
11985
|
transitionDigest: ledger.digestIdentity("call", host.callID)
|
|
11846
11986
|
}));
|
|
@@ -12205,9 +12345,10 @@ function createSessionRuntime(options) {
|
|
|
12205
12345
|
const marker = projectReceiptMintMarker(receipt, ledger.getSessionSalt());
|
|
12206
12346
|
if (!marker)
|
|
12207
12347
|
return;
|
|
12348
|
+
const existing = output.metadata[SYSTEMATIC_WORKFLOW_RECEIPT_METADATA_KEY];
|
|
12208
12349
|
output.metadata = {
|
|
12209
12350
|
...output.metadata,
|
|
12210
|
-
[SYSTEMATIC_WORKFLOW_RECEIPT_METADATA_KEY]: marker
|
|
12351
|
+
[SYSTEMATIC_WORKFLOW_RECEIPT_METADATA_KEY]: existing ? Array.isArray(existing) ? [...existing, marker] : [existing, marker] : marker
|
|
12211
12352
|
};
|
|
12212
12353
|
}
|
|
12213
12354
|
function mergeProgressionMarker(output, marker) {
|
|
@@ -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;
|