pi-crew 0.1.41 → 0.1.44

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.
Files changed (191) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +51 -0
  3. package/agents/analyst.md +11 -11
  4. package/agents/critic.md +11 -11
  5. package/agents/executor.md +11 -11
  6. package/agents/explorer.md +11 -11
  7. package/agents/planner.md +11 -11
  8. package/agents/reviewer.md +11 -11
  9. package/agents/security-reviewer.md +11 -11
  10. package/agents/test-engineer.md +11 -11
  11. package/agents/verifier.md +11 -11
  12. package/agents/writer.md +11 -11
  13. package/docs/refactor-tasks-phase3.md +394 -394
  14. package/docs/refactor-tasks-phase4.md +564 -564
  15. package/docs/refactor-tasks-phase5.md +402 -402
  16. package/docs/refactor-tasks-phase6.md +662 -662
  17. package/docs/research-extension-examples.md +297 -297
  18. package/docs/research-extension-system.md +324 -324
  19. package/docs/research-optimization-plan.md +548 -548
  20. package/docs/research-phase10-distillation.md +199 -0
  21. package/docs/research-phase11-distillation.md +201 -0
  22. package/docs/research-pi-coding-agent.md +357 -357
  23. package/docs/research-source-pi-crew-reference.md +174 -174
  24. package/docs/runtime-flow.md +148 -148
  25. package/docs/source-runtime-refactor-map.md +83 -83
  26. package/index.ts +6 -6
  27. package/package.json +1 -1
  28. package/src/agents/agent-serializer.ts +34 -34
  29. package/src/agents/discover-agents.ts +5 -4
  30. package/src/config/config.ts +28 -4
  31. package/src/extension/cross-extension-rpc.ts +82 -82
  32. package/src/extension/management.ts +37 -8
  33. package/src/extension/notification-router.ts +2 -2
  34. package/src/extension/register.ts +130 -8
  35. package/src/extension/registration/commands.ts +11 -9
  36. package/src/extension/registration/compaction-guard.ts +125 -125
  37. package/src/extension/registration/subagent-tools.ts +28 -19
  38. package/src/extension/registration/team-tool.ts +2 -1
  39. package/src/extension/result-watcher.ts +4 -4
  40. package/src/extension/run-bundle-schema.ts +8 -4
  41. package/src/extension/run-import.ts +4 -0
  42. package/src/extension/run-index.ts +23 -1
  43. package/src/extension/run-maintenance.ts +43 -24
  44. package/src/extension/team-tool/api.ts +2 -2
  45. package/src/extension/team-tool/cancel.ts +76 -4
  46. package/src/extension/team-tool/context.ts +1 -0
  47. package/src/extension/team-tool/doctor.ts +8 -1
  48. package/src/extension/team-tool/handle-settings.ts +188 -0
  49. package/src/extension/team-tool/inspect.ts +41 -41
  50. package/src/extension/team-tool/lifecycle-actions.ts +79 -79
  51. package/src/extension/team-tool/plan.ts +19 -19
  52. package/src/extension/team-tool/respond.ts +67 -0
  53. package/src/extension/team-tool/run.ts +6 -4
  54. package/src/extension/team-tool/status.ts +99 -93
  55. package/src/extension/team-tool-types.ts +4 -0
  56. package/src/extension/team-tool.ts +5 -1
  57. package/src/i18n.ts +184 -0
  58. package/src/observability/correlation.ts +2 -2
  59. package/src/observability/event-to-metric.ts +10 -3
  60. package/src/observability/exporters/adapter.ts +7 -1
  61. package/src/observability/exporters/otlp-exporter.ts +14 -2
  62. package/src/observability/exporters/prometheus-exporter.ts +9 -2
  63. package/src/observability/metric-registry.ts +18 -3
  64. package/src/observability/metric-retention.ts +11 -3
  65. package/src/observability/metric-sink.ts +9 -4
  66. package/src/observability/metrics-primitives.ts +4 -3
  67. package/src/prompt/prompt-runtime.ts +72 -68
  68. package/src/runtime/agent-control.ts +63 -63
  69. package/src/runtime/agent-memory.ts +72 -72
  70. package/src/runtime/agent-observability.ts +114 -114
  71. package/src/runtime/async-marker.ts +26 -26
  72. package/src/runtime/attention-events.ts +28 -23
  73. package/src/runtime/background-runner.ts +53 -53
  74. package/src/runtime/child-pi.ts +4 -4
  75. package/src/runtime/completion-guard.ts +95 -4
  76. package/src/runtime/concurrency.ts +1 -1
  77. package/src/runtime/crash-recovery.ts +32 -1
  78. package/src/runtime/crew-agent-runtime.ts +59 -58
  79. package/src/runtime/deadletter.ts +14 -4
  80. package/src/runtime/delivery-coordinator.ts +143 -0
  81. package/src/runtime/direct-run.ts +35 -35
  82. package/src/runtime/foreground-control.ts +82 -82
  83. package/src/runtime/green-contract.ts +46 -46
  84. package/src/runtime/group-join.ts +106 -106
  85. package/src/runtime/heartbeat-gradient.ts +28 -28
  86. package/src/runtime/heartbeat-watcher.ts +48 -4
  87. package/src/runtime/live-agent-control.ts +87 -87
  88. package/src/runtime/live-agent-manager.ts +85 -85
  89. package/src/runtime/live-control-realtime.ts +36 -36
  90. package/src/runtime/live-session-runtime.ts +305 -305
  91. package/src/runtime/manifest-cache.ts +2 -2
  92. package/src/runtime/model-fallback.ts +272 -261
  93. package/src/runtime/overflow-recovery.ts +157 -0
  94. package/src/runtime/parallel-research.ts +44 -44
  95. package/src/runtime/parallel-utils.ts +1 -1
  96. package/src/runtime/pi-json-output.ts +111 -111
  97. package/src/runtime/policy-engine.ts +79 -78
  98. package/src/runtime/post-exit-stdio-guard.ts +2 -2
  99. package/src/runtime/process-status.ts +56 -56
  100. package/src/runtime/progress-event-coalescer.ts +43 -43
  101. package/src/runtime/recovery-recipes.ts +74 -74
  102. package/src/runtime/retry-executor.ts +5 -0
  103. package/src/runtime/role-permission.ts +39 -39
  104. package/src/runtime/runtime-resolver.ts +1 -1
  105. package/src/runtime/session-resources.ts +25 -0
  106. package/src/runtime/session-snapshot.ts +59 -0
  107. package/src/runtime/session-usage.ts +79 -79
  108. package/src/runtime/sidechain-output.ts +29 -29
  109. package/src/runtime/stale-reconciler.ts +179 -0
  110. package/src/runtime/subagent-manager.ts +3 -3
  111. package/src/runtime/supervisor-contact.ts +59 -0
  112. package/src/runtime/task-display.ts +38 -38
  113. package/src/runtime/task-output-context.ts +127 -127
  114. package/src/runtime/task-runner/live-executor.ts +101 -101
  115. package/src/runtime/task-runner/progress.ts +119 -111
  116. package/src/runtime/task-runner/result-utils.ts +14 -14
  117. package/src/runtime/task-runner/state-helpers.ts +22 -22
  118. package/src/runtime/task-runner.ts +14 -0
  119. package/src/runtime/team-runner.ts +9 -10
  120. package/src/runtime/worker-heartbeat.ts +21 -21
  121. package/src/runtime/worker-startup.ts +57 -57
  122. package/src/schema/config-schema.ts +2 -1
  123. package/src/schema/team-tool-schema.ts +115 -109
  124. package/src/state/artifact-store.ts +4 -2
  125. package/src/state/atomic-write.ts +12 -4
  126. package/src/state/contracts.ts +109 -105
  127. package/src/state/event-log.ts +3 -4
  128. package/src/state/jsonl-writer.ts +4 -1
  129. package/src/state/locks.ts +9 -1
  130. package/src/state/task-claims.ts +44 -42
  131. package/src/state/usage.ts +29 -29
  132. package/src/subagents/async-entry.ts +1 -1
  133. package/src/subagents/index.ts +3 -3
  134. package/src/subagents/live/control.ts +1 -1
  135. package/src/subagents/live/manager.ts +1 -1
  136. package/src/subagents/live/realtime.ts +1 -1
  137. package/src/subagents/live/session-runtime.ts +1 -1
  138. package/src/subagents/manager.ts +1 -1
  139. package/src/subagents/spawn.ts +1 -1
  140. package/src/teams/discover-teams.ts +2 -2
  141. package/src/teams/team-serializer.ts +38 -38
  142. package/src/types/diff.d.ts +18 -18
  143. package/src/ui/crew-footer.ts +101 -101
  144. package/src/ui/crew-select-list.ts +111 -111
  145. package/src/ui/crew-widget.ts +5 -4
  146. package/src/ui/dashboard-panes/metrics-pane.ts +34 -34
  147. package/src/ui/dynamic-border.ts +25 -25
  148. package/src/ui/layout-primitives.ts +106 -106
  149. package/src/ui/live-run-sidebar.ts +1 -1
  150. package/src/ui/loaders.ts +158 -158
  151. package/src/ui/mascot.ts +3 -2
  152. package/src/ui/powerbar-publisher.ts +7 -6
  153. package/src/ui/render-diff.ts +119 -119
  154. package/src/ui/render-scheduler.ts +54 -14
  155. package/src/ui/run-dashboard.ts +39 -11
  156. package/src/ui/run-snapshot-cache.ts +336 -36
  157. package/src/ui/spinner.ts +17 -17
  158. package/src/ui/status-colors.ts +58 -54
  159. package/src/ui/syntax-highlight.ts +116 -116
  160. package/src/ui/theme-adapter.ts +1 -1
  161. package/src/ui/transcript-viewer.ts +7 -2
  162. package/src/utils/atomic-write.ts +33 -0
  163. package/src/utils/completion-dedupe.ts +63 -63
  164. package/src/utils/file-coalescer.ts +5 -3
  165. package/src/utils/frontmatter.ts +68 -36
  166. package/src/utils/git.ts +262 -262
  167. package/src/utils/ids.ts +12 -12
  168. package/src/utils/internal-error.ts +1 -1
  169. package/src/utils/names.ts +27 -26
  170. package/src/utils/paths.ts +1 -1
  171. package/src/utils/redaction.ts +44 -41
  172. package/src/utils/safe-paths.ts +47 -34
  173. package/src/utils/sleep.ts +2 -2
  174. package/src/utils/timings.ts +2 -0
  175. package/src/utils/visual.ts +9 -1
  176. package/src/workflows/discover-workflows.ts +4 -1
  177. package/src/workflows/validate-workflow.ts +40 -40
  178. package/src/worktree/branch-freshness.ts +45 -45
  179. package/src/worktree/worktree-manager.ts +6 -1
  180. package/teams/default.team.md +12 -12
  181. package/teams/fast-fix.team.md +11 -11
  182. package/teams/implementation.team.md +18 -18
  183. package/teams/parallel-research.team.md +14 -14
  184. package/teams/research.team.md +11 -11
  185. package/teams/review.team.md +12 -12
  186. package/workflows/default.workflow.md +29 -29
  187. package/workflows/fast-fix.workflow.md +22 -22
  188. package/workflows/implementation.workflow.md +38 -38
  189. package/workflows/parallel-research.workflow.md +46 -46
  190. package/workflows/research.workflow.md +22 -22
  191. package/workflows/review.workflow.md +30 -30
