@getpipher/armory-fleet 0.3.0 → 0.5.0
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/package.json +1 -1
- package/src/engine/run-registry.ts +13 -1
- package/src/engine/spawnSubagent.ts +33 -16
- package/src/index.ts +155 -2
- package/src/lifecycle/artifacts-parser.ts +57 -0
- package/src/lifecycle/default.ts +74 -0
- package/src/lifecycle/lifecycle-todo.ts +84 -0
- package/src/lifecycle/lifecycle-types.ts +66 -0
- package/src/lifecycle/port.ts +7 -0
- package/src/lifecycle/prompt-template.ts +40 -0
- package/src/lifecycle/registry.ts +169 -0
- package/src/lifecycle/run-lifecycle.ts +251 -0
- package/src/panel/bg-runs-store.ts +36 -0
- package/src/panel/fleet-items.ts +36 -0
- package/src/panel/fleet-panel.ts +334 -18
- package/src/panel/rows.ts +90 -0
- package/src/runtime/async-runner.ts +123 -0
- package/src/runtime/concurrency-pool.ts +27 -0
- package/src/runtime/results-inbox.ts +45 -0
- package/src/runtime/resume.ts +48 -0
- package/src/runtime/run-journal.ts +61 -0
- package/src/scheduling/expressions.ts +60 -0
- package/src/scheduling/pid-lock.ts +43 -0
- package/src/scheduling/scheduler.ts +147 -0
- package/src/todo-sync/adapter.ts +6 -0
- package/src/todo-sync/port.ts +2 -0
- package/src/tools/fleet-results.ts +35 -0
- package/src/tools/subagent.ts +58 -0
- package/src/vendor/cron-parser/NOTICE.md +23 -0
- package/src/vendor/cron-parser/lib/date.js +79 -0
- package/src/vendor/cron-parser/lib/expression.js +614 -0
- package/src/vendor/cron-parser/lib/number.js +8 -0
- package/src/vendor/cron-parser/lib/parser.js +103 -0
- package/src/vendor/cron-parser/types.d.ts +12 -0
- package/src/worktree/diff-service.ts +40 -0
- package/src/worktree/worktree-service.ts +92 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Type declarations for the vendored cron-parser lib (v1.1.1, CJS, dep-free).
|
|
2
|
+
declare module "../vendor/cron-parser/lib/parser.js" {
|
|
3
|
+
export interface CronExpressionIter {
|
|
4
|
+
next(): Date;
|
|
5
|
+
prev(): Date;
|
|
6
|
+
hasNext(): boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface ParseOptions { currentDate?: Date; endDate?: Date; iterator?: boolean; }
|
|
9
|
+
export function parseExpression(expression: string, options?: ParseOptions): CronExpressionIter;
|
|
10
|
+
export function parseString(entry: string): unknown;
|
|
11
|
+
export function parseFile(filePath: string): unknown;
|
|
12
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// src/worktree/diff-service.ts
|
|
2
|
+
// SPEC-5a §7 — worktree-diff artifact discovery for isolated runs (Q3=A).
|
|
3
|
+
// All changes in the worktree vs base: tracked modifications + untracked new files.
|
|
4
|
+
import { execSync } from "node:child_process";
|
|
5
|
+
|
|
6
|
+
export interface PhaseArtifacts {
|
|
7
|
+
paths: string[];
|
|
8
|
+
summary: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function sh(cmd: string, cwd: string): string {
|
|
12
|
+
return execSync(cmd, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).toString();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const MAX_SUMMARY = 200;
|
|
16
|
+
|
|
17
|
+
export class DiffService {
|
|
18
|
+
/**
|
|
19
|
+
* Compute a phase's artifacts = all changes in the worktree vs baseRef.
|
|
20
|
+
* Tracked modifications via `git diff --name-only`; untracked new files via
|
|
21
|
+
* `git status --porcelain` (?? entries). Deduped + sorted.
|
|
22
|
+
*
|
|
23
|
+
* @param childFinalText the child's final text, truncated to MAX_SUMMARY chars as the prose summary.
|
|
24
|
+
*/
|
|
25
|
+
diffPhase(worktreePath: string, baseRef: string, childFinalText = ""): PhaseArtifacts {
|
|
26
|
+
const tracked = sh(`git diff --name-only ${baseRef} --`, worktreePath)
|
|
27
|
+
.split("\n")
|
|
28
|
+
.filter(Boolean);
|
|
29
|
+
const status = sh("git status --porcelain", worktreePath);
|
|
30
|
+
const untracked = status
|
|
31
|
+
.split("\n")
|
|
32
|
+
.filter((l) => l.startsWith("?? "))
|
|
33
|
+
.map((l) => l.slice(3).trim());
|
|
34
|
+
const paths = Array.from(new Set([...tracked, ...untracked])).sort();
|
|
35
|
+
const summary = childFinalText.length > MAX_SUMMARY
|
|
36
|
+
? childFinalText.slice(0, MAX_SUMMARY - 1) + "…"
|
|
37
|
+
: childFinalText;
|
|
38
|
+
return { paths, summary };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// src/worktree/worktree-service.ts
|
|
2
|
+
// Greenfield git worktree lifecycle (SPEC-5a §6, Q9=A — thin shell-outs, no git library).
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
|
+
import { existsSync, mkdirSync, rmSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
|
|
7
|
+
export interface WorktreeRef {
|
|
8
|
+
path: string;
|
|
9
|
+
branch: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface WorktreeServiceOpts {
|
|
13
|
+
rootDir: string;
|
|
14
|
+
/** Where worktrees live. Defaults to <rootDir>/.pi/fleet/worktrees. */
|
|
15
|
+
worktreesDir?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function sh(cmd: string, cwd: string): string {
|
|
19
|
+
return execSync(cmd, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class WorktreeService {
|
|
23
|
+
private readonly rootDir: string;
|
|
24
|
+
private readonly worktreesDir: string;
|
|
25
|
+
|
|
26
|
+
constructor(opts: WorktreeServiceOpts) {
|
|
27
|
+
this.rootDir = opts.rootDir;
|
|
28
|
+
this.worktreesDir = opts.worktreesDir ?? join(opts.rootDir, ".pi", "fleet", "worktrees");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
branchFor(runId: string): string {
|
|
32
|
+
return `fleet/${runId}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
pathFor(runId: string): string {
|
|
36
|
+
return join(this.worktreesDir, runId);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
exists(runId: string): boolean {
|
|
40
|
+
return existsSync(this.pathFor(runId));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
create(runId: string, baseRef = "HEAD"): WorktreeRef {
|
|
44
|
+
if (this.exists(runId)) {
|
|
45
|
+
throw new Error(`worktree for run ${runId} already exists at ${this.pathFor(runId)}`);
|
|
46
|
+
}
|
|
47
|
+
mkdirSync(this.worktreesDir, { recursive: true });
|
|
48
|
+
const branch = this.branchFor(runId);
|
|
49
|
+
const path = this.pathFor(runId);
|
|
50
|
+
try {
|
|
51
|
+
sh(`git worktree add -b ${branch} ${path} ${baseRef}`, this.rootDir);
|
|
52
|
+
} catch (e) {
|
|
53
|
+
if (existsSync(path)) rmSync(path, { recursive: true, force: true });
|
|
54
|
+
const msg = (e as Error).message;
|
|
55
|
+
const tail = msg.split("\n").filter(Boolean).pop() ?? msg;
|
|
56
|
+
throw new Error(`worktree create failed for run ${runId} (base ${baseRef}): ${tail}`);
|
|
57
|
+
}
|
|
58
|
+
return { path, branch };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** SPEC-5a: remove the worktree dir but KEEP the branch (for completed runs the branch is
|
|
62
|
+
* kept for merge/inspection; only the worktree dir is temporary scaffolding). */
|
|
63
|
+
removeWorktree(runId: string): void {
|
|
64
|
+
const path = this.pathFor(runId);
|
|
65
|
+
if (existsSync(path)) {
|
|
66
|
+
try {
|
|
67
|
+
sh(`git worktree remove --force ${path}`, this.rootDir);
|
|
68
|
+
} catch {
|
|
69
|
+
rmSync(path, { recursive: true, force: true });
|
|
70
|
+
try { sh("git worktree prune", this.rootDir); } catch { /* ignore */ }
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
remove(runId: string): void {
|
|
76
|
+
const path = this.pathFor(runId);
|
|
77
|
+
const branch = this.branchFor(runId);
|
|
78
|
+
if (existsSync(path)) {
|
|
79
|
+
try {
|
|
80
|
+
sh(`git worktree remove --force ${path}`, this.rootDir);
|
|
81
|
+
} catch {
|
|
82
|
+
rmSync(path, { recursive: true, force: true });
|
|
83
|
+
try { sh("git worktree prune", this.rootDir); } catch { /* ignore */ }
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
sh(`git branch -D ${branch}`, this.rootDir);
|
|
88
|
+
} catch {
|
|
89
|
+
// branch may not exist; ignore
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|