pi-crew 0.1.44 → 0.1.45
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 +27 -0
- package/agents/analyst.md +11 -11
- package/agents/critic.md +11 -11
- package/agents/executor.md +11 -11
- package/agents/explorer.md +11 -11
- package/agents/planner.md +11 -11
- package/agents/reviewer.md +11 -11
- package/agents/security-reviewer.md +11 -11
- package/agents/test-engineer.md +11 -11
- package/agents/verifier.md +11 -11
- package/agents/writer.md +11 -11
- package/docs/refactor-tasks-phase3.md +394 -394
- package/docs/refactor-tasks-phase4.md +564 -564
- package/docs/refactor-tasks-phase5.md +402 -402
- package/docs/refactor-tasks-phase6.md +662 -662
- package/docs/research-extension-examples.md +297 -297
- package/docs/research-extension-system.md +324 -324
- package/docs/research-optimization-plan.md +548 -548
- package/docs/research-phase10-distillation.md +198 -198
- package/docs/research-phase11-distillation.md +201 -201
- package/docs/research-pi-coding-agent.md +357 -357
- package/docs/research-source-pi-crew-reference.md +174 -174
- package/docs/runtime-flow.md +148 -148
- package/docs/source-runtime-refactor-map.md +83 -83
- package/index.ts +6 -6
- package/package.json +1 -1
- package/src/agents/agent-serializer.ts +34 -34
- package/src/extension/cross-extension-rpc.ts +82 -82
- package/src/extension/register.ts +8 -1
- package/src/extension/registration/commands.ts +18 -2
- package/src/extension/registration/compaction-guard.ts +125 -125
- package/src/extension/registration/subagent-tools.ts +148 -148
- package/src/extension/registration/team-tool.ts +26 -8
- package/src/extension/run-bundle-schema.ts +89 -89
- package/src/extension/run-maintenance.ts +43 -43
- package/src/extension/team-tool/cancel.ts +105 -102
- package/src/extension/team-tool/context.ts +1 -0
- package/src/extension/team-tool/handle-settings.ts +188 -188
- package/src/extension/team-tool/inspect.ts +41 -41
- package/src/extension/team-tool/lifecycle-actions.ts +79 -79
- package/src/extension/team-tool/plan.ts +19 -19
- package/src/extension/team-tool/respond.ts +83 -66
- package/src/extension/team-tool/run.ts +1 -0
- package/src/i18n.ts +184 -184
- package/src/observability/exporters/otlp-exporter.ts +77 -77
- package/src/prompt/prompt-runtime.ts +72 -72
- package/src/runtime/agent-control.ts +63 -63
- 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/attention-events.ts +28 -28
- package/src/runtime/background-runner.ts +53 -53
- package/src/runtime/child-pi.ts +444 -444
- package/src/runtime/completion-guard.ts +190 -190
- package/src/runtime/crew-agent-records.ts +8 -0
- package/src/runtime/delivery-coordinator.ts +153 -142
- package/src/runtime/direct-run.ts +35 -35
- package/src/runtime/foreground-control.ts +82 -82
- package/src/runtime/green-contract.ts +46 -46
- package/src/runtime/group-join.ts +106 -106
- package/src/runtime/heartbeat-gradient.ts +28 -28
- package/src/runtime/heartbeat-watcher.ts +124 -124
- package/src/runtime/live-agent-control.ts +87 -87
- package/src/runtime/live-agent-manager.ts +85 -85
- package/src/runtime/live-control-realtime.ts +36 -36
- package/src/runtime/live-session-runtime.ts +305 -305
- package/src/runtime/overflow-recovery.ts +175 -156
- package/src/runtime/parallel-research.ts +44 -44
- package/src/runtime/pi-json-output.ts +111 -111
- package/src/runtime/policy-engine.ts +79 -79
- package/src/runtime/progress-event-coalescer.ts +43 -43
- package/src/runtime/recovery-recipes.ts +74 -74
- package/src/runtime/retry-executor.ts +64 -64
- package/src/runtime/role-permission.ts +39 -39
- package/src/runtime/session-resources.ts +25 -25
- package/src/runtime/session-snapshot.ts +59 -59
- package/src/runtime/session-usage.ts +79 -79
- package/src/runtime/sidechain-output.ts +29 -29
- package/src/runtime/stale-reconciler.ts +199 -179
- package/src/runtime/supervisor-contact.ts +59 -59
- package/src/runtime/task-display.ts +38 -38
- package/src/runtime/task-output-context.ts +127 -127
- package/src/runtime/task-runner/live-executor.ts +101 -101
- package/src/runtime/task-runner/progress.ts +119 -119
- package/src/runtime/task-runner/result-utils.ts +14 -14
- package/src/runtime/task-runner/state-helpers.ts +22 -22
- package/src/runtime/team-runner.ts +13 -4
- package/src/runtime/worker-heartbeat.ts +21 -21
- package/src/runtime/worker-startup.ts +57 -57
- package/src/state/state-store.ts +43 -0
- package/src/state/task-claims.ts +44 -44
- package/src/state/types.ts +2 -0
- 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/team-serializer.ts +38 -38
- package/src/types/diff.d.ts +18 -18
- package/src/ui/crew-footer.ts +101 -101
- package/src/ui/crew-select-list.ts +111 -111
- package/src/ui/crew-widget.ts +5 -1
- package/src/ui/dashboard-panes/mailbox-pane.ts +2 -1
- package/src/ui/dashboard-panes/metrics-pane.ts +34 -34
- package/src/ui/dynamic-border.ts +25 -25
- package/src/ui/layout-primitives.ts +106 -106
- package/src/ui/loaders.ts +158 -158
- package/src/ui/powerbar-publisher.ts +1 -1
- package/src/ui/render-diff.ts +119 -119
- package/src/ui/render-scheduler.ts +143 -143
- package/src/ui/run-snapshot-cache.ts +56 -37
- package/src/ui/snapshot-types.ts +5 -0
- package/src/ui/spinner.ts +17 -17
- package/src/ui/status-colors.ts +58 -58
- package/src/ui/syntax-highlight.ts +116 -116
- package/src/utils/atomic-write.ts +33 -33
- package/src/utils/completion-dedupe.ts +63 -63
- package/src/utils/frontmatter.ts +68 -68
- package/src/utils/git.ts +262 -262
- package/src/utils/ids.ts +12 -12
- package/src/utils/names.ts +27 -27
- package/src/utils/redaction.ts +44 -44
- package/src/utils/safe-paths.ts +47 -47
- package/src/utils/sleep.ts +32 -32
- package/src/workflows/validate-workflow.ts +40 -40
- package/src/worktree/branch-freshness.ts +45 -45
- 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/workflows/default.workflow.md +29 -29
- package/workflows/fast-fix.workflow.md +22 -22
- package/workflows/implementation.workflow.md +38 -38
- package/workflows/parallel-research.workflow.md +46 -46
- package/workflows/research.workflow.md +22 -22
- package/workflows/review.workflow.md +30 -30
package/src/utils/redaction.ts
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
const SECRET_KEY_PATTERN = /(?:^|[_.-])(token|api[-_]?key|password|passwd|secret|credential|authorization|private[-_]?key)(?:$|[_.-])/i;
|
|
2
|
-
const INLINE_SECRET_PATTERN = /(^|[\s,{])(([A-Za-z0-9_.-]*(?:api[-_]?key|token|password|passwd|secret|credential|authorization|private[-_]?key)[A-Za-z0-9_.-]*)\s*[=:]\s*)([^\s,;"'}]+)/gi;
|
|
3
|
-
const AUTH_HEADER_PATTERN = /\b(Authorization\s*:\s*(?:Bearer|Basic|Token)?\s*)([^\r\n]+)/gi;
|
|
4
|
-
const BEARER_PATTERN = /\b(Bearer\s+)([A-Za-z0-9._~+/=-]{8,})\b/g;
|
|
5
|
-
const PEM_PRIVATE_KEY_PATTERN = /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g;
|
|
6
|
-
|
|
7
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
8
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
9
|
-
// Exclude built-in types whose Object.entries() would produce empty arrays.
|
|
10
|
-
if (value instanceof Date || value instanceof RegExp || value instanceof Error || value instanceof Map || value instanceof Set) return false;
|
|
11
|
-
return true;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function isSecretKey(keyName: string): boolean {
|
|
15
|
-
return SECRET_KEY_PATTERN.test(keyName) || /^(token|apiKey|api_key|password|secret|credential|authorization|privateKey|private_key)$/i.test(keyName);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function redactSecretString(value: string): string {
|
|
19
|
-
return value
|
|
20
|
-
.replace(PEM_PRIVATE_KEY_PATTERN, "***")
|
|
21
|
-
.replace(AUTH_HEADER_PATTERN, "$1***")
|
|
22
|
-
.replace(BEARER_PATTERN, "$1***")
|
|
23
|
-
.replace(INLINE_SECRET_PATTERN, "$1$2***");
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function redactSecrets(value: unknown, keyName = ""): unknown {
|
|
27
|
-
if (keyName && isSecretKey(keyName)) return "***";
|
|
28
|
-
if (typeof value === "string") return redactSecretString(value);
|
|
29
|
-
if (Array.isArray(value)) return value.map((item) => redactSecrets(item));
|
|
30
|
-
if (isRecord(value)) {
|
|
31
|
-
const output: Record<string, unknown> = {};
|
|
32
|
-
for (const [key, entry] of Object.entries(value)) output[key] = redactSecrets(entry, key);
|
|
33
|
-
return output;
|
|
34
|
-
}
|
|
35
|
-
return value;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function redactJsonLine(line: string): string {
|
|
39
|
-
try {
|
|
40
|
-
return JSON.stringify(redactSecrets(JSON.parse(line) as unknown));
|
|
41
|
-
} catch {
|
|
42
|
-
return redactSecretString(line);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
1
|
+
const SECRET_KEY_PATTERN = /(?:^|[_.-])(token|api[-_]?key|password|passwd|secret|credential|authorization|private[-_]?key)(?:$|[_.-])/i;
|
|
2
|
+
const INLINE_SECRET_PATTERN = /(^|[\s,{])(([A-Za-z0-9_.-]*(?:api[-_]?key|token|password|passwd|secret|credential|authorization|private[-_]?key)[A-Za-z0-9_.-]*)\s*[=:]\s*)([^\s,;"'}]+)/gi;
|
|
3
|
+
const AUTH_HEADER_PATTERN = /\b(Authorization\s*:\s*(?:Bearer|Basic|Token)?\s*)([^\r\n]+)/gi;
|
|
4
|
+
const BEARER_PATTERN = /\b(Bearer\s+)([A-Za-z0-9._~+/=-]{8,})\b/g;
|
|
5
|
+
const PEM_PRIVATE_KEY_PATTERN = /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g;
|
|
6
|
+
|
|
7
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
8
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
9
|
+
// Exclude built-in types whose Object.entries() would produce empty arrays.
|
|
10
|
+
if (value instanceof Date || value instanceof RegExp || value instanceof Error || value instanceof Map || value instanceof Set) return false;
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function isSecretKey(keyName: string): boolean {
|
|
15
|
+
return SECRET_KEY_PATTERN.test(keyName) || /^(token|apiKey|api_key|password|secret|credential|authorization|privateKey|private_key)$/i.test(keyName);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function redactSecretString(value: string): string {
|
|
19
|
+
return value
|
|
20
|
+
.replace(PEM_PRIVATE_KEY_PATTERN, "***")
|
|
21
|
+
.replace(AUTH_HEADER_PATTERN, "$1***")
|
|
22
|
+
.replace(BEARER_PATTERN, "$1***")
|
|
23
|
+
.replace(INLINE_SECRET_PATTERN, "$1$2***");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function redactSecrets(value: unknown, keyName = ""): unknown {
|
|
27
|
+
if (keyName && isSecretKey(keyName)) return "***";
|
|
28
|
+
if (typeof value === "string") return redactSecretString(value);
|
|
29
|
+
if (Array.isArray(value)) return value.map((item) => redactSecrets(item));
|
|
30
|
+
if (isRecord(value)) {
|
|
31
|
+
const output: Record<string, unknown> = {};
|
|
32
|
+
for (const [key, entry] of Object.entries(value)) output[key] = redactSecrets(entry, key);
|
|
33
|
+
return output;
|
|
34
|
+
}
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function redactJsonLine(line: string): string {
|
|
39
|
+
try {
|
|
40
|
+
return JSON.stringify(redactSecrets(JSON.parse(line) as unknown));
|
|
41
|
+
} catch {
|
|
42
|
+
return redactSecretString(line);
|
|
43
|
+
}
|
|
44
|
+
}
|
package/src/utils/safe-paths.ts
CHANGED
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
|
|
4
|
-
export function isSafePathId(value: string): boolean {
|
|
5
|
-
return /^[A-Za-z0-9_-]+$/.test(value);
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function assertSafePathId(kind: string, value: string): string {
|
|
9
|
-
if (!isSafePathId(value)) throw new Error(`Invalid ${kind}: ${value}`);
|
|
10
|
-
return value;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function resolveContainedPath(baseDir: string, targetPath: string): string {
|
|
14
|
-
const base = path.resolve(baseDir);
|
|
15
|
-
const resolved = path.isAbsolute(targetPath) ? path.resolve(targetPath) : path.resolve(base, targetPath);
|
|
16
|
-
const relative = path.relative(base, resolved);
|
|
17
|
-
if (relative.startsWith("..") || path.isAbsolute(relative)) throw new Error(`Path is outside ${baseDir}: ${targetPath}`);
|
|
18
|
-
return resolved;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function resolveRealContainedPath(baseDir: string, targetPath: string): string {
|
|
22
|
-
const resolved = resolveContainedPath(baseDir, targetPath);
|
|
23
|
-
let realBase: string;
|
|
24
|
-
let realTarget: string;
|
|
25
|
-
try {
|
|
26
|
-
realBase = fs.realpathSync.native(baseDir);
|
|
27
|
-
} catch (baseError) {
|
|
28
|
-
throw new Error(`Cannot resolve real path of base directory ${baseDir}: ${baseError instanceof Error ? baseError.message : String(baseError)}`);
|
|
29
|
-
}
|
|
30
|
-
try {
|
|
31
|
-
realTarget = fs.realpathSync.native(resolved);
|
|
32
|
-
} catch (targetError) {
|
|
33
|
-
if ((targetError as NodeJS.ErrnoException).code === "ENOENT") {
|
|
34
|
-
throw new Error(`Path does not exist: ${resolved}`);
|
|
35
|
-
}
|
|
36
|
-
throw new Error(`Cannot resolve real path of ${resolved}: ${targetError instanceof Error ? targetError.message : String(targetError)}`);
|
|
37
|
-
}
|
|
38
|
-
const relative = path.relative(realBase, realTarget);
|
|
39
|
-
if (relative.startsWith("..") || path.isAbsolute(relative)) throw new Error(`Path is outside ${baseDir}: ${targetPath}`);
|
|
40
|
-
return realTarget;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function resolveContainedRelativePath(baseDir: string, relativePath: string, kind = "path"): string {
|
|
44
|
-
const normalized = relativePath.replaceAll("\\", "/").replace(/^\.\/+/, "");
|
|
45
|
-
if (!normalized || normalized.split("/").some((segment) => segment === "..") || path.isAbsolute(normalized)) throw new Error(`Invalid ${kind}: ${relativePath}`);
|
|
46
|
-
return resolveContainedPath(baseDir, path.resolve(baseDir, normalized));
|
|
47
|
-
}
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
|
|
4
|
+
export function isSafePathId(value: string): boolean {
|
|
5
|
+
return /^[A-Za-z0-9_-]+$/.test(value);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function assertSafePathId(kind: string, value: string): string {
|
|
9
|
+
if (!isSafePathId(value)) throw new Error(`Invalid ${kind}: ${value}`);
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function resolveContainedPath(baseDir: string, targetPath: string): string {
|
|
14
|
+
const base = path.resolve(baseDir);
|
|
15
|
+
const resolved = path.isAbsolute(targetPath) ? path.resolve(targetPath) : path.resolve(base, targetPath);
|
|
16
|
+
const relative = path.relative(base, resolved);
|
|
17
|
+
if (relative.startsWith("..") || path.isAbsolute(relative)) throw new Error(`Path is outside ${baseDir}: ${targetPath}`);
|
|
18
|
+
return resolved;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function resolveRealContainedPath(baseDir: string, targetPath: string): string {
|
|
22
|
+
const resolved = resolveContainedPath(baseDir, targetPath);
|
|
23
|
+
let realBase: string;
|
|
24
|
+
let realTarget: string;
|
|
25
|
+
try {
|
|
26
|
+
realBase = fs.realpathSync.native(baseDir);
|
|
27
|
+
} catch (baseError) {
|
|
28
|
+
throw new Error(`Cannot resolve real path of base directory ${baseDir}: ${baseError instanceof Error ? baseError.message : String(baseError)}`);
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
realTarget = fs.realpathSync.native(resolved);
|
|
32
|
+
} catch (targetError) {
|
|
33
|
+
if ((targetError as NodeJS.ErrnoException).code === "ENOENT") {
|
|
34
|
+
throw new Error(`Path does not exist: ${resolved}`);
|
|
35
|
+
}
|
|
36
|
+
throw new Error(`Cannot resolve real path of ${resolved}: ${targetError instanceof Error ? targetError.message : String(targetError)}`);
|
|
37
|
+
}
|
|
38
|
+
const relative = path.relative(realBase, realTarget);
|
|
39
|
+
if (relative.startsWith("..") || path.isAbsolute(relative)) throw new Error(`Path is outside ${baseDir}: ${targetPath}`);
|
|
40
|
+
return realTarget;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function resolveContainedRelativePath(baseDir: string, relativePath: string, kind = "path"): string {
|
|
44
|
+
const normalized = relativePath.replaceAll("\\", "/").replace(/^\.\/+/, "");
|
|
45
|
+
if (!normalized || normalized.split("/").some((segment) => segment === "..") || path.isAbsolute(normalized)) throw new Error(`Invalid ${kind}: ${relativePath}`);
|
|
46
|
+
return resolveContainedPath(baseDir, path.resolve(baseDir, normalized));
|
|
47
|
+
}
|
package/src/utils/sleep.ts
CHANGED
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sleep helper that respects abort signal.
|
|
3
|
-
*/
|
|
4
|
-
export function sleep(ms: number, signal?: AbortSignal): Promise<void> {
|
|
5
|
-
return new Promise((resolve, reject) => {
|
|
6
|
-
if (signal?.aborted) {
|
|
7
|
-
reject(signal.reason instanceof Error ? signal.reason : new Error("Aborted"));
|
|
8
|
-
return;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
let settled = false;
|
|
12
|
-
const cleanup = (): void => {
|
|
13
|
-
if (signal) signal.removeEventListener("abort", onAbort);
|
|
14
|
-
};
|
|
15
|
-
const timeout = setTimeout(() => {
|
|
16
|
-
if (settled) return;
|
|
17
|
-
settled = true;
|
|
18
|
-
cleanup();
|
|
19
|
-
resolve();
|
|
20
|
-
}, ms);
|
|
21
|
-
|
|
22
|
-
const onAbort = (): void => {
|
|
23
|
-
if (settled) return;
|
|
24
|
-
settled = true;
|
|
25
|
-
clearTimeout(timeout);
|
|
26
|
-
cleanup();
|
|
27
|
-
reject(signal?.reason instanceof Error ? signal.reason : new Error("Aborted"));
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
signal?.addEventListener("abort", onAbort);
|
|
31
|
-
});
|
|
32
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Sleep helper that respects abort signal.
|
|
3
|
+
*/
|
|
4
|
+
export function sleep(ms: number, signal?: AbortSignal): Promise<void> {
|
|
5
|
+
return new Promise((resolve, reject) => {
|
|
6
|
+
if (signal?.aborted) {
|
|
7
|
+
reject(signal.reason instanceof Error ? signal.reason : new Error("Aborted"));
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let settled = false;
|
|
12
|
+
const cleanup = (): void => {
|
|
13
|
+
if (signal) signal.removeEventListener("abort", onAbort);
|
|
14
|
+
};
|
|
15
|
+
const timeout = setTimeout(() => {
|
|
16
|
+
if (settled) return;
|
|
17
|
+
settled = true;
|
|
18
|
+
cleanup();
|
|
19
|
+
resolve();
|
|
20
|
+
}, ms);
|
|
21
|
+
|
|
22
|
+
const onAbort = (): void => {
|
|
23
|
+
if (settled) return;
|
|
24
|
+
settled = true;
|
|
25
|
+
clearTimeout(timeout);
|
|
26
|
+
cleanup();
|
|
27
|
+
reject(signal?.reason instanceof Error ? signal.reason : new Error("Aborted"));
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
signal?.addEventListener("abort", onAbort);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
import type { TeamConfig } from "../teams/team-config.ts";
|
|
2
|
-
import type { WorkflowConfig } from "./workflow-config.ts";
|
|
3
|
-
|
|
4
|
-
export function validateWorkflowForTeam(workflow: WorkflowConfig, team: TeamConfig): string[] {
|
|
5
|
-
const errors: string[] = [];
|
|
6
|
-
const roles = new Set(team.roles.map((role) => role.name));
|
|
7
|
-
const stepIds = new Set<string>();
|
|
8
|
-
|
|
9
|
-
for (const step of workflow.steps) {
|
|
10
|
-
if (stepIds.has(step.id)) errors.push(`Duplicate workflow step id '${step.id}'.`);
|
|
11
|
-
stepIds.add(step.id);
|
|
12
|
-
if (!roles.has(step.role)) errors.push(`Step '${step.id}' references unknown team role '${step.role}'.`);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
for (const step of workflow.steps) {
|
|
16
|
-
for (const dep of step.dependsOn ?? []) {
|
|
17
|
-
if (!stepIds.has(dep)) errors.push(`Step '${step.id}' depends on unknown step '${dep}'.`);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const visiting = new Set<string>();
|
|
22
|
-
const visited = new Set<string>();
|
|
23
|
-
const byId = new Map(workflow.steps.map((step) => [step.id, step]));
|
|
24
|
-
|
|
25
|
-
function visit(id: string, trail: string[]): void {
|
|
26
|
-
if (visited.has(id)) return;
|
|
27
|
-
if (visiting.has(id)) {
|
|
28
|
-
errors.push(`Workflow dependency cycle detected: ${[...trail, id].join(" -> ")}.`);
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
visiting.add(id);
|
|
32
|
-
const step = byId.get(id);
|
|
33
|
-
for (const dep of step?.dependsOn ?? []) visit(dep, [...trail, id]);
|
|
34
|
-
visiting.delete(id);
|
|
35
|
-
visited.add(id);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
for (const step of workflow.steps) visit(step.id, []);
|
|
39
|
-
return [...new Set(errors)];
|
|
40
|
-
}
|
|
1
|
+
import type { TeamConfig } from "../teams/team-config.ts";
|
|
2
|
+
import type { WorkflowConfig } from "./workflow-config.ts";
|
|
3
|
+
|
|
4
|
+
export function validateWorkflowForTeam(workflow: WorkflowConfig, team: TeamConfig): string[] {
|
|
5
|
+
const errors: string[] = [];
|
|
6
|
+
const roles = new Set(team.roles.map((role) => role.name));
|
|
7
|
+
const stepIds = new Set<string>();
|
|
8
|
+
|
|
9
|
+
for (const step of workflow.steps) {
|
|
10
|
+
if (stepIds.has(step.id)) errors.push(`Duplicate workflow step id '${step.id}'.`);
|
|
11
|
+
stepIds.add(step.id);
|
|
12
|
+
if (!roles.has(step.role)) errors.push(`Step '${step.id}' references unknown team role '${step.role}'.`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
for (const step of workflow.steps) {
|
|
16
|
+
for (const dep of step.dependsOn ?? []) {
|
|
17
|
+
if (!stepIds.has(dep)) errors.push(`Step '${step.id}' depends on unknown step '${dep}'.`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const visiting = new Set<string>();
|
|
22
|
+
const visited = new Set<string>();
|
|
23
|
+
const byId = new Map(workflow.steps.map((step) => [step.id, step]));
|
|
24
|
+
|
|
25
|
+
function visit(id: string, trail: string[]): void {
|
|
26
|
+
if (visited.has(id)) return;
|
|
27
|
+
if (visiting.has(id)) {
|
|
28
|
+
errors.push(`Workflow dependency cycle detected: ${[...trail, id].join(" -> ")}.`);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
visiting.add(id);
|
|
32
|
+
const step = byId.get(id);
|
|
33
|
+
for (const dep of step?.dependsOn ?? []) visit(dep, [...trail, id]);
|
|
34
|
+
visiting.delete(id);
|
|
35
|
+
visited.add(id);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for (const step of workflow.steps) visit(step.id, []);
|
|
39
|
+
return [...new Set(errors)];
|
|
40
|
+
}
|
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
2
|
-
|
|
3
|
-
export type BranchFreshnessStatus = "fresh" | "stale" | "diverged" | "unknown";
|
|
4
|
-
export type StaleBranchPolicy = "warn" | "block" | "auto_rebase" | "auto_merge_forward";
|
|
5
|
-
|
|
6
|
-
export interface BranchFreshness {
|
|
7
|
-
status: BranchFreshnessStatus;
|
|
8
|
-
branch?: string;
|
|
9
|
-
mainRef: string;
|
|
10
|
-
ahead: number;
|
|
11
|
-
behind: number;
|
|
12
|
-
missingFixes: string[];
|
|
13
|
-
message: string;
|
|
14
|
-
error?: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function git(cwd: string, args: string[]): string {
|
|
18
|
-
return execFileSync("git", args, { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function count(cwd: string, range: string): number {
|
|
22
|
-
const raw = git(cwd, ["rev-list", "--count", range]);
|
|
23
|
-
const parsed = Number.parseInt(raw, 10);
|
|
24
|
-
return Number.isFinite(parsed) ? parsed : 0;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function checkBranchFreshness(cwd: string, mainRef = "main"): BranchFreshness {
|
|
28
|
-
try {
|
|
29
|
-
git(cwd, ["rev-parse", "--is-inside-work-tree"]);
|
|
30
|
-
const branch = git(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
31
|
-
const behind = count(cwd, `${branch}..${mainRef}`);
|
|
32
|
-
const ahead = count(cwd, `${mainRef}..${branch}`);
|
|
33
|
-
const missingFixes = behind > 0 ? git(cwd, ["log", "--format=%s", `${branch}..${mainRef}`]).split("\n").map((line) => line.trim()).filter(Boolean) : [];
|
|
34
|
-
if (behind === 0) return { status: "fresh", branch, mainRef, ahead, behind, missingFixes, message: `Branch '${branch}' is fresh against ${mainRef}.` };
|
|
35
|
-
if (ahead > 0) return { status: "diverged", branch, mainRef, ahead, behind, missingFixes, message: `Branch '${branch}' diverged from ${mainRef}: ahead=${ahead}, behind=${behind}.` };
|
|
36
|
-
return { status: "stale", branch, mainRef, ahead, behind, missingFixes, message: `Branch '${branch}' is ${behind} commit(s) behind ${mainRef}.` };
|
|
37
|
-
} catch (error) {
|
|
38
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
39
|
-
return { status: "unknown", mainRef, ahead: 0, behind: 0, missingFixes: [], message: "Branch freshness could not be determined.", error: message };
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function shouldBlockForBranchFreshness(freshness: BranchFreshness, policy: StaleBranchPolicy = "warn"): boolean {
|
|
44
|
-
return policy === "block" && (freshness.status === "stale" || freshness.status === "diverged");
|
|
45
|
-
}
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
export type BranchFreshnessStatus = "fresh" | "stale" | "diverged" | "unknown";
|
|
4
|
+
export type StaleBranchPolicy = "warn" | "block" | "auto_rebase" | "auto_merge_forward";
|
|
5
|
+
|
|
6
|
+
export interface BranchFreshness {
|
|
7
|
+
status: BranchFreshnessStatus;
|
|
8
|
+
branch?: string;
|
|
9
|
+
mainRef: string;
|
|
10
|
+
ahead: number;
|
|
11
|
+
behind: number;
|
|
12
|
+
missingFixes: string[];
|
|
13
|
+
message: string;
|
|
14
|
+
error?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function git(cwd: string, args: string[]): string {
|
|
18
|
+
return execFileSync("git", args, { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function count(cwd: string, range: string): number {
|
|
22
|
+
const raw = git(cwd, ["rev-list", "--count", range]);
|
|
23
|
+
const parsed = Number.parseInt(raw, 10);
|
|
24
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function checkBranchFreshness(cwd: string, mainRef = "main"): BranchFreshness {
|
|
28
|
+
try {
|
|
29
|
+
git(cwd, ["rev-parse", "--is-inside-work-tree"]);
|
|
30
|
+
const branch = git(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
31
|
+
const behind = count(cwd, `${branch}..${mainRef}`);
|
|
32
|
+
const ahead = count(cwd, `${mainRef}..${branch}`);
|
|
33
|
+
const missingFixes = behind > 0 ? git(cwd, ["log", "--format=%s", `${branch}..${mainRef}`]).split("\n").map((line) => line.trim()).filter(Boolean) : [];
|
|
34
|
+
if (behind === 0) return { status: "fresh", branch, mainRef, ahead, behind, missingFixes, message: `Branch '${branch}' is fresh against ${mainRef}.` };
|
|
35
|
+
if (ahead > 0) return { status: "diverged", branch, mainRef, ahead, behind, missingFixes, message: `Branch '${branch}' diverged from ${mainRef}: ahead=${ahead}, behind=${behind}.` };
|
|
36
|
+
return { status: "stale", branch, mainRef, ahead, behind, missingFixes, message: `Branch '${branch}' is ${behind} commit(s) behind ${mainRef}.` };
|
|
37
|
+
} catch (error) {
|
|
38
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
39
|
+
return { status: "unknown", mainRef, ahead: 0, behind: 0, missingFixes: [], message: "Branch freshness could not be determined.", error: message };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function shouldBlockForBranchFreshness(freshness: BranchFreshness, policy: StaleBranchPolicy = "warn"): boolean {
|
|
44
|
+
return policy === "block" && (freshness.status === "stale" || freshness.status === "diverged");
|
|
45
|
+
}
|
package/teams/default.team.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: default
|
|
3
|
-
description: Balanced team for ordinary implementation tasks
|
|
4
|
-
defaultWorkflow: default
|
|
5
|
-
workspaceMode: single
|
|
6
|
-
maxConcurrency: 2
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
- explorer: agent=explorer fast discovery
|
|
10
|
-
- planner: agent=planner plan the work
|
|
11
|
-
- executor: agent=executor implement changes
|
|
12
|
-
- verifier: agent=verifier verify completion
|
|
1
|
+
---
|
|
2
|
+
name: default
|
|
3
|
+
description: Balanced team for ordinary implementation tasks
|
|
4
|
+
defaultWorkflow: default
|
|
5
|
+
workspaceMode: single
|
|
6
|
+
maxConcurrency: 2
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
- explorer: agent=explorer fast discovery
|
|
10
|
+
- planner: agent=planner plan the work
|
|
11
|
+
- executor: agent=executor implement changes
|
|
12
|
+
- verifier: agent=verifier verify completion
|
package/teams/fast-fix.team.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: fast-fix
|
|
3
|
-
description: Small team for quick bug fixes
|
|
4
|
-
defaultWorkflow: fast-fix
|
|
5
|
-
workspaceMode: single
|
|
6
|
-
maxConcurrency: 1
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
- explorer: agent=explorer find the relevant files
|
|
10
|
-
- executor: agent=executor make the fix
|
|
11
|
-
- verifier: agent=verifier verify the fix
|
|
1
|
+
---
|
|
2
|
+
name: fast-fix
|
|
3
|
+
description: Small team for quick bug fixes
|
|
4
|
+
defaultWorkflow: fast-fix
|
|
5
|
+
workspaceMode: single
|
|
6
|
+
maxConcurrency: 1
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
- explorer: agent=explorer find the relevant files
|
|
10
|
+
- executor: agent=executor make the fix
|
|
11
|
+
- verifier: agent=verifier verify the fix
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: implementation
|
|
3
|
-
description: Full implementation team with parallel specialists, critique, execution, review, and verification
|
|
4
|
-
defaultWorkflow: implementation
|
|
5
|
-
workspaceMode: single
|
|
6
|
-
maxConcurrency: 3
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
- explorer: agent=explorer map the codebase
|
|
10
|
-
- analyst: agent=analyst clarify requirements and constraints
|
|
11
|
-
- planner: agent=planner create execution plan
|
|
12
|
-
- critic: agent=critic challenge and synthesize specialist findings
|
|
13
|
-
- executor: agent=executor implement the plan
|
|
14
|
-
- reviewer: agent=reviewer review the implementation
|
|
15
|
-
- security-reviewer: agent=security-reviewer review security and trust boundaries
|
|
16
|
-
- test-engineer: agent=test-engineer design and run verification
|
|
17
|
-
- verifier: agent=verifier verify done
|
|
18
|
-
- writer: agent=writer summarize documentation or release notes when needed
|
|
1
|
+
---
|
|
2
|
+
name: implementation
|
|
3
|
+
description: Full implementation team with parallel specialists, critique, execution, review, and verification
|
|
4
|
+
defaultWorkflow: implementation
|
|
5
|
+
workspaceMode: single
|
|
6
|
+
maxConcurrency: 3
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
- explorer: agent=explorer map the codebase
|
|
10
|
+
- analyst: agent=analyst clarify requirements and constraints
|
|
11
|
+
- planner: agent=planner create execution plan
|
|
12
|
+
- critic: agent=critic challenge and synthesize specialist findings
|
|
13
|
+
- executor: agent=executor implement the plan
|
|
14
|
+
- reviewer: agent=reviewer review the implementation
|
|
15
|
+
- security-reviewer: agent=security-reviewer review security and trust boundaries
|
|
16
|
+
- test-engineer: agent=test-engineer design and run verification
|
|
17
|
+
- verifier: agent=verifier verify done
|
|
18
|
+
- writer: agent=writer summarize documentation or release notes when needed
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: parallel-research
|
|
3
|
-
description: Parallel research team for multi-project/source audits
|
|
4
|
-
workspaceMode: single
|
|
5
|
-
defaultWorkflow: parallel-research
|
|
6
|
-
maxConcurrency: 4
|
|
7
|
-
triggers: đọc sâu, deep read, deep research, source audit, multiple projects, parallel research, pi-*
|
|
8
|
-
category: research
|
|
9
|
-
cost: cheap
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
- explorer: agent=explorer gather source facts in parallel shards
|
|
13
|
-
- analyst: agent=analyst synthesize shard findings
|
|
14
|
-
- writer: agent=writer produce final notes
|
|
1
|
+
---
|
|
2
|
+
name: parallel-research
|
|
3
|
+
description: Parallel research team for multi-project/source audits
|
|
4
|
+
workspaceMode: single
|
|
5
|
+
defaultWorkflow: parallel-research
|
|
6
|
+
maxConcurrency: 4
|
|
7
|
+
triggers: đọc sâu, deep read, deep research, source audit, multiple projects, parallel research, pi-*
|
|
8
|
+
category: research
|
|
9
|
+
cost: cheap
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
- explorer: agent=explorer gather source facts in parallel shards
|
|
13
|
+
- analyst: agent=analyst synthesize shard findings
|
|
14
|
+
- writer: agent=writer produce final notes
|
package/teams/research.team.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: research
|
|
3
|
-
description: Team for investigation and documentation
|
|
4
|
-
defaultWorkflow: research
|
|
5
|
-
workspaceMode: single
|
|
6
|
-
maxConcurrency: 2
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
- explorer: agent=explorer gather codebase facts
|
|
10
|
-
- analyst: agent=analyst analyze findings
|
|
11
|
-
- writer: agent=writer produce final notes
|
|
1
|
+
---
|
|
2
|
+
name: research
|
|
3
|
+
description: Team for investigation and documentation
|
|
4
|
+
defaultWorkflow: research
|
|
5
|
+
workspaceMode: single
|
|
6
|
+
maxConcurrency: 2
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
- explorer: agent=explorer gather codebase facts
|
|
10
|
+
- analyst: agent=analyst analyze findings
|
|
11
|
+
- writer: agent=writer produce final notes
|
package/teams/review.team.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: review
|
|
3
|
-
description: Team for code review and security review
|
|
4
|
-
defaultWorkflow: review
|
|
5
|
-
workspaceMode: single
|
|
6
|
-
maxConcurrency: 2
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
- explorer: agent=explorer understand changed areas
|
|
10
|
-
- reviewer: agent=reviewer review correctness and maintainability
|
|
11
|
-
- security-reviewer: agent=security-reviewer review security risks
|
|
12
|
-
- verifier: agent=verifier summarize pass/fail
|
|
1
|
+
---
|
|
2
|
+
name: review
|
|
3
|
+
description: Team for code review and security review
|
|
4
|
+
defaultWorkflow: review
|
|
5
|
+
workspaceMode: single
|
|
6
|
+
maxConcurrency: 2
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
- explorer: agent=explorer understand changed areas
|
|
10
|
+
- reviewer: agent=reviewer review correctness and maintainability
|
|
11
|
+
- security-reviewer: agent=security-reviewer review security risks
|
|
12
|
+
- verifier: agent=verifier summarize pass/fail
|
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: default
|
|
3
|
-
description: Explore, plan, execute, and verify
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
## explore
|
|
7
|
-
role: explorer
|
|
8
|
-
|
|
9
|
-
Explore the codebase for the goal: {goal}
|
|
10
|
-
|
|
11
|
-
## plan
|
|
12
|
-
role: planner
|
|
13
|
-
dependsOn: explore
|
|
14
|
-
output: plan.md
|
|
15
|
-
|
|
16
|
-
Create a concise implementation plan for: {goal}
|
|
17
|
-
|
|
18
|
-
## execute
|
|
19
|
-
role: executor
|
|
20
|
-
dependsOn: plan
|
|
21
|
-
|
|
22
|
-
Implement the plan for: {goal}
|
|
23
|
-
|
|
24
|
-
## verify
|
|
25
|
-
role: verifier
|
|
26
|
-
dependsOn: execute
|
|
27
|
-
verify: true
|
|
28
|
-
|
|
29
|
-
Verify completion for: {goal}
|
|
1
|
+
---
|
|
2
|
+
name: default
|
|
3
|
+
description: Explore, plan, execute, and verify
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## explore
|
|
7
|
+
role: explorer
|
|
8
|
+
|
|
9
|
+
Explore the codebase for the goal: {goal}
|
|
10
|
+
|
|
11
|
+
## plan
|
|
12
|
+
role: planner
|
|
13
|
+
dependsOn: explore
|
|
14
|
+
output: plan.md
|
|
15
|
+
|
|
16
|
+
Create a concise implementation plan for: {goal}
|
|
17
|
+
|
|
18
|
+
## execute
|
|
19
|
+
role: executor
|
|
20
|
+
dependsOn: plan
|
|
21
|
+
|
|
22
|
+
Implement the plan for: {goal}
|
|
23
|
+
|
|
24
|
+
## verify
|
|
25
|
+
role: verifier
|
|
26
|
+
dependsOn: execute
|
|
27
|
+
verify: true
|
|
28
|
+
|
|
29
|
+
Verify completion for: {goal}
|