@@ -1,41 +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
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
9
- }
10
-
11
- function isSecretKey(keyName: string): boolean {
12
- return SECRET_KEY_PATTERN.test(keyName) || /^(token|apiKey|api_key|password|secret|credential|authorization|privateKey|private_key)$/i.test(keyName);
13
- }
14
-
15
- export function redactSecretString(value: string): string {
16
- return value
17
- .replace(PEM_PRIVATE_KEY_PATTERN, "***")
18
- .replace(AUTH_HEADER_PATTERN, "$1***")
19
- .replace(BEARER_PATTERN, "$1***")
20
- .replace(INLINE_SECRET_PATTERN, "$1$2***");
21
- }
22
-
23
- export function redactSecrets(value: unknown, keyName = ""): unknown {
24
- if (keyName && isSecretKey(keyName)) return "***";
25
- if (typeof value === "string") return redactSecretString(value);
26
- if (Array.isArray(value)) return value.map((item) => redactSecrets(item));
27
- if (isRecord(value)) {
28
- const output: Record<string, unknown> = {};
29
- for (const [key, entry] of Object.entries(value)) output[key] = redactSecrets(entry, key);
30
- return output;
31
- }
32
- return value;
33
- }
34
-
35
- export function redactJsonLine(line: string): string {
36
- try {
37
- return JSON.stringify(redactSecrets(JSON.parse(line) as unknown));
38
- } catch {
39
- return redactSecretString(line);
40
- }
41
- }
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,34 +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
- const realBase = fs.realpathSync.native(baseDir);
24
- const realTarget = fs.realpathSync.native(resolved);
25
- const relative = path.relative(realBase, realTarget);
26
- if (relative.startsWith("..") || path.isAbsolute(relative)) throw new Error(`Path is outside ${baseDir}: ${targetPath}`);
27
- return realTarget;
28
- }
29
-
30
- export function resolveContainedRelativePath(baseDir: string, relativePath: string, kind = "path"): string {
31
- const normalized = relativePath.replaceAll("\\", "/").replace(/^\.\/+/, "");
32
- if (!normalized || normalized.split("/").some((segment) => segment === "..") || path.isAbsolute(normalized)) throw new Error(`Invalid ${kind}: ${relativePath}`);
33
- return resolveContainedPath(baseDir, path.resolve(baseDir, normalized));
34
- }
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
+ }
@@ -4,7 +4,7 @@
4
4
  export function sleep(ms: number, signal?: AbortSignal): Promise<void> {
5
5
  return new Promise((resolve, reject) => {
6
6
  if (signal?.aborted) {
7
- reject(new Error("Aborted"));
7
+ reject(signal.reason instanceof Error ? signal.reason : new Error("Aborted"));
8
8
  return;
9
9
  }
10
10
 
@@ -24,7 +24,7 @@ export function sleep(ms: number, signal?: AbortSignal): Promise<void> {
24
24
  settled = true;
25
25
  clearTimeout(timeout);
26
26
  cleanup();
27
- reject(new Error("Aborted"));
27
+ reject(signal?.reason instanceof Error ? signal.reason : new Error("Aborted"));
28
28
  };
29
29
 
30
30
  signal?.addEventListener("abort", onAbort);
@@ -5,6 +5,7 @@
5
5
 
6
6
  const ENABLED = process.env.PI_TIMING === "1";
7
7
  const timings: Array<{ label: string; ms: number }> = [];
8
+ const MAX_TIMINGS = 500;
8
9
  let lastTime = Date.now();
9
10
 
10
11
  export function resetTimings(): void {
@@ -15,6 +16,7 @@ export function resetTimings(): void {
15
16
 
16
17
  export function time(label: string): void {
17
18
  if (!ENABLED) return;
19
+ if (timings.length >= MAX_TIMINGS) timings.shift();
18
20
  const now = Date.now();
19
21
  timings.push({ label, ms: now - lastTime });
20
22
  lastTime = now;
@@ -4,6 +4,14 @@ const WIDTH_CACHE_LIMIT = 256;
4
4
  const widthCache = new Map<string, number>();
5
5
 
6
6
  export function visibleWidth(value: string): number {
7
+ // Skip caching for very long strings to avoid memory pressure.
8
+ if (value.length > 4096) {
9
+ let length = 0;
10
+ for (const char of value.replace(ANSI_PATTERN, "")) {
11
+ if (char !== "\n") length += 1;
12
+ }
13
+ return length;
14
+ }
7
15
  const cached = widthCache.get(value);
8
16
  if (cached !== undefined) return cached;
9
17
  let length = 0;
@@ -57,7 +65,7 @@ export function truncateToWidth(value: string, width: number, ellipsis = "…"):
57
65
  continue;
58
66
  }
59
67
  const char = value[i] as string;
60
- const nextIndex = i + (char.codePointAt(0) ?? 0) > 0xFFFF ? i + 2 : i + 1;
68
+ const nextIndex = ((char.codePointAt(0) ?? 0) > 0xFFFF) ? i + 2 : i + 1;
61
69
  const segment = value.slice(i, nextIndex);
62
70
  const charWidth = visibleWidth(segment);
63
71
  if (renderedWidth + charWidth > width - ellipsis.length) {
@@ -62,7 +62,10 @@ const parseOptionalInteger = (value: string | undefined): number | undefined =>
62
62
 
63
63
  function hasSectionBoundary(body: string, match: RegExpMatchArray): boolean {
64
64
  const index = match.index ?? 0;
65
- return index === 0 || body.slice(0, index).trim() === "" || body.slice(Math.max(0, index - 2), index) === "\n\n";
65
+ if (index === 0 || body.slice(0, index).trim() === "") return true;
66
+ const prev = body.slice(Math.max(0, index - 2), index);
67
+ // Accept blank line or single newline before heading.
68
+ return prev === "\n\n" || prev.endsWith("\n");
66
69
  }
67
70
 
68
71
  function isStepHeading(body: string, match: RegExpMatchArray): boolean {
@@ -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
+ }
@@ -92,7 +92,12 @@ export function prepareTaskWorkspace(manifest: TeamRunManifest, task: TeamTaskSt
92
92
  const worktreePath = path.join(worktreeRoot, task.id);
93
93
  const branch = `pi-crew/${sanitizeBranchPart(manifest.runId)}/${sanitizeBranchPart(task.id)}`;
94
94
  if (fs.existsSync(worktreePath)) {
95
- const currentBranch = git(worktreePath, ["rev-parse", "--abbrev-ref", "HEAD"]);
95
+ let currentBranch: string;
96
+ try {
97
+ currentBranch = git(worktreePath, ["rev-parse", "--abbrev-ref", "HEAD"]);
98
+ } catch (gitError) {
99
+ throw new Error(`Existing worktree at ${worktreePath} is not a valid git repository; cannot verify branch: ${gitError instanceof Error ? gitError.message : String(gitError)}`);
100
+ }
96
101
  if (currentBranch !== branch) {
97
102
  throw new Error(`Existing worktree branch mismatch at ${worktreePath}: expected '${branch}', got '${currentBranch}'.`);
98
103
  }
@@ -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
@@ -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
@@ -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
@@ -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