pi-crew 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +57 -32
- package/CHANGELOG.md +466 -448
- package/LICENSE +21 -21
- package/NOTICE.md +16 -16
- package/README.md +323 -323
- package/docs/FEATURE_INTAKE.md +126 -0
- package/docs/HARNESS.md +86 -0
- package/docs/HARNESS_BACKLOG.md +41 -0
- package/docs/TEST_MATRIX.md +49 -0
- package/docs/actions-reference.md +595 -595
- package/docs/architecture.md +180 -180
- package/docs/code-review-2026-05-11.md +592 -592
- package/docs/commands-reference.md +347 -347
- package/docs/comparison-pi-subagents-vs-pi-crew.md +303 -0
- package/docs/decisions/0001-durable-state.md +41 -0
- package/docs/decisions/0002-child-process-for-async.md +42 -0
- package/docs/decisions/0003-depth-guard.md +36 -0
- package/docs/decisions/0004-execfile-over-exec.md +34 -0
- package/docs/decisions/0005-no-parameter-properties.md +49 -0
- package/docs/decisions/0006-publish-bundled-esm.md +63 -0
- package/docs/decisions/0007-active-run-binary-index.md +54 -0
- package/docs/decisions/0008-child-pi-warm-pool.md +61 -0
- package/docs/decisions/README.md +23 -0
- package/docs/followup-review-round4-2026-05-13.md +107 -0
- package/docs/implementation-plan-top3.md +333 -0
- package/docs/live-mailbox-runtime.md +36 -36
- package/docs/next-upgrade-roadmap.md +808 -808
- package/docs/oh-my-pi-research.md +509 -0
- package/docs/perf/baseline-2026-05.md +113 -0
- package/docs/perf/final-report-2026-05.md +206 -0
- package/docs/perf/sprint-1-report.md +71 -0
- package/docs/perf/sprint-2-report.md +81 -0
- package/docs/perf/sprint-2.5-report.md +53 -0
- package/docs/perf/sprint-3-report.md +36 -0
- package/docs/perf/sprint-4-report.md +47 -0
- package/docs/perf/sprint-5-report.md +51 -0
- package/docs/perf/sprint-6-report.md +94 -0
- package/docs/perf/sprint-7-report.md +74 -0
- package/docs/perf/upgrade-plan-2026-05.md +147 -0
- package/docs/pi-subagents3-deep-analysis.md +508 -0
- package/docs/product/README.md +31 -0
- package/docs/product/platform.md +27 -0
- package/docs/product/runtime-safety.md +37 -0
- package/docs/product/team-run.md +39 -0
- package/docs/product/team-tool.md +37 -0
- package/docs/publishing.md +65 -65
- package/docs/resource-formats.md +134 -134
- package/docs/runtime-analysis-child-vs-live.md +171 -0
- package/docs/runtime-flow.md +148 -148
- package/docs/runtime-migration-in-process-analysis.md +250 -0
- package/docs/stories/README.md +30 -0
- package/docs/stories/backlog.md +36 -0
- package/docs/templates/decision.md +27 -0
- package/docs/templates/story.md +44 -0
- package/docs/templates/validation-report.md +32 -0
- package/docs/usage.md +238 -238
- package/index.ts +7 -6
- package/install.mjs +65 -65
- package/package.json +107 -100
- package/schema.json +222 -222
- package/skills/child-pi-spawning/SKILL.md +213 -0
- package/skills/context-artifact-hygiene/SKILL.md +32 -0
- package/skills/event-log-tracing/SKILL.md +299 -0
- package/skills/git-master/SKILL.md +225 -24
- package/skills/live-agent-lifecycle/SKILL.md +192 -0
- package/skills/mailbox-interactive/SKILL.md +300 -19
- package/skills/model-routing-context/SKILL.md +94 -0
- package/skills/multi-perspective-review/SKILL.md +88 -0
- package/skills/read-only-explorer/SKILL.md +250 -26
- package/skills/safe-bash/SKILL.md +307 -21
- package/skills/verification-before-done/SKILL.md +11 -2
- package/skills/widget-rendering/SKILL.md +258 -0
- package/skills/workspace-isolation/SKILL.md +202 -0
- package/skills/worktree-isolation/SKILL.md +202 -18
- package/src/adapters/claude-adapter.ts +25 -25
- package/src/adapters/codex-adapter.ts +21 -21
- package/src/adapters/cursor-adapter.ts +17 -17
- package/src/adapters/export-util.ts +137 -137
- package/src/adapters/index.ts +15 -15
- package/src/adapters/registry.ts +18 -18
- package/src/adapters/types.ts +23 -23
- package/src/agents/agent-config.ts +38 -38
- package/src/agents/agent-serializer.ts +38 -38
- package/src/agents/discover-agents.ts +121 -118
- package/src/config/config.ts +740 -858
- package/src/config/defaults.ts +96 -96
- package/src/config/drift-detector.ts +211 -211
- package/src/config/markers.ts +327 -327
- package/src/config/resilient-parser.ts +109 -108
- package/src/config/suggestions.ts +74 -74
- package/src/config/types.ts +199 -0
- package/src/extension/async-notifier.ts +123 -89
- package/src/extension/autonomous-policy.ts +169 -169
- package/src/extension/cross-extension-rpc.ts +104 -104
- package/src/extension/help.ts +47 -47
- package/src/extension/import-index.ts +69 -69
- package/src/extension/management.ts +395 -382
- package/src/extension/notification-router.ts +116 -116
- package/src/extension/notification-sink.ts +51 -51
- package/src/extension/project-init.ts +168 -168
- package/src/extension/register.ts +859 -668
- package/src/extension/registration/artifact-cleanup.ts +15 -15
- package/src/extension/registration/command-utils.ts +54 -54
- package/src/extension/registration/commands.ts +559 -452
- package/src/extension/registration/compaction-guard.ts +125 -125
- package/src/extension/registration/subagent-helpers.ts +102 -102
- package/src/extension/registration/subagent-tools.ts +220 -159
- package/src/extension/registration/team-tool.ts +159 -99
- package/src/extension/registration/viewers.ts +29 -0
- package/src/extension/result-watcher.ts +128 -128
- package/src/extension/run-bundle-schema.ts +89 -89
- package/src/extension/run-export.ts +73 -73
- package/src/extension/run-import.ts +84 -84
- package/src/extension/run-index.ts +94 -94
- package/src/extension/run-maintenance.ts +142 -142
- package/src/extension/session-summary.ts +8 -8
- package/src/extension/team-manager-command.ts +96 -96
- package/src/extension/team-recommendation.ts +188 -188
- package/src/extension/team-tool/api.ts +5 -2
- package/src/extension/team-tool/cancel.ts +224 -209
- package/src/extension/team-tool/config-patch.ts +36 -36
- package/src/extension/team-tool/context.ts +60 -60
- package/src/extension/team-tool/doctor.ts +242 -242
- package/src/extension/team-tool/handle-settings.ts +421 -195
- package/src/extension/team-tool/inspect.ts +41 -41
- package/src/extension/team-tool/lifecycle-actions.ts +139 -139
- package/src/extension/team-tool/parallel-dispatch.ts +156 -156
- package/src/extension/team-tool/plan.ts +19 -19
- package/src/extension/team-tool/respond.ts +112 -111
- package/src/extension/team-tool/run.ts +246 -229
- package/src/extension/team-tool/status.ts +110 -110
- package/src/extension/team-tool-types.ts +13 -13
- package/src/extension/team-tool.ts +344 -344
- package/src/extension/tool-result.ts +16 -16
- package/src/extension/validate-resources.ts +77 -77
- package/src/hooks/registry.ts +61 -61
- package/src/hooks/types.ts +40 -40
- package/src/i18n.ts +184 -184
- package/src/observability/correlation.ts +35 -35
- package/src/observability/event-to-metric.ts +68 -68
- package/src/observability/exporters/adapter.ts +30 -30
- package/src/observability/exporters/otlp-exporter.ts +106 -92
- package/src/observability/exporters/prometheus-exporter.ts +54 -54
- package/src/observability/metric-registry.ts +87 -87
- package/src/observability/metric-retention.ts +54 -54
- package/src/observability/metric-sink.ts +81 -56
- package/src/observability/metrics-primitives.ts +167 -167
- package/src/prompt/prompt-runtime.ts +72 -72
- package/src/runtime/adaptive-plan.ts +338 -0
- package/src/runtime/agent-control.ts +169 -169
- package/src/runtime/agent-memory.ts +72 -72
- package/src/runtime/agent-observability.ts +114 -114
- package/src/runtime/async-marker.ts +26 -26
- package/src/runtime/async-runner.ts +153 -153
- package/src/runtime/attention-events.ts +28 -28
- package/src/runtime/auto-resume.ts +100 -100
- package/src/runtime/background-runner.ts +122 -89
- package/src/runtime/cancellation.ts +61 -61
- package/src/runtime/capability-inventory.ts +116 -116
- package/src/runtime/child-pi-pool.ts +68 -0
- package/src/runtime/child-pi.ts +541 -461
- package/src/runtime/code-summary.ts +247 -247
- package/src/runtime/compaction-summary.ts +271 -271
- package/src/runtime/concurrency.ts +58 -58
- package/src/runtime/crash-recovery.ts +317 -301
- package/src/runtime/crew-agent-records.ts +379 -281
- package/src/runtime/crew-agent-runtime.ts +60 -60
- package/src/runtime/cross-extension-rpc.ts +72 -0
- package/src/runtime/custom-tools/irc-tool.ts +201 -201
- package/src/runtime/custom-tools/submit-result-tool.ts +90 -90
- package/src/runtime/deadletter.ts +47 -47
- package/src/runtime/delivery-coordinator.ts +176 -176
- package/src/runtime/delta-conflict.ts +360 -360
- package/src/runtime/diagnostic-export.ts +102 -102
- package/src/runtime/direct-run.ts +35 -35
- package/src/runtime/effectiveness.ts +82 -81
- package/src/runtime/errors/crew-errors.ts +166 -0
- package/src/runtime/event-stream-bridge.ts +92 -92
- package/src/runtime/foreground-control.ts +82 -82
- package/src/runtime/green-contract.ts +46 -46
- package/src/runtime/group-join.ts +234 -106
- package/src/runtime/heartbeat-watcher.ts +145 -124
- package/src/runtime/iteration-hooks.ts +267 -267
- package/src/runtime/live-agent-control.ts +88 -88
- package/src/runtime/live-agent-manager.ts +377 -179
- package/src/runtime/live-control-realtime.ts +36 -36
- package/src/runtime/live-session-runtime.ts +676 -600
- package/src/runtime/loop-gates.ts +129 -129
- package/src/runtime/manifest-cache.ts +263 -263
- package/src/runtime/mcp-proxy.ts +113 -113
- package/src/runtime/metric-parser.ts +40 -40
- package/src/runtime/model-fallback.ts +282 -274
- package/src/runtime/model-resolver.ts +118 -0
- package/src/runtime/output-validator.ts +187 -187
- package/src/runtime/overflow-recovery.ts +175 -175
- package/src/runtime/parallel-research.ts +44 -44
- package/src/runtime/parallel-utils.ts +156 -156
- package/src/runtime/parent-guard.ts +80 -80
- package/src/runtime/phase-progress.ts +217 -217
- package/src/runtime/pi-args.ts +165 -165
- package/src/runtime/pi-json-output.ts +111 -111
- package/src/runtime/pi-spawn.ts +167 -167
- package/src/runtime/policy-engine.ts +79 -79
- package/src/runtime/post-checks.ts +125 -125
- package/src/runtime/post-exit-stdio-guard.ts +86 -86
- package/src/runtime/process-status.ts +97 -73
- package/src/runtime/progress-event-coalescer.ts +43 -43
- package/src/runtime/recovery-recipes.ts +74 -74
- package/src/runtime/retry-executor.ts +81 -81
- package/src/runtime/role-permission.ts +39 -39
- package/src/runtime/run-tracker.ts +99 -0
- package/src/runtime/runtime-policy.ts +21 -0
- package/src/runtime/runtime-resolver.ts +94 -91
- package/src/runtime/scheduler.ts +294 -0
- package/src/runtime/semaphore.ts +131 -131
- package/src/runtime/sensitive-paths.ts +92 -92
- package/src/runtime/session-usage.ts +79 -79
- package/src/runtime/settings-store.ts +103 -0
- package/src/runtime/sidechain-output.ts +29 -29
- package/src/runtime/skill-instructions.ts +222 -222
- package/src/runtime/stale-reconciler.ts +198 -189
- package/src/runtime/streaming-output.ts +47 -0
- package/src/runtime/subagent-manager.ts +404 -400
- package/src/runtime/subprocess-tool-registry.ts +67 -67
- package/src/runtime/task-display.ts +38 -38
- package/src/runtime/task-graph-scheduler.ts +122 -122
- package/src/runtime/task-graph.ts +207 -207
- package/src/runtime/task-output-context.ts +177 -177
- package/src/runtime/task-packet.ts +93 -93
- package/src/runtime/task-quality.ts +207 -207
- package/src/runtime/task-runner/capabilities.ts +78 -78
- package/src/runtime/task-runner/live-executor.ts +131 -113
- package/src/runtime/task-runner/progress.ts +119 -119
- package/src/runtime/task-runner/prompt-builder.ts +139 -139
- package/src/runtime/task-runner/prompt-pipeline.ts +64 -64
- package/src/runtime/task-runner/result-utils.ts +14 -14
- package/src/runtime/task-runner/run-projection.ts +103 -103
- package/src/runtime/task-runner/state-helpers.ts +22 -22
- package/src/runtime/task-runner.ts +469 -459
- package/src/runtime/team-runner.ts +693 -945
- package/src/runtime/usage-tracker.ts +71 -0
- package/src/runtime/worker-heartbeat.ts +21 -21
- package/src/runtime/worker-startup.ts +57 -57
- package/src/runtime/workflow-state.ts +187 -187
- package/src/runtime/yield-handler.ts +190 -190
- package/src/schema/config-schema.ts +172 -168
- package/src/schema/team-tool-schema.ts +126 -126
- package/src/schema/validation-types.ts +151 -148
- package/src/skills/discover-skills.ts +67 -67
- package/src/skills/skill-templates.ts +374 -374
- package/src/state/active-run-registry.ts +227 -191
- package/src/state/artifact-store.ts +130 -129
- package/src/state/atomic-write.ts +262 -195
- package/src/state/blob-store.ts +116 -116
- package/src/state/contracts.ts +111 -111
- package/src/state/event-log-rotation.ts +161 -158
- package/src/state/event-log.ts +383 -303
- package/src/state/event-reconstructor.ts +217 -217
- package/src/state/jsonl-writer.ts +82 -82
- package/src/state/locks.ts +146 -146
- package/src/state/mailbox.ts +446 -405
- package/src/state/state-store.ts +364 -351
- package/src/state/task-claims.ts +44 -44
- package/src/state/types.ts +285 -285
- package/src/state/usage.ts +29 -29
- package/src/subagents/async-entry.ts +1 -1
- package/src/subagents/index.ts +3 -3
- package/src/subagents/live/control.ts +1 -1
- package/src/subagents/live/manager.ts +1 -1
- package/src/subagents/live/realtime.ts +1 -1
- package/src/subagents/live/session-runtime.ts +1 -1
- package/src/subagents/manager.ts +1 -1
- package/src/subagents/spawn.ts +1 -1
- package/src/teams/discover-teams.ts +116 -116
- package/src/teams/team-config.ts +27 -27
- package/src/teams/team-serializer.ts +38 -38
- package/src/types/diff.d.ts +18 -18
- package/src/ui/agent-management-overlay.ts +144 -144
- package/src/ui/crew-widget.ts +487 -370
- package/src/ui/dashboard-panes/agents-pane.ts +109 -28
- package/src/ui/dashboard-panes/cancellation-pane.ts +42 -42
- package/src/ui/dashboard-panes/capability-pane.ts +59 -59
- package/src/ui/dashboard-panes/health-pane.ts +30 -30
- package/src/ui/dashboard-panes/mailbox-pane.ts +35 -35
- package/src/ui/dashboard-panes/progress-pane.ts +30 -30
- package/src/ui/dashboard-panes/transcript-pane.ts +10 -10
- package/src/ui/heartbeat-aggregator.ts +63 -63
- package/src/ui/keybinding-map.ts +97 -94
- package/src/ui/live-conversation-overlay.ts +152 -0
- package/src/ui/live-run-sidebar.ts +180 -180
- package/src/ui/mascot.ts +442 -442
- package/src/ui/overlays/agent-picker-overlay.ts +57 -57
- package/src/ui/overlays/confirm-overlay.ts +58 -58
- package/src/ui/overlays/mailbox-compose-overlay.ts +144 -144
- package/src/ui/overlays/mailbox-compose-preview.ts +63 -63
- package/src/ui/overlays/mailbox-detail-overlay.ts +122 -122
- package/src/ui/pi-ui-compat.ts +57 -57
- package/src/ui/powerbar-publisher.ts +221 -197
- package/src/ui/render-scheduler.ts +216 -143
- package/src/ui/run-action-dispatcher.ts +118 -118
- package/src/ui/run-dashboard.ts +526 -464
- package/src/ui/run-event-bus.ts +208 -208
- package/src/ui/run-snapshot-cache.ts +826 -777
- package/src/ui/settings-overlay.ts +721 -0
- package/src/ui/snapshot-types.ts +86 -70
- package/src/ui/theme-adapter.ts +190 -190
- package/src/ui/tool-progress-formatter.ts +89 -0
- package/src/ui/transcript-cache.ts +94 -94
- package/src/ui/transcript-viewer.ts +335 -335
- package/src/utils/conflict-detect.ts +662 -0
- package/src/utils/file-coalescer.ts +86 -86
- package/src/utils/frontmatter.ts +68 -68
- package/src/utils/fs-watch.ts +88 -31
- package/src/utils/gh-protocol.ts +479 -0
- package/src/utils/ids.ts +17 -17
- package/src/utils/incremental-reader.ts +104 -104
- package/src/utils/internal-error.ts +6 -6
- package/src/utils/names.ts +27 -27
- package/src/utils/paths.ts +102 -63
- package/src/utils/redaction.ts +44 -44
- package/src/utils/safe-paths.ts +47 -47
- package/src/utils/scan-cache.ts +136 -136
- package/src/utils/sse-parser.ts +134 -134
- package/src/utils/task-name-generator.ts +337 -337
- package/src/utils/timings.ts +33 -33
- package/src/utils/visual.ts +243 -198
- package/src/workflows/discover-workflows.ts +139 -139
- package/src/workflows/validate-workflow.ts +40 -40
- package/src/workflows/workflow-config.ts +26 -26
- package/src/workflows/workflow-serializer.ts +32 -32
- package/src/worktree/branch-freshness.ts +45 -45
- package/src/worktree/cleanup.ts +75 -75
- package/src/worktree/worktree-manager.ts +188 -188
- package/teams/default.team.md +12 -12
- package/teams/fast-fix.team.md +11 -11
- package/teams/implementation.team.md +18 -18
- package/teams/parallel-research.team.md +14 -14
- package/teams/research.team.md +11 -11
- package/teams/review.team.md +12 -12
- package/tsconfig.json +19 -19
- package/workflows/default.workflow.md +30 -30
- package/workflows/fast-fix.workflow.md +23 -23
- package/workflows/implementation.workflow.md +43 -43
- package/workflows/parallel-research.workflow.md +46 -46
- package/workflows/research.workflow.md +22 -22
- package/workflows/review.workflow.md +30 -30
- package/skills/task-packet/SKILL.md +0 -28
- package/skills/verify-evidence/SKILL.md +0 -27
|
@@ -1,148 +1,151 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Validation severity levels for pi-crew config.
|
|
3
|
-
*
|
|
4
|
-
* - Required fields invalid → always ERROR
|
|
5
|
-
* - Optional fields invalid → ERROR (strict) / WARNING (lenient)
|
|
6
|
-
* - Missing recommended optional fields → INFO
|
|
7
|
-
* - Values outside recommended ranges → WARNING
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { Value } from "typebox/value";
|
|
11
|
-
import { PiTeamsConfigSchema } from "./config-schema.ts";
|
|
12
|
-
|
|
13
|
-
// ---------------------------------------------------------------------------
|
|
14
|
-
// Types
|
|
15
|
-
// ---------------------------------------------------------------------------
|
|
16
|
-
|
|
17
|
-
export type ValidationSeverity = "ERROR" | "WARNING" | "INFO";
|
|
18
|
-
|
|
19
|
-
export type ValidationMode = "strict" | "lenient";
|
|
20
|
-
|
|
21
|
-
export interface ValidationFinding {
|
|
22
|
-
severity: ValidationSeverity;
|
|
23
|
-
message: string;
|
|
24
|
-
field?: string;
|
|
25
|
-
suggestion?: string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface ValidationOutcome {
|
|
29
|
-
findings: ValidationFinding[];
|
|
30
|
-
mode: ValidationMode;
|
|
31
|
-
hasErrors: boolean;
|
|
32
|
-
hasWarnings: boolean;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// ---------------------------------------------------------------------------
|
|
36
|
-
// Constants
|
|
37
|
-
// ---------------------------------------------------------------------------
|
|
38
|
-
|
|
39
|
-
const DEFAULT_MODE: ValidationMode = "strict";
|
|
40
|
-
|
|
41
|
-
/** Recommended range constraints (not enforced by schema, but advisory). */
|
|
42
|
-
const RECOMMENDED_RANGES: Record<string, { max: number; label: string }> = {
|
|
43
|
-
"limits.maxConcurrentWorkers": { max: 8, label: "maxConcurrentWorkers > 8 may degrade performance" },
|
|
44
|
-
"limits.maxTaskDepth": { max: 4, label: "maxTaskDepth > 4 may cause excessive nesting" },
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
/** Recommended optional top-level keys that should be explicitly set. */
|
|
48
|
-
const RECOMMENDED_OPTIONAL_KEYS: readonly string[] = [
|
|
49
|
-
"limits",
|
|
50
|
-
"runtime",
|
|
51
|
-
];
|
|
52
|
-
|
|
53
|
-
// ---------------------------------------------------------------------------
|
|
54
|
-
// Helpers
|
|
55
|
-
// ---------------------------------------------------------------------------
|
|
56
|
-
|
|
57
|
-
function isObject(value: unknown): value is Record<string, unknown> {
|
|
58
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function getNestedValue(obj: Record<string, unknown>, dottedPath: string): unknown {
|
|
62
|
-
const parts = dottedPath.split(".");
|
|
63
|
-
let current: unknown = obj;
|
|
64
|
-
for (const part of parts) {
|
|
65
|
-
if (!isObject(current)) return undefined;
|
|
66
|
-
current = current[part];
|
|
67
|
-
}
|
|
68
|
-
return current;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// ---------------------------------------------------------------------------
|
|
72
|
-
// Implementation
|
|
73
|
-
// ---------------------------------------------------------------------------
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Validate a raw config value with severity-tagged findings.
|
|
77
|
-
*
|
|
78
|
-
* @param raw The raw config value to validate.
|
|
79
|
-
* @param mode Validation mode — 'strict' (default) or 'lenient'.
|
|
80
|
-
* @returns A `ValidationOutcome` with findings grouped by severity.
|
|
81
|
-
*/
|
|
82
|
-
export function validateWithSeverity(raw: unknown, mode: ValidationMode = DEFAULT_MODE): ValidationOutcome {
|
|
83
|
-
const findings: ValidationFinding[] = [];
|
|
84
|
-
|
|
85
|
-
if (!isObject(raw)) {
|
|
86
|
-
findings.push({ severity: "ERROR", message: "config must be an object", field: "config" });
|
|
87
|
-
return { findings, mode, hasErrors: true, hasWarnings: false };
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// --- Schema-based validation ---
|
|
91
|
-
const isValid = Value.Check(PiTeamsConfigSchema, raw);
|
|
92
|
-
if (!isValid) {
|
|
93
|
-
for (const error of Value.Errors(PiTeamsConfigSchema, raw)) {
|
|
94
|
-
const errRecord = error as unknown as Record<string, unknown>;
|
|
95
|
-
const rawPath = typeof errRecord.path === "string"
|
|
96
|
-
? errRecord.path
|
|
97
|
-
: typeof errRecord.instancePath === "string"
|
|
98
|
-
? errRecord.instancePath
|
|
99
|
-
: "config";
|
|
100
|
-
|
|
101
|
-
const message = typeof errRecord.message === "string"
|
|
102
|
-
? errRecord.message
|
|
103
|
-
: "invalid value";
|
|
104
|
-
|
|
105
|
-
const field = rawPath.replace(/^\//, "").replace(/\//g, ".") || undefined;
|
|
106
|
-
|
|
107
|
-
// Additional properties are less severe in lenient mode
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Validation severity levels for pi-crew config.
|
|
3
|
+
*
|
|
4
|
+
* - Required fields invalid → always ERROR
|
|
5
|
+
* - Optional fields invalid → ERROR (strict) / WARNING (lenient)
|
|
6
|
+
* - Missing recommended optional fields → INFO
|
|
7
|
+
* - Values outside recommended ranges → WARNING
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Value } from "@sinclair/typebox/value";
|
|
11
|
+
import { PiTeamsConfigSchema } from "./config-schema.ts";
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Types
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
export type ValidationSeverity = "ERROR" | "WARNING" | "INFO";
|
|
18
|
+
|
|
19
|
+
export type ValidationMode = "strict" | "lenient";
|
|
20
|
+
|
|
21
|
+
export interface ValidationFinding {
|
|
22
|
+
severity: ValidationSeverity;
|
|
23
|
+
message: string;
|
|
24
|
+
field?: string;
|
|
25
|
+
suggestion?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ValidationOutcome {
|
|
29
|
+
findings: ValidationFinding[];
|
|
30
|
+
mode: ValidationMode;
|
|
31
|
+
hasErrors: boolean;
|
|
32
|
+
hasWarnings: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Constants
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
const DEFAULT_MODE: ValidationMode = "strict";
|
|
40
|
+
|
|
41
|
+
/** Recommended range constraints (not enforced by schema, but advisory). */
|
|
42
|
+
const RECOMMENDED_RANGES: Record<string, { max: number; label: string }> = {
|
|
43
|
+
"limits.maxConcurrentWorkers": { max: 8, label: "maxConcurrentWorkers > 8 may degrade performance" },
|
|
44
|
+
"limits.maxTaskDepth": { max: 4, label: "maxTaskDepth > 4 may cause excessive nesting" },
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/** Recommended optional top-level keys that should be explicitly set. */
|
|
48
|
+
const RECOMMENDED_OPTIONAL_KEYS: readonly string[] = [
|
|
49
|
+
"limits",
|
|
50
|
+
"runtime",
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// Helpers
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
function isObject(value: unknown): value is Record<string, unknown> {
|
|
58
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getNestedValue(obj: Record<string, unknown>, dottedPath: string): unknown {
|
|
62
|
+
const parts = dottedPath.split(".");
|
|
63
|
+
let current: unknown = obj;
|
|
64
|
+
for (const part of parts) {
|
|
65
|
+
if (!isObject(current)) return undefined;
|
|
66
|
+
current = current[part];
|
|
67
|
+
}
|
|
68
|
+
return current;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Implementation
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Validate a raw config value with severity-tagged findings.
|
|
77
|
+
*
|
|
78
|
+
* @param raw The raw config value to validate.
|
|
79
|
+
* @param mode Validation mode — 'strict' (default) or 'lenient'.
|
|
80
|
+
* @returns A `ValidationOutcome` with findings grouped by severity.
|
|
81
|
+
*/
|
|
82
|
+
export function validateWithSeverity(raw: unknown, mode: ValidationMode = DEFAULT_MODE): ValidationOutcome {
|
|
83
|
+
const findings: ValidationFinding[] = [];
|
|
84
|
+
|
|
85
|
+
if (!isObject(raw)) {
|
|
86
|
+
findings.push({ severity: "ERROR", message: "config must be an object", field: "config" });
|
|
87
|
+
return { findings, mode, hasErrors: true, hasWarnings: false };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// --- Schema-based validation ---
|
|
91
|
+
const isValid = Value.Check(PiTeamsConfigSchema, raw);
|
|
92
|
+
if (!isValid) {
|
|
93
|
+
for (const error of Value.Errors(PiTeamsConfigSchema, raw)) {
|
|
94
|
+
const errRecord = error as unknown as Record<string, unknown>;
|
|
95
|
+
const rawPath = typeof errRecord.path === "string"
|
|
96
|
+
? errRecord.path
|
|
97
|
+
: typeof errRecord.instancePath === "string"
|
|
98
|
+
? errRecord.instancePath
|
|
99
|
+
: "config";
|
|
100
|
+
|
|
101
|
+
const message = typeof errRecord.message === "string"
|
|
102
|
+
? errRecord.message
|
|
103
|
+
: "invalid value";
|
|
104
|
+
|
|
105
|
+
const field = rawPath.replace(/^\//, "").replace(/\//g, ".") || undefined;
|
|
106
|
+
|
|
107
|
+
// Additional properties / unknown keys are less severe in lenient mode.
|
|
108
|
+
// @sinclair/typebox uses keyword=undefined with message="Unexpected property" for these.
|
|
109
|
+
const isUnknownProperty = errRecord.keyword === "additionalProperties" ||
|
|
110
|
+
message === "Unexpected property" || message.startsWith("Unexpected property");
|
|
111
|
+
if (isUnknownProperty) {
|
|
112
|
+
findings.push({
|
|
113
|
+
severity: mode === "lenient" ? "WARNING" : "ERROR",
|
|
114
|
+
message: `unknown property${field ? ` '${field}'` : ""}: ${message}`,
|
|
115
|
+
field,
|
|
116
|
+
});
|
|
117
|
+
} else {
|
|
118
|
+
findings.push({ severity: "ERROR", message, field });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// --- Missing recommended optional keys → INFO ---
|
|
124
|
+
for (const key of RECOMMENDED_OPTIONAL_KEYS) {
|
|
125
|
+
if (!(key in raw)) {
|
|
126
|
+
findings.push({
|
|
127
|
+
severity: "INFO",
|
|
128
|
+
message: `recommended config section '${key}' is not set`,
|
|
129
|
+
field: key,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// --- Values outside recommended ranges → WARNING ---
|
|
135
|
+
for (const [dottedPath, constraint] of Object.entries(RECOMMENDED_RANGES)) {
|
|
136
|
+
const value = getNestedValue(raw, dottedPath);
|
|
137
|
+
if (typeof value === "number" && value > constraint.max) {
|
|
138
|
+
findings.push({
|
|
139
|
+
severity: "WARNING",
|
|
140
|
+
message: constraint.label,
|
|
141
|
+
field: dottedPath,
|
|
142
|
+
suggestion: `consider using a value ≤ ${constraint.max}`,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const hasErrors = findings.some((f) => f.severity === "ERROR");
|
|
148
|
+
const hasWarnings = findings.some((f) => f.severity === "WARNING");
|
|
149
|
+
|
|
150
|
+
return { findings, mode, hasErrors, hasWarnings };
|
|
151
|
+
}
|
|
@@ -1,67 +1,67 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { logInternalError } from "../utils/internal-error.ts";
|
|
5
|
-
import { isSafePathId, resolveContainedPath, resolveRealContainedPath } from "../utils/safe-paths.ts";
|
|
6
|
-
|
|
7
|
-
const PACKAGE_SKILLS_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "skills");
|
|
8
|
-
|
|
9
|
-
export interface SkillDescriptor {
|
|
10
|
-
name: string;
|
|
11
|
-
description: string;
|
|
12
|
-
source: "project" | "package";
|
|
13
|
-
path: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function listSkillDirs(cwd: string): Array<{ root: string; source: "project" | "package" }> {
|
|
17
|
-
return [
|
|
18
|
-
{ root: path.resolve(cwd, "skills"), source: "project" },
|
|
19
|
-
{ root: PACKAGE_SKILLS_DIR, source: "package" },
|
|
20
|
-
];
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function frontmatterDescription(content: string): string | undefined {
|
|
24
|
-
const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content);
|
|
25
|
-
if (!match) return undefined;
|
|
26
|
-
const line = match[1].split(/\r?\n/).find((entry) => entry.startsWith("description:"));
|
|
27
|
-
return line?.slice("description:".length).trim();
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function discoverSkills(cwd: string): SkillDescriptor[] {
|
|
31
|
-
const results: SkillDescriptor[] = [];
|
|
32
|
-
for (const dir of listSkillDirs(cwd)) {
|
|
33
|
-
if (!fs.existsSync(dir.root)) continue;
|
|
34
|
-
try {
|
|
35
|
-
for (const entry of fs.readdirSync(dir.root, { withFileTypes: true })) {
|
|
36
|
-
if (!entry.isDirectory()) continue;
|
|
37
|
-
if (!isSafePathId(entry.name)) continue;
|
|
38
|
-
const skillDirPath = path.join(dir.root, entry.name);
|
|
39
|
-
try {
|
|
40
|
-
if (fs.lstatSync(skillDirPath).isSymbolicLink()) continue;
|
|
41
|
-
} catch { continue; }
|
|
42
|
-
const skillMdRelative = path.join(entry.name, "SKILL.md");
|
|
43
|
-
let skillMdPath: string;
|
|
44
|
-
try {
|
|
45
|
-
skillMdPath = resolveContainedPath(dir.root, skillMdRelative);
|
|
46
|
-
} catch { continue; }
|
|
47
|
-
if (!fs.existsSync(skillMdPath)) continue;
|
|
48
|
-
try {
|
|
49
|
-
if (fs.lstatSync(skillMdPath).isSymbolicLink()) continue;
|
|
50
|
-
} catch { continue; }
|
|
51
|
-
let description = "";
|
|
52
|
-
try {
|
|
53
|
-
const realPath = resolveRealContainedPath(dir.root, skillMdRelative);
|
|
54
|
-
const content = fs.readFileSync(realPath, "utf-8");
|
|
55
|
-
description = frontmatterDescription(content) ?? "";
|
|
56
|
-
skillMdPath = realPath;
|
|
57
|
-
} catch (error) {
|
|
58
|
-
logInternalError("discoverSkills.readSkill", error, `skill=${entry.name}`);
|
|
59
|
-
}
|
|
60
|
-
results.push({ name: entry.name, description, source: dir.source, path: skillMdPath });
|
|
61
|
-
}
|
|
62
|
-
} catch (error) {
|
|
63
|
-
logInternalError("discoverSkills.readdir", error, `root=${dir.root}`);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return results;
|
|
67
|
-
}
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { logInternalError } from "../utils/internal-error.ts";
|
|
5
|
+
import { isSafePathId, resolveContainedPath, resolveRealContainedPath } from "../utils/safe-paths.ts";
|
|
6
|
+
|
|
7
|
+
const PACKAGE_SKILLS_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "skills");
|
|
8
|
+
|
|
9
|
+
export interface SkillDescriptor {
|
|
10
|
+
name: string;
|
|
11
|
+
description: string;
|
|
12
|
+
source: "project" | "package";
|
|
13
|
+
path: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function listSkillDirs(cwd: string): Array<{ root: string; source: "project" | "package" }> {
|
|
17
|
+
return [
|
|
18
|
+
{ root: path.resolve(cwd, "skills"), source: "project" },
|
|
19
|
+
{ root: PACKAGE_SKILLS_DIR, source: "package" },
|
|
20
|
+
];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function frontmatterDescription(content: string): string | undefined {
|
|
24
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content);
|
|
25
|
+
if (!match) return undefined;
|
|
26
|
+
const line = match[1].split(/\r?\n/).find((entry) => entry.startsWith("description:"));
|
|
27
|
+
return line?.slice("description:".length).trim();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function discoverSkills(cwd: string): SkillDescriptor[] {
|
|
31
|
+
const results: SkillDescriptor[] = [];
|
|
32
|
+
for (const dir of listSkillDirs(cwd)) {
|
|
33
|
+
if (!fs.existsSync(dir.root)) continue;
|
|
34
|
+
try {
|
|
35
|
+
for (const entry of fs.readdirSync(dir.root, { withFileTypes: true })) {
|
|
36
|
+
if (!entry.isDirectory()) continue;
|
|
37
|
+
if (!isSafePathId(entry.name)) continue;
|
|
38
|
+
const skillDirPath = path.join(dir.root, entry.name);
|
|
39
|
+
try {
|
|
40
|
+
if (fs.lstatSync(skillDirPath).isSymbolicLink()) continue;
|
|
41
|
+
} catch { continue; }
|
|
42
|
+
const skillMdRelative = path.join(entry.name, "SKILL.md");
|
|
43
|
+
let skillMdPath: string;
|
|
44
|
+
try {
|
|
45
|
+
skillMdPath = resolveContainedPath(dir.root, skillMdRelative);
|
|
46
|
+
} catch { continue; }
|
|
47
|
+
if (!fs.existsSync(skillMdPath)) continue;
|
|
48
|
+
try {
|
|
49
|
+
if (fs.lstatSync(skillMdPath).isSymbolicLink()) continue;
|
|
50
|
+
} catch { continue; }
|
|
51
|
+
let description = "";
|
|
52
|
+
try {
|
|
53
|
+
const realPath = resolveRealContainedPath(dir.root, skillMdRelative);
|
|
54
|
+
const content = fs.readFileSync(realPath, "utf-8");
|
|
55
|
+
description = frontmatterDescription(content) ?? "";
|
|
56
|
+
skillMdPath = realPath;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
logInternalError("discoverSkills.readSkill", error, `skill=${entry.name}`);
|
|
59
|
+
}
|
|
60
|
+
results.push({ name: entry.name, description, source: dir.source, path: skillMdPath });
|
|
61
|
+
}
|
|
62
|
+
} catch (error) {
|
|
63
|
+
logInternalError("discoverSkills.readdir", error, `root=${dir.root}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return results;
|
|
67
|
+
}
|