pi-crew 0.9.68 → 0.10.2
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/CHANGELOG.md +222 -0
- package/NOTICE.md +21 -0
- package/README.md +44 -2
- package/agents/analyst.md +1 -1
- package/agents/cold-verifier.md +3 -1
- package/agents/critic.md +1 -1
- package/agents/executor.md +1 -1
- package/agents/explorer.md +1 -1
- package/agents/planner.md +1 -1
- package/agents/reviewer.md +1 -1
- package/agents/security-reviewer.md +1 -1
- package/agents/test-engineer.md +1 -1
- package/agents/verifier.md +1 -1
- package/agents/writer.md +1 -1
- package/dist/index.mjs +68113 -60774
- package/docs/README.md +2 -0
- package/docs/actions-reference.md +31 -0
- package/docs/commands-reference.md +17 -6
- package/docs/resource-formats.md +13 -0
- package/package.json +4 -2
- package/schema.json +503 -91
- package/scripts/resource-sampler.mjs +36 -2
- package/skills/requirements-to-task-packet/SKILL.md +26 -0
- package/skills/widget-rendering/SKILL.md +7 -7
- package/src/agents/agent-config.ts +2 -1
- package/src/agents/discover-agents.ts +23 -14
- package/src/config/config-merge.ts +183 -0
- package/src/config/config-validation.ts +687 -0
- package/src/config/config.ts +22 -864
- package/src/config/defaults.ts +43 -2
- package/src/config/drift-detector.ts +1 -1
- package/src/config/env-vars.ts +691 -0
- package/src/config/role-tools.ts +11 -9
- package/src/config/sanitize-project-config.ts +172 -0
- package/src/config/types.ts +49 -1
- package/src/extension/async-notifier.ts +25 -2
- package/src/extension/crew-cleanup.ts +13 -0
- package/src/extension/crew-vibes/config.ts +2 -1
- package/src/extension/crew-vibes/footer.ts +19 -0
- package/src/extension/crew-vibes/index.ts +11 -1
- package/src/extension/plan-orchestrate.ts +132 -0
- package/src/extension/register.ts +8 -0
- package/src/extension/registration/command-registration.ts +1 -0
- package/src/extension/registration/commands/dashboard.ts +158 -0
- package/src/extension/registration/commands/index.ts +35 -0
- package/src/extension/registration/commands/manage.ts +303 -0
- package/src/extension/registration/commands/run.ts +228 -0
- package/src/extension/registration/commands/shared.ts +639 -0
- package/src/extension/registration/commands/status.ts +60 -0
- package/src/extension/registration/commands.ts +13 -1224
- package/src/extension/registration/foreground-run-controller.ts +10 -2
- package/src/extension/registration/lifecycle-handlers.ts +178 -17
- package/src/extension/registration/runtime-cleanup.ts +23 -5
- package/src/extension/registration/subagent-tools.ts +218 -9
- package/src/extension/registration/team-tool.ts +5 -1
- package/src/extension/registration/ui.ts +5 -4
- package/src/extension/rpc-hmac.ts +5 -3
- package/src/extension/team-tool/api/heartbeat.ts +47 -10
- package/src/extension/team-tool/api/plan-approval.ts +9 -0
- package/src/extension/team-tool/api/task-claims.ts +109 -40
- package/src/extension/team-tool/cancel.ts +84 -50
- package/src/extension/team-tool/dispatch/index.ts +1 -0
- package/src/extension/team-tool/dispatch/run.ts +4 -1
- package/src/extension/team-tool/doctor.ts +103 -1
- package/src/extension/team-tool/orchestrate.ts +66 -1
- package/src/extension/team-tool/plans.ts +192 -0
- package/src/extension/team-tool/respond.ts +197 -65
- package/src/extension/team-tool/run-deadline.ts +35 -3
- package/src/extension/team-tool/run-intent.ts +63 -0
- package/src/extension/team-tool/run.ts +74 -20
- package/src/extension/team-tool/status.ts +84 -26
- package/src/extension/team-tool.ts +11 -2
- package/src/hooks/registry.ts +1 -6
- package/src/i18n.ts +9 -0
- package/src/prompt/prompt-runtime.ts +521 -2
- package/src/prompt/worker-events-channel.ts +173 -0
- package/src/runtime/README.md +8 -8
- package/src/runtime/async-runner.ts +7 -3
- package/src/runtime/background-runner.ts +42 -14
- package/src/runtime/broker/broker-issuer.ts +9 -2
- package/src/runtime/broker/crew-broker-tokens.ts +43 -6
- package/src/runtime/broker/crew-broker.ts +838 -10
- package/src/runtime/broker/wait-status-cache.ts +157 -0
- package/src/runtime/budget-enforcement.ts +281 -0
- package/src/runtime/child-pi/child-pi-constants.ts +8 -0
- package/src/runtime/child-pi/child-pi-spawn.ts +60 -14
- package/src/runtime/child-pi/child-pi-streams.ts +21 -1
- package/src/runtime/child-pi/child-pi-timers.ts +324 -0
- package/src/runtime/child-pi/child-pi.ts +97 -201
- package/src/runtime/child-pi/mock-fixtures.ts +16 -2
- package/src/runtime/crew-agent-records.ts +259 -14
- package/src/runtime/delegate-spawn.ts +148 -0
- package/src/runtime/detached-run-results.ts +90 -0
- package/src/runtime/deterministic-ast.ts +2 -1
- package/src/runtime/dispatch-batch.ts +945 -0
- package/src/runtime/finalize-run.ts +557 -0
- package/src/runtime/goal-workflow/adaptive-plan.ts +116 -15
- package/src/runtime/goal-workflow/dynamic-workflow-runner.ts +8 -3
- package/src/runtime/goal-workflow/goal-state-store.ts +1 -1
- package/src/runtime/group-join.ts +11 -125
- package/src/runtime/live-session/live-session-runtime.ts +26 -1
- package/src/runtime/merge-gate.ts +32 -10
- package/src/runtime/merge-loop.ts +130 -0
- package/src/runtime/model/model-budget-summary.ts +53 -0
- package/src/runtime/model/model-fallback.ts +36 -2
- package/src/runtime/model/pi-args.ts +10 -0
- package/src/runtime/model/provider-extensions.ts +10 -0
- package/src/runtime/orphan-worker-registry.ts +1 -1
- package/src/runtime/output/output-validator.ts +45 -0
- package/src/runtime/parent-guard.ts +3 -1
- package/src/runtime/peer-dep.ts +2 -1
- package/src/runtime/per-write-validator.ts +0 -5
- package/src/runtime/pi-spawn.ts +61 -15
- package/src/runtime/plan-approval.ts +125 -0
- package/src/runtime/plan-replan.ts +151 -0
- package/src/runtime/process-status.ts +16 -1
- package/src/runtime/recovery/checkpoint.ts +0 -18
- package/src/runtime/recovery/crash-recovery.ts +111 -46
- package/src/runtime/run-tracker.ts +77 -10
- package/src/runtime/scheduler-context.ts +98 -0
- package/src/runtime/scheduling/coalesce-tasks.ts +5 -0
- package/src/runtime/scheduling/global-worker-cap.ts +2 -1
- package/src/runtime/scheduling/nested-slots.ts +70 -0
- package/src/runtime/scheduling/run-coalesced-task-group.ts +64 -13
- package/src/runtime/scheduling/task-graph-scheduler.ts +0 -10
- package/src/runtime/settings-store.ts +219 -0
- package/src/runtime/spawn-policy.ts +217 -0
- package/src/runtime/stale-reconciler.ts +87 -6
- package/src/runtime/subagent-manager.ts +25 -1
- package/src/runtime/task-output-context.ts +230 -9
- package/src/runtime/task-packet.ts +23 -1
- package/src/runtime/task-runner/child-executor.ts +106 -7
- package/src/runtime/task-runner/post-execution.ts +125 -1
- package/src/runtime/task-runner/pre-execution.ts +39 -1
- package/src/runtime/task-runner/prompt-builder.ts +51 -1
- package/src/runtime/task-runner/retrieval-orchestrator.ts +72 -18
- package/src/runtime/task-runner/spec-evidence.ts +403 -0
- package/src/runtime/task-runner/state-helpers.ts +26 -24
- package/src/runtime/task-runner.ts +11 -0
- package/src/runtime/team-runner.ts +132 -1673
- package/src/runtime/verification/spec-sandbox.ts +255 -0
- package/src/runtime/verification/verification-gates.ts +3 -2
- package/src/runtime/verification/verification-worktree.ts +2 -1
- package/src/runtime/workflow-phase-advance.ts +100 -0
- package/src/runtime/workspace-tree.ts +9 -0
- package/src/schema/config-schema.ts +66 -26
- package/src/schema/sensitive-config-paths.ts +64 -0
- package/src/schema/team-tool-schema.ts +13 -3
- package/src/state/README.md +4 -10
- package/src/state/atomic-write.ts +20 -3
- package/src/state/contracts.ts +38 -0
- package/src/state/coordination/mailbox.ts +12 -2
- package/src/state/event-log/cursor.ts +223 -0
- package/src/state/event-log/event-log-rotation.ts +12 -4
- package/src/state/event-log/event-log.ts +152 -369
- package/src/state/event-log/sequence-cache.ts +373 -0
- package/src/state/event-log/worker-atomic-writer.ts +2 -1
- package/src/state/stores/active-run-registry.ts +3 -2
- package/src/state/stores/manifest-io.ts +237 -0
- package/src/state/stores/ownership-map.ts +162 -0
- package/src/state/stores/plan-store.ts +241 -0
- package/src/state/stores/run-cache.ts +0 -90
- package/src/state/stores/spec-store.ts +189 -0
- package/src/state/stores/state-store.ts +139 -232
- package/src/state/types.ts +199 -0
- package/src/ui/dashboard-panes/plan-pane.ts +136 -0
- package/src/ui/dashboard-panes/progress-pane.ts +6 -0
- package/src/ui/dashboard-panes/transcript-pane.ts +31 -0
- package/src/ui/dock-footer.ts +49 -0
- package/src/ui/heartbeat-aggregator.ts +9 -1
- package/src/ui/inline-panel/agent-pane.ts +375 -0
- package/src/ui/inline-panel/agent-transcript.ts +338 -0
- package/src/ui/inline-panel/agent-view-overlay.ts +225 -0
- package/src/ui/inline-panel/crew-editor.ts +192 -0
- package/src/ui/inline-panel/index.ts +290 -0
- package/src/ui/inline-panel/panel-rows.ts +37 -0
- package/src/ui/inline-panel/panel-selection.ts +157 -0
- package/src/ui/inline-panel/panel-store.ts +111 -0
- package/src/ui/inline-panel/view-session-store.ts +36 -0
- package/src/ui/keybinding-map.ts +54 -13
- package/src/ui/pi-ui-compat.ts +9 -0
- package/src/ui/powerbar-publisher.ts +52 -1
- package/src/ui/run-dashboard.ts +31 -5
- package/src/ui/run-snapshot-cache.ts +57 -30
- package/src/ui/snapshot-types.ts +6 -1
- package/src/ui/widget/index.ts +176 -22
- package/src/ui/widget/task-list.ts +198 -0
- package/src/ui/widget/widget-formatters.ts +240 -4
- package/src/ui/widget/widget-renderer.ts +243 -38
- package/src/ui/widget/widget-types.ts +11 -0
- package/src/utils/child-process-shield.ts +106 -0
- package/src/utils/file-coalescer.ts +0 -4
- package/src/utils/fs-errno.ts +66 -0
- package/src/utils/fs-watch.ts +1 -1
- package/src/utils/internal-error.ts +3 -1
- package/src/utils/paths.ts +11 -3
- package/src/utils/redaction.ts +7 -0
- package/src/utils/safe-abort.ts +45 -0
- package/src/utils/task-name-generator.ts +1 -8
- package/src/workflows/discover-workflows.ts +20 -2
- package/src/workflows/validate-workflow.ts +7 -1
- package/src/workflows/workflow-config.ts +17 -0
- package/src/workflows/workflow-serializer.ts +3 -0
- package/src/worktree/worktree-manager.ts +22 -0
- package/workflows/default.workflow.md +36 -26
- package/workflows/strict-fast-fix.workflow.md +26 -0
- package/src/agents/agent-search.ts +0 -98
- package/src/benchmark/benchmark-runner.ts +0 -313
- package/src/benchmark/feedback-loop.ts +0 -73
- package/src/config/resilient-parser.ts +0 -117
- package/src/extension/crew-vibes/cat-frames.ts +0 -18
- package/src/extension/result-watcher.ts +0 -139
- package/src/observability/exporters/prometheus-exporter.ts +0 -54
- package/src/observability/metric-retention.ts +0 -64
- package/src/runtime/compaction/compaction-summary.ts +0 -278
- package/src/runtime/errors/crew-errors.ts +0 -162
- package/src/runtime/live-session/intercom-bridge.ts +0 -187
- package/src/runtime/loop-gates.ts +0 -128
- package/src/runtime/metric-parser.ts +0 -36
- package/src/runtime/output/stream-preview.ts +0 -184
- package/src/runtime/output/tool-progress.ts +0 -278
- package/src/runtime/phase-tracker.ts +0 -385
- package/src/runtime/pipeline-runner.ts +0 -523
- package/src/runtime/process/process-lifecycle.ts +0 -491
- package/src/runtime/recovery/retry-runner.ts +0 -330
- package/src/runtime/run-drift.ts +0 -219
- package/src/runtime/task-quality.ts +0 -199
- package/src/runtime/task-runner/run-projection.ts +0 -128
- package/src/runtime/verification/post-checks.ts +0 -142
- package/src/state/coordination/schedule.ts +0 -166
- package/src/state/event-log/jsonl-writer.ts +0 -115
- package/src/state/hook-instinct-bridge.ts +0 -94
- package/src/state/hook-integrations.ts +0 -51
- package/src/state/session-state-map.ts +0 -51
- package/src/state/stores/blob-store.ts +0 -308
- package/src/state/stores/instinct-store.ts +0 -275
- package/src/state/stores/observation-store.ts +0 -176
- package/src/state/tiered-eval.ts +0 -480
- package/src/state/types-eval.ts +0 -58
- package/src/tools/safe-bash-extension.ts +0 -54
- package/src/tools/safe-bash.ts +0 -505
- package/src/ui/agent-management-overlay.ts +0 -160
- package/src/ui/crew-footer.ts +0 -102
- package/src/ui/crew-select-list.ts +0 -114
- package/src/ui/dashboard-panes/capability-pane.ts +0 -77
- package/src/ui/transcript-entries.ts +0 -256
- package/src/utils/conflict-detect.ts +0 -721
- package/src/utils/fingerprint.ts +0 -180
- package/src/utils/gh-protocol.ts +0 -556
- package/src/utils/project-detector.ts +0 -160
- package/src/utils/sse-parser.ts +0 -131
- package/src/workflows/cost-estimator.ts +0 -34
- package/src/workflows/intermediate-store.ts +0 -166
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* safe-abort.ts — abort helpers that cannot crash the host process.
|
|
3
|
+
*
|
|
4
|
+
* When a child process was spawned with `options.signal` and the signal fires
|
|
5
|
+
* AFTER that child already exited (fast workers, cleanup-after-completion,
|
|
6
|
+
* session switches mid-run), Node's child_process abort listener throws
|
|
7
|
+
* `AbortError: The operation was aborted` SYNCHRONOUSLY from
|
|
8
|
+
* `AbortController.abort()` (node:child_process abortChildProcess).
|
|
9
|
+
*
|
|
10
|
+
* In pi extension event handlers that throw propagates straight into pi's
|
|
11
|
+
* `uncaughtException` handling and kills the whole terminal session — the
|
|
12
|
+
* crash seen here: session_before_switch → stopSessionBoundSubagents → abort
|
|
13
|
+
* → AbortError → "pi exiting due to uncaughtException".
|
|
14
|
+
*
|
|
15
|
+
* Abort-after-exit is a semantic no-op (the work is already gone), so every
|
|
16
|
+
* controller abort that can be linked to a spawned child MUST go through
|
|
17
|
+
* these helpers: swallow the throw, log it, carry on.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { logInternalError } from "./internal-error.ts";
|
|
21
|
+
|
|
22
|
+
/** Minimal controller shape — anything with an `abort()` method. */
|
|
23
|
+
export interface Abortable {
|
|
24
|
+
abort(): void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Abort a controller, tolerating Node's abort-after-child-exit AbortError.
|
|
29
|
+
* Never throws. `scope` names the call site for the internal error log.
|
|
30
|
+
*/
|
|
31
|
+
export function safeAbort(controller: Abortable | undefined, scope: string): void {
|
|
32
|
+
if (!controller) return;
|
|
33
|
+
try {
|
|
34
|
+
controller.abort();
|
|
35
|
+
} catch (error) {
|
|
36
|
+
logInternalError(`safe-abort.${scope}`, error);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Abort every controller in an iterable; a throwing controller does not
|
|
41
|
+
* prevent the remaining ones from being aborted. */
|
|
42
|
+
export function safeAbortAll(controllers: Iterable<Abortable> | undefined, scope: string): void {
|
|
43
|
+
if (!controllers) return;
|
|
44
|
+
for (const controller of controllers) safeAbort(controller, scope);
|
|
45
|
+
}
|
|
@@ -295,7 +295,7 @@ function capitalize(s: string): string {
|
|
|
295
295
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
-
|
|
298
|
+
const usedNames = new Set<string>();
|
|
299
299
|
|
|
300
300
|
/**
|
|
301
301
|
* Generate a unique two-word identifier (e.g., "SwiftFalcon").
|
|
@@ -328,10 +328,3 @@ export function generateTaskName(): string {
|
|
|
328
328
|
usedNames.clear();
|
|
329
329
|
return generateTaskName();
|
|
330
330
|
}
|
|
331
|
-
|
|
332
|
-
/**
|
|
333
|
-
* Reset name generator state (for testing).
|
|
334
|
-
*/
|
|
335
|
-
export function resetTaskNames(): void {
|
|
336
|
-
usedNames = new Set<string>();
|
|
337
|
-
}
|
|
@@ -2,6 +2,7 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import type { ResourceSource } from "../agents/agent-config.ts";
|
|
4
4
|
import { parseCsv, parseFrontmatter } from "../utils/frontmatter.ts";
|
|
5
|
+
import { logInternalError } from "../utils/internal-error.ts";
|
|
5
6
|
import { packageRoot, projectCrewRoot, userPiRoot } from "../utils/paths.ts";
|
|
6
7
|
import type { WorkflowConfig, WorkflowStep } from "./workflow-config.ts";
|
|
7
8
|
|
|
@@ -24,6 +25,8 @@ const STEP_CONFIG_KEYS = new Set([
|
|
|
24
25
|
"verify",
|
|
25
26
|
"task",
|
|
26
27
|
"seedPaths",
|
|
28
|
+
"specRefs",
|
|
29
|
+
"specStrict",
|
|
27
30
|
"preStepScript",
|
|
28
31
|
"preStepArgs",
|
|
29
32
|
"preStepTimeout",
|
|
@@ -68,6 +71,11 @@ function parseStepSection(id: string, body: string): WorkflowStep | undefined {
|
|
|
68
71
|
worktree: config.worktree === "true" ? true : config.worktree === "false" ? false : undefined,
|
|
69
72
|
verify: config.verify === "true" ? true : config.verify === "false" ? false : undefined,
|
|
70
73
|
seedPaths: parseCsv(config.seedPaths) || undefined,
|
|
74
|
+
specRefs: parseCsv(config.specRefs) || undefined,
|
|
75
|
+
// Tri-state (round-1 P1-2): ABSENT = undefined so the workflow-level flag
|
|
76
|
+
// survives the `step.specStrict ?? workflow.specStrict` dispatch merge; an
|
|
77
|
+
// explicit `false` stays false (per-step opt-out of a strict workflow).
|
|
78
|
+
specStrict: config.specStrict === undefined ? undefined : config.specStrict === "true" || config.specStrict === "1",
|
|
71
79
|
preStepScript: config.preStepScript || undefined,
|
|
72
80
|
preStepArgs: parseCsv(config.preStepArgs) || undefined,
|
|
73
81
|
preStepTimeout: parseOptionalInteger(config.preStepTimeout) ?? undefined,
|
|
@@ -155,8 +163,16 @@ function parseWorkflowFile(filePath: string, source: ResourceSource): WorkflowCo
|
|
|
155
163
|
// runtime, but stripping here prevents the script value from even reaching
|
|
156
164
|
// downstream consumers (coalesce-tasks, policy-engine, etc.).
|
|
157
165
|
if (source === "project" && step.preStepScript) {
|
|
158
|
-
|
|
159
|
-
|
|
166
|
+
// F-02 redaction: NEVER log the preStepScript body — project workflows
|
|
167
|
+
// are untrusted input and the script may contain secrets. Log name,
|
|
168
|
+
// step id, and script length only.
|
|
169
|
+
logInternalError(
|
|
170
|
+
"discover-workflows",
|
|
171
|
+
new Error(
|
|
172
|
+
`Stripping preStepScript from project workflow '${name}' step '${step.id}' — script body redacted (${step.preStepScript.length} chars) (F-02: project-sourced pre-step scripts are not allowed for RCE prevention)`,
|
|
173
|
+
),
|
|
174
|
+
undefined,
|
|
175
|
+
"warn",
|
|
160
176
|
);
|
|
161
177
|
step.preStepScript = undefined;
|
|
162
178
|
step.preStepArgs = undefined;
|
|
@@ -174,6 +190,8 @@ function parseWorkflowFile(filePath: string, source: ResourceSource): WorkflowCo
|
|
|
174
190
|
maxConcurrency: parseOptionalInteger(frontmatter.maxConcurrency),
|
|
175
191
|
topology: parseTopology(frontmatter.topology),
|
|
176
192
|
coalesceMicroTasks: parseOptionalBoolean(frontmatter.coalesceMicroTasks),
|
|
193
|
+
specStrict: parseOptionalBoolean(frontmatter.specStrict),
|
|
194
|
+
adaptive: parseOptionalBoolean(frontmatter.adaptive),
|
|
177
195
|
steps,
|
|
178
196
|
};
|
|
179
197
|
} catch {
|
|
@@ -9,7 +9,13 @@ export function validateWorkflowForTeam(workflow: WorkflowConfig, team: TeamConf
|
|
|
9
9
|
for (const step of workflow.steps) {
|
|
10
10
|
if (stepIds.has(step.id)) errors.push(`Duplicate workflow step id '${step.id}'.`);
|
|
11
11
|
stepIds.add(step.id);
|
|
12
|
-
if (!roles.has(step.role))
|
|
12
|
+
if (!roles.has(step.role)) {
|
|
13
|
+
// F19-9: the distill adapter needs roles the default team lacks
|
|
14
|
+
// (analyst/critic) — hint the correct invocation instead of a bare
|
|
15
|
+
// unknown-role error.
|
|
16
|
+
const distillHint = workflow.name === "distill" ? " (run with team='implementation' — needs analyst+critic roles)" : "";
|
|
17
|
+
errors.push(`Step '${step.id}' references unknown team role '${step.role}'.${distillHint}`);
|
|
18
|
+
}
|
|
13
19
|
}
|
|
14
20
|
|
|
15
21
|
for (const step of workflow.steps) {
|
|
@@ -22,6 +22,12 @@ export interface WorkflowStep {
|
|
|
22
22
|
/** Per-step files to overlay into the worktree (in addition to global worktree.seedPaths).
|
|
23
23
|
* Useful when only certain steps need access to local drafts or scripts. */
|
|
24
24
|
seedPaths?: string[];
|
|
25
|
+
/** T4/R6 (ADR-6): workspace SpecRecord ids this step is held to; frozen
|
|
26
|
+
* into the task packet at dispatch (specStrict per workflow frontmatter). */
|
|
27
|
+
specRefs?: string[];
|
|
28
|
+
/** T4/R6 (ADR-6 §7): per-step strict-mode opt-in. Normally inherited from
|
|
29
|
+
* the workflow-level frontmatter flag (merged at dispatch). */
|
|
30
|
+
specStrict?: boolean;
|
|
25
31
|
/** Path to a deterministic script to run before dispatching the LLM worker.
|
|
26
32
|
* Script stdout is injected into the worker's prompt as context.
|
|
27
33
|
* Pattern origin: Understand-Anything deterministic pre-step pattern. */
|
|
@@ -65,6 +71,17 @@ export interface WorkflowConfig {
|
|
|
65
71
|
* in v0.9.17 unless the workflow author opts in. See
|
|
66
72
|
* src/runtime/scheduling/coalesce-tasks.ts. */
|
|
67
73
|
coalesceMicroTasks?: boolean;
|
|
74
|
+
/** T4/R6 (ADR-6 §7): strict spec mode opt-in (frontmatter `specStrict: true`).
|
|
75
|
+
* Requires a verifier-role step (reject-start, §5) and fails the write-gate
|
|
76
|
+
* on coverage gaps or machine-check failures (§4). */
|
|
77
|
+
specStrict?: boolean;
|
|
78
|
+
/** Frontmatter `adaptive: true` — the workflow's `assess` step (planner role)
|
|
79
|
+
* emits an ADAPTIVE_PLAN_JSON block that the runtime parses and injects as
|
|
80
|
+
* concrete tasks, so the plan painted in the task list is derived from the
|
|
81
|
+
* goal, not from fixed phase templates. The `implementation` workflow is
|
|
82
|
+
* adaptive by name (backward compat); this flag extends the mechanism to
|
|
83
|
+
* any workflow. See src/runtime/goal-workflow/adaptive-plan.ts. */
|
|
84
|
+
adaptive?: boolean;
|
|
68
85
|
}
|
|
69
86
|
|
|
70
87
|
/** A dynamic workflow (runtime === "dynamic"). steps is empty — the script is the source of truth. */
|
|
@@ -14,6 +14,8 @@ function serializeStep(step: WorkflowStep): string[] {
|
|
|
14
14
|
if (step.progress !== undefined) lines.push(`progress: ${step.progress ? "true" : "false"}`);
|
|
15
15
|
if (step.worktree !== undefined) lines.push(`worktree: ${step.worktree ? "true" : "false"}`);
|
|
16
16
|
if (step.verify !== undefined) lines.push(`verify: ${step.verify ? "true" : "false"}`);
|
|
17
|
+
if (step.specRefs?.length) lines.push(`specRefs: ${step.specRefs.join(", ")}`);
|
|
18
|
+
if (step.specStrict !== undefined && step.specStrict !== false) lines.push("specStrict: true");
|
|
17
19
|
lines.push("", step.task.trim(), "");
|
|
18
20
|
return lines;
|
|
19
21
|
}
|
|
@@ -24,6 +26,7 @@ export function serializeWorkflow(workflow: WorkflowConfig): string {
|
|
|
24
26
|
`name: ${workflow.name}`,
|
|
25
27
|
`description: ${workflow.description}`,
|
|
26
28
|
...(workflow.maxConcurrency !== undefined ? [`maxConcurrency: ${workflow.maxConcurrency}`] : []),
|
|
29
|
+
...(workflow.specStrict !== undefined ? [`specStrict: ${workflow.specStrict ? "true" : "false"}`] : []),
|
|
27
30
|
"---",
|
|
28
31
|
"",
|
|
29
32
|
...workflow.steps.flatMap(serializeStep),
|
|
@@ -146,6 +146,12 @@ export function assertCleanLeader(repoRoot: string): void {
|
|
|
146
146
|
|
|
147
147
|
// --- Async versions ---
|
|
148
148
|
|
|
149
|
+
// R5-L1 (Round 5 LOW-1): the exported clear*() fns below stay unwired by
|
|
150
|
+
// leader decision (rewiring run-start/lock-release paths is riskier than the
|
|
151
|
+
// leak); the FIFO caps bound these caches instead.
|
|
152
|
+
const MAX_GIT_ROOT_CACHE = 256;
|
|
153
|
+
const MAX_CLEAN_LEADER_CACHE = 256;
|
|
154
|
+
|
|
149
155
|
/** Cache for findGitRoot results keyed by cwd. Cleared per-run. */
|
|
150
156
|
const _gitRootCache = new Map<string, string>();
|
|
151
157
|
|
|
@@ -159,6 +165,10 @@ export async function findGitRootAsync(cwd: string): Promise<string> {
|
|
|
159
165
|
if (cached) return cached;
|
|
160
166
|
const root = await gitAsync(cwd, ["rev-parse", "--show-toplevel"]);
|
|
161
167
|
_gitRootCache.set(cwd, root);
|
|
168
|
+
if (_gitRootCache.size > MAX_GIT_ROOT_CACHE) {
|
|
169
|
+
const oldest = _gitRootCache.keys().next().value;
|
|
170
|
+
if (oldest !== undefined) _gitRootCache.delete(oldest);
|
|
171
|
+
}
|
|
162
172
|
return root;
|
|
163
173
|
}
|
|
164
174
|
|
|
@@ -179,6 +189,10 @@ export async function assertCleanLeaderAsync(repoRoot: string): Promise<void> {
|
|
|
179
189
|
throw new Error("Worktree mode requires a clean leader repository. Commit/stash changes or use workspaceMode: 'single'.");
|
|
180
190
|
}
|
|
181
191
|
_cleanLeaderCache.add(repoRoot);
|
|
192
|
+
if (_cleanLeaderCache.size > MAX_CLEAN_LEADER_CACHE) {
|
|
193
|
+
const oldest = _cleanLeaderCache.values().next().value;
|
|
194
|
+
if (oldest !== undefined) _cleanLeaderCache.delete(oldest);
|
|
195
|
+
}
|
|
182
196
|
}
|
|
183
197
|
|
|
184
198
|
function linkNodeModulesIfPresent(repoRoot: string, worktreePath: string): boolean {
|
|
@@ -469,6 +483,10 @@ async function branchExistsAsync(repoRoot: string, branch: string): Promise<{ lo
|
|
|
469
483
|
// run no new stale worktrees appear, so one prune suffices).
|
|
470
484
|
const _prunedRepos = new Set<string>();
|
|
471
485
|
const _pruneInFlight = new Map<string, Promise<void>>();
|
|
486
|
+
// R5-L2 (Round 5 LOW-2): FIFO cap — dedupe-only Set, never cleared in
|
|
487
|
+
// production; eviction just means a re-prune for that repo later, which is
|
|
488
|
+
// harmless (prune is idempotent and coalesced).
|
|
489
|
+
const MAX_PRUNED_REPOS = 128;
|
|
472
490
|
async function pruneStaleWorktreesAsync(repoRoot: string): Promise<void> {
|
|
473
491
|
if (_prunedRepos.has(repoRoot)) return;
|
|
474
492
|
const inFlight = _pruneInFlight.get(repoRoot);
|
|
@@ -483,6 +501,10 @@ async function pruneStaleWorktreesAsync(repoRoot: string): Promise<void> {
|
|
|
483
501
|
/* best-effort */
|
|
484
502
|
}
|
|
485
503
|
_prunedRepos.add(repoRoot);
|
|
504
|
+
if (_prunedRepos.size > MAX_PRUNED_REPOS) {
|
|
505
|
+
const oldest = _prunedRepos.values().next().value;
|
|
506
|
+
if (oldest !== undefined) _prunedRepos.delete(oldest);
|
|
507
|
+
}
|
|
486
508
|
_pruneInFlight.delete(repoRoot);
|
|
487
509
|
})();
|
|
488
510
|
_pruneInFlight.set(repoRoot, p);
|
|
@@ -1,31 +1,41 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: default
|
|
3
|
-
description:
|
|
4
|
-
topology:
|
|
3
|
+
description: Plan the goal into concrete tasks, execute them in parallel phases, verify
|
|
4
|
+
topology: complex-dag
|
|
5
|
+
adaptive: true
|
|
5
6
|
---
|
|
6
7
|
|
|
7
|
-
##
|
|
8
|
-
role: explorer
|
|
9
|
-
|
|
10
|
-
Explore the codebase for the goal: {goal}
|
|
11
|
-
|
|
12
|
-
## plan
|
|
8
|
+
## assess
|
|
13
9
|
role: planner
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
10
|
+
output: adaptive-plan.json
|
|
11
|
+
|
|
12
|
+
Plan the concrete task list for: {goal}
|
|
13
|
+
|
|
14
|
+
You are the orchestration planner. Inspect the repository enough to break the goal into specific, executable tasks; do not use a fixed template. Small tasks may need one executor plus one verifier; broader tasks may need parallel explorers, executors, and a verification phase.
|
|
15
|
+
|
|
16
|
+
Return a concise rationale, then include exactly one JSON block between these markers:
|
|
17
|
+
|
|
18
|
+
ADAPTIVE_PLAN_JSON_START
|
|
19
|
+
{
|
|
20
|
+
"phases": [
|
|
21
|
+
{
|
|
22
|
+
"name": "short-phase-name",
|
|
23
|
+
"tasks": [
|
|
24
|
+
{
|
|
25
|
+
"role": "explorer|executor|verifier",
|
|
26
|
+
"title": "short task title",
|
|
27
|
+
"task": "specific autonomous task prompt for this subagent"
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
ADAPTIVE_PLAN_JSON_END
|
|
34
|
+
|
|
35
|
+
Rules:
|
|
36
|
+
- **MAXIMIZE PARALLELISM**: Put independent tasks in the SAME phase so they run concurrently. Never create sequential phases when tasks are independent.
|
|
37
|
+
- Task titles name the concrete piece of work — they become the task list the user watches.
|
|
38
|
+
- Choose the smallest effective number of tasks per phase; a simple goal may have 1-2 phases with 1-2 tasks.
|
|
39
|
+
- End implementation plans with a verification phase (`verifier` role): run FAST targeted checks only (e.g. the affected test files), never the full suite.
|
|
40
|
+
- Tasks within the same phase run in parallel; phases run sequentially.
|
|
41
|
+
- Do not include more than 12 tasks per phase; split or summarize oversized plans instead.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: strict-fast-fix
|
|
3
|
+
description: T4 follow-up — fast-fix variant with strict spec binding (ADR-6 §5). Use this to exercise provenance v2 / freeze-at-dispatch / strict-mode gate end-to-end. Imports the spec via `scripts/spec-import.mjs` first.
|
|
4
|
+
topology: sequential
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## explore
|
|
8
|
+
role: explorer
|
|
9
|
+
specRefs: b5-t4-strict-probe
|
|
10
|
+
specStrict: true
|
|
11
|
+
|
|
12
|
+
Find the likely source of the issue: {goal}. You are held to the SPEC-EVIDENCE contract for the referenced spec — emit a `=== SPEC-EVIDENCE ===` block with `cited: <id>:<id>` lines for each requirement the work touches, then `=== END ===`.
|
|
13
|
+
|
|
14
|
+
## execute
|
|
15
|
+
role: executor
|
|
16
|
+
dependsOn: explore
|
|
17
|
+
|
|
18
|
+
Make the smallest safe fix. Continue the SPEC-EVIDENCE contract from explore: cite every requirement the implementation satisfies.
|
|
19
|
+
|
|
20
|
+
## verify
|
|
21
|
+
role: verifier
|
|
22
|
+
dependsOn: execute
|
|
23
|
+
verify: true
|
|
24
|
+
|
|
25
|
+
Verify the fix with available evidence, AND cross-reference the SPEC-EVIDENCE footer from explore+execute against the spec's requirements (machine-check the citations vs the spec id set).
|
|
26
|
+
Run FAST checks ONCE (cache output to .crew/cache/): `npm run test:critical && npx tsc --noEmit` (completes in <60s). Do NOT run `npm run test:unit` or `npm test` — too slow (642 files, >4 min). Cross-reference cached output with the fix. Do NOT re-run tests. Give PASS or FAIL with specific test evidence + spec gate verdict.
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import type { AgentConfig } from "./agent-config.ts";
|
|
2
|
-
|
|
3
|
-
// ─── BM25 Agent Search ──────────────────────────────────────────────────────
|
|
4
|
-
// Lightweight BM25 search over agent descriptors for task-to-agent matching.
|
|
5
|
-
// Based on the same BM25 algorithm used in oh-my-pi's tool-index.ts.
|
|
6
|
-
|
|
7
|
-
export interface AgentSearchDocument {
|
|
8
|
-
agent: AgentConfig;
|
|
9
|
-
termFrequencies: Map<string, number>;
|
|
10
|
-
length: number;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface AgentSearchIndex {
|
|
14
|
-
documents: AgentSearchDocument[];
|
|
15
|
-
averageLength: number;
|
|
16
|
-
documentFrequencies: Map<string, number>;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface AgentSearchResult {
|
|
20
|
-
agent: AgentConfig;
|
|
21
|
-
score: number;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const BM25_K1 = 1.2;
|
|
25
|
-
const BM25_B = 0.75;
|
|
26
|
-
const FIELD_WEIGHTS = {
|
|
27
|
-
name: 6,
|
|
28
|
-
description: 2,
|
|
29
|
-
role: 3,
|
|
30
|
-
} as const;
|
|
31
|
-
|
|
32
|
-
function tokenize(value: string): string[] {
|
|
33
|
-
return value
|
|
34
|
-
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
35
|
-
.replace(/[^a-zA-Z0-9]+/g, " ")
|
|
36
|
-
.toLowerCase()
|
|
37
|
-
.trim()
|
|
38
|
-
.split(/\s+/)
|
|
39
|
-
.filter((token) => token.length > 0);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function addWeightedTokens(termFrequencies: Map<string, number>, value: string | undefined, weight: number): void {
|
|
43
|
-
if (!value) return;
|
|
44
|
-
for (const token of tokenize(value)) {
|
|
45
|
-
termFrequencies.set(token, (termFrequencies.get(token) ?? 0) + weight);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function buildAgentSearchDocument(agent: AgentConfig): AgentSearchDocument {
|
|
50
|
-
const termFrequencies = new Map<string, number>();
|
|
51
|
-
addWeightedTokens(termFrequencies, agent.name, FIELD_WEIGHTS.name);
|
|
52
|
-
addWeightedTokens(termFrequencies, agent.description, FIELD_WEIGHTS.description);
|
|
53
|
-
// Role from agent name heuristic
|
|
54
|
-
const roleHint = agent.name?.replace(/[-_]/g, " ") ?? "";
|
|
55
|
-
addWeightedTokens(termFrequencies, roleHint, FIELD_WEIGHTS.role);
|
|
56
|
-
const length = Array.from(termFrequencies.values()).reduce((sum, value) => sum + value, 0);
|
|
57
|
-
return { agent, termFrequencies, length };
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function buildAgentSearchIndex(agents: Iterable<AgentConfig>): AgentSearchIndex {
|
|
61
|
-
const documents = Array.from(agents, buildAgentSearchDocument);
|
|
62
|
-
const averageLength = documents.reduce((sum, document) => sum + document.length, 0) / documents.length || 1;
|
|
63
|
-
const documentFrequencies = new Map<string, number>();
|
|
64
|
-
for (const document of documents) {
|
|
65
|
-
for (const token of new Set(document.termFrequencies.keys())) {
|
|
66
|
-
documentFrequencies.set(token, (documentFrequencies.get(token) ?? 0) + 1);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
return { documents, averageLength, documentFrequencies };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function searchAgents(index: AgentSearchIndex, query: string, limit: number): AgentSearchResult[] {
|
|
73
|
-
const queryTokens = tokenize(query);
|
|
74
|
-
if (queryTokens.length === 0) return [];
|
|
75
|
-
if (index.documents.length === 0) return [];
|
|
76
|
-
|
|
77
|
-
const queryTermCounts = new Map<string, number>();
|
|
78
|
-
for (const token of queryTokens) {
|
|
79
|
-
queryTermCounts.set(token, (queryTermCounts.get(token) ?? 0) + 1);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return index.documents
|
|
83
|
-
.map((document) => {
|
|
84
|
-
let score = 0;
|
|
85
|
-
for (const [token, queryTermCount] of queryTermCounts) {
|
|
86
|
-
const termFrequency = document.termFrequencies.get(token) ?? 0;
|
|
87
|
-
if (termFrequency === 0) continue;
|
|
88
|
-
const documentFrequency = index.documentFrequencies.get(token) ?? 0;
|
|
89
|
-
const idf = Math.log(1 + (index.documents.length - documentFrequency + 0.5) / (documentFrequency + 0.5));
|
|
90
|
-
const normalization = BM25_K1 * (1 - BM25_B + BM25_B * (document.length / index.averageLength));
|
|
91
|
-
score += queryTermCount * idf * ((termFrequency * (BM25_K1 + 1)) / (termFrequency + normalization));
|
|
92
|
-
}
|
|
93
|
-
return { agent: document.agent, score };
|
|
94
|
-
})
|
|
95
|
-
.filter((result) => result.score > 0)
|
|
96
|
-
.sort((left, right) => right.score - left.score || left.agent.name.localeCompare(right.agent.name))
|
|
97
|
-
.slice(0, limit);
|
|
98
|
-
}
|