ramstack 0.1.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/LICENSE +21 -0
- package/README.md +44 -0
- package/agents/comment-sicko.md +32 -0
- package/agents/poteto-agent.md +9 -0
- package/cli/rstack.mjs +539 -0
- package/package.json +29 -0
- package/skills/architect/README.md +25 -0
- package/skills/architect/SKILL.md +83 -0
- package/skills/architect/agents/openai.yaml +5 -0
- package/skills/architect/references/design-red-flags.md +33 -0
- package/skills/architect/references/rationale-template.md +35 -0
- package/skills/architect/references/runner-prompt.md +20 -0
- package/skills/arena/README.md +21 -0
- package/skills/arena/SKILL.md +71 -0
- package/skills/arena/agents/openai.yaml +5 -0
- package/skills/automate-me/README.md +21 -0
- package/skills/automate-me/SKILL.md +109 -0
- package/skills/automate-me/agents/openai.yaml +5 -0
- package/skills/blast-radius/README.md +21 -0
- package/skills/blast-radius/SKILL.md +50 -0
- package/skills/blast-radius/agents/openai.yaml +5 -0
- package/skills/bro/README.md +16 -0
- package/skills/bro/SKILL.md +7 -0
- package/skills/bro/agents/openai.yaml +5 -0
- package/skills/create-verification-skill/README.md +22 -0
- package/skills/create-verification-skill/SKILL.md +44 -0
- package/skills/create-verification-skill/agents/openai.yaml +5 -0
- package/skills/create-verification-skill/references/feature-map-example/README.md +47 -0
- package/skills/create-verification-skill/references/feature-map-example/create-note.md +39 -0
- package/skills/create-verification-skill/references/feature-map-example/search.md +45 -0
- package/skills/figure-it-out/README.md +22 -0
- package/skills/figure-it-out/SKILL.md +55 -0
- package/skills/figure-it-out/agents/openai.yaml +5 -0
- package/skills/how/README.md +25 -0
- package/skills/how/SKILL.md +134 -0
- package/skills/how/agents/openai.yaml +3 -0
- package/skills/how/references/critic-prompt.md +59 -0
- package/skills/how/references/critique-rubric.md +58 -0
- package/skills/how/references/explainer-prompt.md +55 -0
- package/skills/how/references/explorer-prompt.md +52 -0
- package/skills/interrogate/README.md +21 -0
- package/skills/interrogate/SKILL.md +112 -0
- package/skills/interrogate/agents/openai.yaml +5 -0
- package/skills/interrogate/references/code-quality-review.md +47 -0
- package/skills/interrogate/references/lead-judgment.md +58 -0
- package/skills/interrogate/references/reviewer-prompt.md +72 -0
- package/skills/interrogate/references/rubric.md +77 -0
- package/skills/maintain-verification-skill/README.md +21 -0
- package/skills/maintain-verification-skill/SKILL.md +39 -0
- package/skills/maintain-verification-skill/agents/openai.yaml +5 -0
- package/skills/no-comments/README.md +22 -0
- package/skills/no-comments/SKILL.md +24 -0
- package/skills/no-comments/agents/openai.yaml +5 -0
- package/skills/poteto-mode/README.md +26 -0
- package/skills/poteto-mode/SKILL.md +140 -0
- package/skills/poteto-mode/agents/openai.yaml +5 -0
- package/skills/poteto-mode/playbooks/authoring-a-skill.md +13 -0
- package/skills/poteto-mode/playbooks/autonomous-run.md +13 -0
- package/skills/poteto-mode/playbooks/autopilot-full.md +13 -0
- package/skills/poteto-mode/playbooks/autopilot-stack.md +16 -0
- package/skills/poteto-mode/playbooks/babysit.md +27 -0
- package/skills/poteto-mode/playbooks/bug-fix.md +17 -0
- package/skills/poteto-mode/playbooks/eval.md +27 -0
- package/skills/poteto-mode/playbooks/feature.md +21 -0
- package/skills/poteto-mode/playbooks/hillclimb.md +21 -0
- package/skills/poteto-mode/playbooks/investigation.md +14 -0
- package/skills/poteto-mode/playbooks/multi-phase-plan.md +3 -0
- package/skills/poteto-mode/playbooks/opening-a-pr.md +11 -0
- package/skills/poteto-mode/playbooks/orchestrate.md +113 -0
- package/skills/poteto-mode/playbooks/pause-safely.md +10 -0
- package/skills/poteto-mode/playbooks/perf-issue.md +24 -0
- package/skills/poteto-mode/playbooks/prototype.md +14 -0
- package/skills/poteto-mode/playbooks/refactoring.md +16 -0
- package/skills/poteto-mode/playbooks/runtime-forensics.md +11 -0
- package/skills/poteto-mode/playbooks/session-pickup.md +13 -0
- package/skills/poteto-mode/playbooks/shipping.md +20 -0
- package/skills/poteto-mode/playbooks/trace-forensics.md +14 -0
- package/skills/poteto-mode/playbooks/visual-parity.md +11 -0
- package/skills/poteto-mode/playbooks/worktree-cleanup.md +14 -0
- package/skills/poteto-mode/references/bugbot-triage.md +142 -0
- package/skills/poteto-mode/references/plan.md +105 -0
- package/skills/poteto-mode/scripts/bootstrap.ts +62 -0
- package/skills/poteto-mode/scripts/bun.lock +67 -0
- package/skills/poteto-mode/scripts/orch/orch.test.ts +634 -0
- package/skills/poteto-mode/scripts/orch/orch.ts +578 -0
- package/skills/poteto-mode/scripts/orch/store.ts +1607 -0
- package/skills/poteto-mode/scripts/package.json +16 -0
- package/skills/poteto-mode/scripts/watch-pr/cli.test.ts +224 -0
- package/skills/poteto-mode/scripts/watch-pr/cli.ts +223 -0
- package/skills/poteto-mode/scripts/watch-pr/fakes.test-helper.ts +118 -0
- package/skills/poteto-mode/scripts/watch-pr/github.test.ts +306 -0
- package/skills/poteto-mode/scripts/watch-pr/github.ts +699 -0
- package/skills/poteto-mode/scripts/watch-pr/policy.test.ts +420 -0
- package/skills/poteto-mode/scripts/watch-pr/policy.ts +832 -0
- package/skills/poteto-mode/scripts/watch-pr/render.ts +169 -0
- package/skills/poteto-mode/scripts/watch-pr/tsconfig.json +13 -0
- package/skills/poteto-mode/scripts/watch-pr/types.compile.ts +93 -0
- package/skills/poteto-mode/scripts/watch-pr/types.ts +401 -0
- package/skills/poteto-mode/scripts/watch-pr/watch-pr +6 -0
- package/skills/poteto-mode/scripts/worktree-audit.sh +86 -0
- package/skills/principle-boundary-discipline/README.md +17 -0
- package/skills/principle-boundary-discipline/SKILL.md +34 -0
- package/skills/principle-boundary-discipline/agents/openai.yaml +5 -0
- package/skills/principle-build-the-lever/README.md +17 -0
- package/skills/principle-build-the-lever/SKILL.md +23 -0
- package/skills/principle-build-the-lever/agents/openai.yaml +5 -0
- package/skills/principle-encode-lessons-in-structure/README.md +17 -0
- package/skills/principle-encode-lessons-in-structure/SKILL.md +31 -0
- package/skills/principle-encode-lessons-in-structure/agents/openai.yaml +5 -0
- package/skills/principle-exhaust-the-design-space/README.md +17 -0
- package/skills/principle-exhaust-the-design-space/SKILL.md +21 -0
- package/skills/principle-exhaust-the-design-space/agents/openai.yaml +5 -0
- package/skills/principle-experience-first/README.md +17 -0
- package/skills/principle-experience-first/SKILL.md +19 -0
- package/skills/principle-experience-first/agents/openai.yaml +5 -0
- package/skills/principle-fix-root-causes/README.md +17 -0
- package/skills/principle-fix-root-causes/SKILL.md +23 -0
- package/skills/principle-fix-root-causes/agents/openai.yaml +5 -0
- package/skills/principle-foundational-thinking/README.md +17 -0
- package/skills/principle-foundational-thinking/SKILL.md +21 -0
- package/skills/principle-foundational-thinking/agents/openai.yaml +5 -0
- package/skills/principle-guard-the-context-window/README.md +17 -0
- package/skills/principle-guard-the-context-window/SKILL.md +17 -0
- package/skills/principle-guard-the-context-window/agents/openai.yaml +5 -0
- package/skills/principle-laziness-protocol/README.md +17 -0
- package/skills/principle-laziness-protocol/SKILL.md +18 -0
- package/skills/principle-laziness-protocol/agents/openai.yaml +5 -0
- package/skills/principle-make-operations-idempotent/README.md +17 -0
- package/skills/principle-make-operations-idempotent/SKILL.md +24 -0
- package/skills/principle-make-operations-idempotent/agents/openai.yaml +5 -0
- package/skills/principle-migrate-callers-then-delete-legacy-apis/README.md +17 -0
- package/skills/principle-migrate-callers-then-delete-legacy-apis/SKILL.md +22 -0
- package/skills/principle-migrate-callers-then-delete-legacy-apis/agents/openai.yaml +5 -0
- package/skills/principle-minimize-reader-load/README.md +17 -0
- package/skills/principle-minimize-reader-load/SKILL.md +23 -0
- package/skills/principle-minimize-reader-load/agents/openai.yaml +5 -0
- package/skills/principle-model-the-domain/README.md +17 -0
- package/skills/principle-model-the-domain/SKILL.md +26 -0
- package/skills/principle-model-the-domain/agents/openai.yaml +5 -0
- package/skills/principle-never-block-on-the-human/README.md +16 -0
- package/skills/principle-never-block-on-the-human/SKILL.md +23 -0
- package/skills/principle-never-block-on-the-human/agents/openai.yaml +5 -0
- package/skills/principle-outcome-oriented-execution/README.md +16 -0
- package/skills/principle-outcome-oriented-execution/SKILL.md +22 -0
- package/skills/principle-outcome-oriented-execution/agents/openai.yaml +5 -0
- package/skills/principle-prove-it-works/README.md +18 -0
- package/skills/principle-prove-it-works/SKILL.md +33 -0
- package/skills/principle-prove-it-works/agents/openai.yaml +5 -0
- package/skills/principle-redesign-from-first-principles/README.md +16 -0
- package/skills/principle-redesign-from-first-principles/SKILL.md +16 -0
- package/skills/principle-redesign-from-first-principles/agents/openai.yaml +5 -0
- package/skills/principle-separate-before-serializing-shared-state/README.md +16 -0
- package/skills/principle-separate-before-serializing-shared-state/SKILL.md +16 -0
- package/skills/principle-separate-before-serializing-shared-state/agents/openai.yaml +5 -0
- package/skills/principle-sequence-verifiable-units/README.md +18 -0
- package/skills/principle-sequence-verifiable-units/SKILL.md +22 -0
- package/skills/principle-sequence-verifiable-units/agents/openai.yaml +5 -0
- package/skills/principle-subtract-before-you-add/README.md +17 -0
- package/skills/principle-subtract-before-you-add/SKILL.md +22 -0
- package/skills/principle-subtract-before-you-add/agents/openai.yaml +5 -0
- package/skills/principle-type-system-discipline/README.md +19 -0
- package/skills/principle-type-system-discipline/SKILL.md +31 -0
- package/skills/principle-type-system-discipline/agents/openai.yaml +5 -0
- package/skills/recall/README.md +23 -0
- package/skills/recall/SKILL.md +35 -0
- package/skills/recall/agents/openai.yaml +5 -0
- package/skills/reflect/README.md +24 -0
- package/skills/reflect/SKILL.md +77 -0
- package/skills/reflect/agents/openai.yaml +5 -0
- package/skills/reflect/references/divergent-reviewer.md +43 -0
- package/skills/reflect/references/judgment-reviewer.md +42 -0
- package/skills/reflect/references/synthesizer.md +56 -0
- package/skills/reflect/references/tooling-reviewer.md +57 -0
- package/skills/setup-rstack/README.md +25 -0
- package/skills/setup-rstack/SKILL.md +81 -0
- package/skills/setup-rstack/agents/openai.yaml +3 -0
- package/skills/show-me-your-work/README.md +23 -0
- package/skills/show-me-your-work/SKILL.md +82 -0
- package/skills/show-me-your-work/agents/openai.yaml +5 -0
- package/skills/show-me-your-work/references/decision-log-template.tsv +1 -0
- package/skills/show-me-your-work/scripts/log.sh +40 -0
- package/skills/swarm/README.md +21 -0
- package/skills/swarm/SKILL.md +46 -0
- package/skills/swarm/agents/openai.yaml +5 -0
- package/skills/tdd/README.md +21 -0
- package/skills/tdd/SKILL.md +44 -0
- package/skills/tdd/agents/openai.yaml +5 -0
- package/skills/teach/README.md +22 -0
- package/skills/teach/SKILL.md +21 -0
- package/skills/teach/agents/openai.yaml +5 -0
- package/skills/technical-writing/README.md +23 -0
- package/skills/technical-writing/SKILL.md +130 -0
- package/skills/technical-writing/agents/openai.yaml +5 -0
- package/skills/typescript-best-practices/README.md +22 -0
- package/skills/typescript-best-practices/SKILL.md +28 -0
- package/skills/typescript-best-practices/agents/openai.yaml +3 -0
- package/skills/typescript-best-practices/references/patterns.md +292 -0
- package/skills/unslop/README.md +21 -0
- package/skills/unslop/SKILL.md +80 -0
- package/skills/unslop/agents/openai.yaml +3 -0
- package/skills/why/README.md +26 -0
- package/skills/why/SKILL.md +229 -0
- package/skills/why/agents/openai.yaml +3 -0
- package/skills/why/references/epistemics.md +144 -0
- package/skills/why/references/investigator-prompt.md +103 -0
- package/skills/why/references/source-playbook.md +17 -0
- package/skills/why/references/sources/code-archaeology.md +88 -0
- package/skills/why/references/sources/databricks.md +70 -0
- package/skills/why/references/sources/datadog.md +99 -0
- package/skills/why/references/sources/incident-postmortem.md +15 -0
- package/skills/why/references/sources/linear.md +48 -0
- package/skills/why/references/sources/notion.md +55 -0
- package/skills/why/references/sources/sentry.md +100 -0
- package/skills/why/references/sources/slack.md +54 -0
- package/skills/why/references/synthesizer-prompt.md +135 -0
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { ensureDependenciesInstalled } from "../bootstrap.ts";
|
|
4
|
+
import {
|
|
5
|
+
NotFoundError,
|
|
6
|
+
UsageError,
|
|
7
|
+
openStore,
|
|
8
|
+
parseVerdict,
|
|
9
|
+
type Counts,
|
|
10
|
+
type Frontier,
|
|
11
|
+
type InboxPointer,
|
|
12
|
+
type OpenGate,
|
|
13
|
+
type StandingLine,
|
|
14
|
+
type StatusReport,
|
|
15
|
+
type Store,
|
|
16
|
+
type Unit,
|
|
17
|
+
type Verdict,
|
|
18
|
+
} from "./store.ts";
|
|
19
|
+
|
|
20
|
+
ensureDependenciesInstalled();
|
|
21
|
+
const {
|
|
22
|
+
Command: CommanderCommand,
|
|
23
|
+
CommanderError,
|
|
24
|
+
InvalidArgumentError,
|
|
25
|
+
Option,
|
|
26
|
+
} = await import("commander");
|
|
27
|
+
type Command = InstanceType<typeof CommanderCommand>;
|
|
28
|
+
|
|
29
|
+
const DISPLAY_LIMIT = 4;
|
|
30
|
+
|
|
31
|
+
interface Io {
|
|
32
|
+
readonly stdout: (value: string) => void;
|
|
33
|
+
readonly stderr: (value: string) => void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface GlobalOptions {
|
|
37
|
+
readonly store?: string;
|
|
38
|
+
readonly json: boolean;
|
|
39
|
+
readonly force: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface UnitAddOptions {
|
|
43
|
+
readonly track: string;
|
|
44
|
+
readonly brief?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface UnitSetOptions {
|
|
48
|
+
readonly state: string;
|
|
49
|
+
readonly branch?: string;
|
|
50
|
+
readonly pr?: number;
|
|
51
|
+
readonly sha?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface UnitListOptions {
|
|
55
|
+
readonly state?: string;
|
|
56
|
+
readonly track?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface LedgerRecordOptions {
|
|
60
|
+
readonly evidence: string;
|
|
61
|
+
readonly verifier?: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface InboxPushOptions {
|
|
65
|
+
readonly report?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
interface InboxDrainOptions {
|
|
69
|
+
readonly peek: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface GateParkOptions {
|
|
73
|
+
readonly question: string;
|
|
74
|
+
readonly options: string;
|
|
75
|
+
readonly default: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface GateResolveOptions {
|
|
79
|
+
readonly answer: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
interface FrontierSetOptions {
|
|
83
|
+
readonly repo?: string;
|
|
84
|
+
readonly prs?: readonly number[];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function message(error: unknown): string {
|
|
88
|
+
return error instanceof Error ? error.message : String(error);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function positiveInteger(value: string): number {
|
|
92
|
+
const parsed = Number(value);
|
|
93
|
+
if (!/^[1-9]\d*$/.test(value) || !Number.isSafeInteger(parsed)) {
|
|
94
|
+
throw new InvalidArgumentError("must be a positive integer");
|
|
95
|
+
}
|
|
96
|
+
return parsed;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function prList(value: string): readonly number[] {
|
|
100
|
+
const parts = value.split(",");
|
|
101
|
+
if (parts.some((part) => part.length === 0)) {
|
|
102
|
+
throw new InvalidArgumentError("requires a comma-separated PR list");
|
|
103
|
+
}
|
|
104
|
+
return parts.map(positiveInteger);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function countLine(value: Counts): string {
|
|
108
|
+
const entries = Object.entries(value);
|
|
109
|
+
return entries.length === 0
|
|
110
|
+
? "none"
|
|
111
|
+
: entries.map(([name, count]) => `${name}=${count}`).join(", ");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function unitLine(unit: Unit): string {
|
|
115
|
+
return [
|
|
116
|
+
unit.id,
|
|
117
|
+
unit.track,
|
|
118
|
+
unit.state,
|
|
119
|
+
unit.branch,
|
|
120
|
+
unit.pr,
|
|
121
|
+
unit.sha,
|
|
122
|
+
unit.brief,
|
|
123
|
+
].join("\t");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function pointerLine(pointer: InboxPointer): string {
|
|
127
|
+
return [
|
|
128
|
+
pointer.ts,
|
|
129
|
+
pointer.agent,
|
|
130
|
+
pointer.unit,
|
|
131
|
+
pointer.status,
|
|
132
|
+
pointer.report,
|
|
133
|
+
].join("\t");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function gateLine(gate: OpenGate): string {
|
|
137
|
+
return [
|
|
138
|
+
gate.id,
|
|
139
|
+
gate.question,
|
|
140
|
+
gate.options,
|
|
141
|
+
gate.defaultAnswer,
|
|
142
|
+
].join("\t");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function compactRows<T>(
|
|
146
|
+
rows: readonly T[],
|
|
147
|
+
format: (row: T) => string,
|
|
148
|
+
empty: string,
|
|
149
|
+
limit: number | null = DISPLAY_LIMIT
|
|
150
|
+
): string {
|
|
151
|
+
if (rows.length === 0) {
|
|
152
|
+
return empty;
|
|
153
|
+
}
|
|
154
|
+
const visible = limit === null ? rows : rows.slice(0, limit);
|
|
155
|
+
const lines = visible.map(format);
|
|
156
|
+
if (limit !== null && rows.length > limit) {
|
|
157
|
+
lines.push(`... ${rows.length - limit} more; use --json`);
|
|
158
|
+
}
|
|
159
|
+
return lines.join("\n");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function frontierLine(value: Frontier): string {
|
|
163
|
+
const prs =
|
|
164
|
+
value.prs.length === 0
|
|
165
|
+
? "none"
|
|
166
|
+
: value.prs
|
|
167
|
+
.map(
|
|
168
|
+
(row) =>
|
|
169
|
+
`${row.branches}#${row.pr}@${row.sha}:${row.state}`
|
|
170
|
+
)
|
|
171
|
+
.join(",");
|
|
172
|
+
return `generation=${value.generation} prs=${prs} lowest-unmerged=${value.lowestUnmerged ?? "none"}`;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function statusLines(report: StatusReport): string {
|
|
176
|
+
const visible = report.summary.openGateIds.slice(0, DISPLAY_LIMIT);
|
|
177
|
+
const more =
|
|
178
|
+
report.summary.openGateIds.length > DISPLAY_LIMIT
|
|
179
|
+
? `,+${report.summary.openGateIds.length - DISPLAY_LIMIT} more`
|
|
180
|
+
: "";
|
|
181
|
+
return [
|
|
182
|
+
`counts: units=${report.units.length}; states=${countLine(report.summary.unitStates)}; ledger=${countLine(report.summary.ledgerVerdicts)}`,
|
|
183
|
+
`changed: ${report.changed}`,
|
|
184
|
+
`gates open: ${report.summary.openGateIds.length}${
|
|
185
|
+
visible.length > 0 ? `; ids=${visible.join(",")}${more}` : ""
|
|
186
|
+
}`,
|
|
187
|
+
].join("\n");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function emit<T>(
|
|
191
|
+
io: Io,
|
|
192
|
+
json: boolean,
|
|
193
|
+
value: T,
|
|
194
|
+
compact: (result: T) => string,
|
|
195
|
+
jsonValue: (result: T) => unknown = (result) => result
|
|
196
|
+
): void {
|
|
197
|
+
const rendered = json
|
|
198
|
+
? JSON.stringify(jsonValue(value), null, 2)
|
|
199
|
+
: compact(value);
|
|
200
|
+
io.stdout(rendered.endsWith("\n") ? rendered : `${rendered}\n`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function storeDirectory(program: Command): string {
|
|
204
|
+
const value = program.opts<GlobalOptions>().store;
|
|
205
|
+
if (value === undefined || value.trim().length === 0) {
|
|
206
|
+
throw new UsageError("set --store <dir> or ORCH_STORE");
|
|
207
|
+
}
|
|
208
|
+
return value;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function frontierRepo(options: FrontierSetOptions): string {
|
|
212
|
+
const value = options.repo;
|
|
213
|
+
if (value === undefined || value.trim().length === 0) {
|
|
214
|
+
throw new UsageError("set --repo <dir> or ORCH_REPO");
|
|
215
|
+
}
|
|
216
|
+
return value;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function runStore<T>(
|
|
220
|
+
program: Command,
|
|
221
|
+
io: Io,
|
|
222
|
+
operation: (store: Store) => Promise<T>,
|
|
223
|
+
compact: (result: T) => string,
|
|
224
|
+
jsonValue?: (result: T) => unknown
|
|
225
|
+
): Promise<void> {
|
|
226
|
+
const options = program.opts<GlobalOptions>();
|
|
227
|
+
const store = openStore(storeDirectory(program), {
|
|
228
|
+
force: options.force,
|
|
229
|
+
onLockStolen: (holder) =>
|
|
230
|
+
io.stderr(`stealing store lock held by pid ${holder}\n`),
|
|
231
|
+
onStaleLock: (holder) =>
|
|
232
|
+
io.stderr(`replacing stale store lock (pid ${holder} is dead)\n`),
|
|
233
|
+
});
|
|
234
|
+
try {
|
|
235
|
+
const result = await operation(store);
|
|
236
|
+
emit(io, options.json, result, compact, jsonValue);
|
|
237
|
+
} finally {
|
|
238
|
+
await store.close();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function leaf(parent: Command, name: string, description: string): Command {
|
|
243
|
+
return parent
|
|
244
|
+
.command(name)
|
|
245
|
+
.description(description)
|
|
246
|
+
.allowExcessArguments(false);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function requireSubcommand(program: Command): never {
|
|
250
|
+
storeDirectory(program);
|
|
251
|
+
throw new UsageError("a valid command is required");
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function createProgram(io: Io): Command {
|
|
255
|
+
const program = new CommanderCommand("orch")
|
|
256
|
+
.description("Plain-file orchestrate bookkeeping")
|
|
257
|
+
.usage("[--store <dir>] [--json] [--force] <command>")
|
|
258
|
+
.configureOutput({ writeOut: io.stdout, writeErr: io.stderr })
|
|
259
|
+
.exitOverride()
|
|
260
|
+
.showHelpAfterError()
|
|
261
|
+
.allowExcessArguments(false)
|
|
262
|
+
.addOption(
|
|
263
|
+
new Option("--store <dir>", "store directory (or ORCH_STORE)").env(
|
|
264
|
+
"ORCH_STORE"
|
|
265
|
+
)
|
|
266
|
+
)
|
|
267
|
+
.option("--json", "print complete rows as JSON", false)
|
|
268
|
+
.option("--force", "steal an existing store lock", false);
|
|
269
|
+
|
|
270
|
+
leaf(program, "init", "initialize the store").action(() =>
|
|
271
|
+
runStore(
|
|
272
|
+
program,
|
|
273
|
+
io,
|
|
274
|
+
(store) => store.init(),
|
|
275
|
+
(result) => `initialized ${result.store}`
|
|
276
|
+
)
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
const unit = program
|
|
280
|
+
.command("unit")
|
|
281
|
+
.description("manage work units")
|
|
282
|
+
.action(() => requireSubcommand(program));
|
|
283
|
+
leaf(unit, "add <id>", "add a unit")
|
|
284
|
+
.requiredOption("--track <track>", "unit track")
|
|
285
|
+
.option("--brief <path>", "brief path")
|
|
286
|
+
.action((id: string, options: UnitAddOptions) =>
|
|
287
|
+
runStore(
|
|
288
|
+
program,
|
|
289
|
+
io,
|
|
290
|
+
(store) =>
|
|
291
|
+
store.units.add({
|
|
292
|
+
id,
|
|
293
|
+
track: options.track,
|
|
294
|
+
brief: options.brief,
|
|
295
|
+
}),
|
|
296
|
+
unitLine
|
|
297
|
+
)
|
|
298
|
+
);
|
|
299
|
+
leaf(unit, "set <id>", "update a unit")
|
|
300
|
+
.requiredOption("--state <state>", "unit state")
|
|
301
|
+
.option("--branch <branch>", "branch name")
|
|
302
|
+
.option("--pr <number>", "pull request number", positiveInteger)
|
|
303
|
+
.option("--sha <sha>", "commit SHA")
|
|
304
|
+
.action((id: string, options: UnitSetOptions) =>
|
|
305
|
+
runStore(
|
|
306
|
+
program,
|
|
307
|
+
io,
|
|
308
|
+
(store) =>
|
|
309
|
+
store.units.set({
|
|
310
|
+
id,
|
|
311
|
+
state: options.state,
|
|
312
|
+
branch: options.branch,
|
|
313
|
+
pr: options.pr,
|
|
314
|
+
sha: options.sha,
|
|
315
|
+
}),
|
|
316
|
+
unitLine
|
|
317
|
+
)
|
|
318
|
+
);
|
|
319
|
+
leaf(unit, "get <id>", "get a unit").action((id: string) =>
|
|
320
|
+
runStore(program, io, (store) => store.units.get(id), unitLine)
|
|
321
|
+
);
|
|
322
|
+
leaf(unit, "list", "list units")
|
|
323
|
+
.option("--state <state>", "filter by state")
|
|
324
|
+
.option("--track <track>", "filter by track")
|
|
325
|
+
.action((options: UnitListOptions) =>
|
|
326
|
+
runStore(
|
|
327
|
+
program,
|
|
328
|
+
io,
|
|
329
|
+
(store) => store.units.list(options),
|
|
330
|
+
(rows) => compactRows(rows, unitLine, "(no units)")
|
|
331
|
+
)
|
|
332
|
+
);
|
|
333
|
+
leaf(unit, "counts", "count units by state").action(() =>
|
|
334
|
+
runStore(program, io, (store) => store.units.counts(), countLine)
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
const ledger = program
|
|
338
|
+
.command("ledger")
|
|
339
|
+
.description("manage verification records")
|
|
340
|
+
.action(() => requireSubcommand(program));
|
|
341
|
+
leaf(ledger, "record", "record a verification verdict")
|
|
342
|
+
.argument("<pr>", "pull request number", positiveInteger)
|
|
343
|
+
.argument("<sha>", "commit SHA")
|
|
344
|
+
.argument("<verdict>", "verification verdict", parseVerdict)
|
|
345
|
+
.requiredOption("--evidence <path>", "evidence path")
|
|
346
|
+
.option("--verifier <name>", "verifier name")
|
|
347
|
+
.action(
|
|
348
|
+
(
|
|
349
|
+
pr: number,
|
|
350
|
+
sha: string,
|
|
351
|
+
verdict: Verdict,
|
|
352
|
+
options: LedgerRecordOptions
|
|
353
|
+
) =>
|
|
354
|
+
runStore(
|
|
355
|
+
program,
|
|
356
|
+
io,
|
|
357
|
+
(store) =>
|
|
358
|
+
store.ledger.record({
|
|
359
|
+
pr,
|
|
360
|
+
sha,
|
|
361
|
+
verdict,
|
|
362
|
+
evidence: options.evidence,
|
|
363
|
+
verifier: options.verifier,
|
|
364
|
+
}),
|
|
365
|
+
(row) => `${row.pr}\t${row.sha}\t${row.verdict}`
|
|
366
|
+
)
|
|
367
|
+
);
|
|
368
|
+
leaf(ledger, "check", "check a verification verdict")
|
|
369
|
+
.argument("<pr>", "pull request number", positiveInteger)
|
|
370
|
+
.argument("<sha>", "commit SHA")
|
|
371
|
+
.action((pr: number, sha: string) =>
|
|
372
|
+
runStore(
|
|
373
|
+
program,
|
|
374
|
+
io,
|
|
375
|
+
(store) => store.ledger.check({ pr, sha }),
|
|
376
|
+
(row) => row.verdict
|
|
377
|
+
)
|
|
378
|
+
);
|
|
379
|
+
leaf(ledger, "summary", "count verification verdicts").action(() =>
|
|
380
|
+
runStore(program, io, (store) => store.ledger.summary(), countLine)
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
const inbox = program
|
|
384
|
+
.command("inbox")
|
|
385
|
+
.description("manage agent pointers")
|
|
386
|
+
.action(() => requireSubcommand(program));
|
|
387
|
+
leaf(inbox, "push <agent> <unit> <status>", "push an inbox pointer")
|
|
388
|
+
.option("--report <path>", "report path")
|
|
389
|
+
.action(
|
|
390
|
+
(
|
|
391
|
+
agent: string,
|
|
392
|
+
unitId: string,
|
|
393
|
+
status: string,
|
|
394
|
+
options: InboxPushOptions
|
|
395
|
+
) =>
|
|
396
|
+
runStore(
|
|
397
|
+
program,
|
|
398
|
+
io,
|
|
399
|
+
(store) =>
|
|
400
|
+
store.inbox.push({
|
|
401
|
+
agent,
|
|
402
|
+
unit: unitId,
|
|
403
|
+
status,
|
|
404
|
+
report: options.report,
|
|
405
|
+
}),
|
|
406
|
+
(result) =>
|
|
407
|
+
`${result.pointer.unit}\t${result.pointer.status}\t${result.filename}`,
|
|
408
|
+
(result) => result.pointer
|
|
409
|
+
)
|
|
410
|
+
);
|
|
411
|
+
leaf(inbox, "drain", "drain inbox pointers")
|
|
412
|
+
.option("--peek", "read without draining", false)
|
|
413
|
+
.action((options: InboxDrainOptions) =>
|
|
414
|
+
runStore(
|
|
415
|
+
program,
|
|
416
|
+
io,
|
|
417
|
+
(store) =>
|
|
418
|
+
options.peek ? store.inbox.peek() : store.inbox.drain(),
|
|
419
|
+
(rows) => compactRows(rows, pointerLine, "(empty)", null)
|
|
420
|
+
)
|
|
421
|
+
);
|
|
422
|
+
leaf(inbox, "count", "count inbox pointers").action(() =>
|
|
423
|
+
runStore(
|
|
424
|
+
program,
|
|
425
|
+
io,
|
|
426
|
+
(store) => store.inbox.count(),
|
|
427
|
+
String,
|
|
428
|
+
(count) => ({ count })
|
|
429
|
+
)
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
const gate = program
|
|
433
|
+
.command("gate")
|
|
434
|
+
.description("manage decision gates")
|
|
435
|
+
.action(() => requireSubcommand(program));
|
|
436
|
+
leaf(gate, "park <id>", "park a decision gate")
|
|
437
|
+
.requiredOption("--question <question>", "gate question")
|
|
438
|
+
.requiredOption("--options <options>", "gate options")
|
|
439
|
+
.requiredOption("--default <answer>", "default answer")
|
|
440
|
+
.action((id: string, options: GateParkOptions) =>
|
|
441
|
+
runStore(
|
|
442
|
+
program,
|
|
443
|
+
io,
|
|
444
|
+
(store) =>
|
|
445
|
+
store.gates.park({
|
|
446
|
+
id,
|
|
447
|
+
question: options.question,
|
|
448
|
+
options: options.options,
|
|
449
|
+
defaultAnswer: options.default,
|
|
450
|
+
}),
|
|
451
|
+
(result) => `${result.id}\topen`
|
|
452
|
+
)
|
|
453
|
+
);
|
|
454
|
+
leaf(gate, "list", "list open decision gates").action(() =>
|
|
455
|
+
runStore(
|
|
456
|
+
program,
|
|
457
|
+
io,
|
|
458
|
+
(store) => store.gates.list(),
|
|
459
|
+
(rows) => compactRows(rows, gateLine, "(no open gates)")
|
|
460
|
+
)
|
|
461
|
+
);
|
|
462
|
+
leaf(gate, "resolve <id>", "resolve a decision gate")
|
|
463
|
+
.requiredOption("--answer <answer>", "chosen answer")
|
|
464
|
+
.action((id: string, options: GateResolveOptions) =>
|
|
465
|
+
runStore(
|
|
466
|
+
program,
|
|
467
|
+
io,
|
|
468
|
+
(store) => store.gates.resolve({ id, answer: options.answer }),
|
|
469
|
+
(result) => `${result.id}\tresolved\t${result.answer}`
|
|
470
|
+
)
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
const frontier = program
|
|
474
|
+
.command("frontier")
|
|
475
|
+
.description("manage the Graphite stack frontier")
|
|
476
|
+
.action(() => requireSubcommand(program));
|
|
477
|
+
leaf(frontier, "set", "discover the Graphite stack and set the frontier")
|
|
478
|
+
.addOption(
|
|
479
|
+
new Option(
|
|
480
|
+
"--repo <dir>",
|
|
481
|
+
"repository directory (or ORCH_REPO)"
|
|
482
|
+
).env("ORCH_REPO")
|
|
483
|
+
)
|
|
484
|
+
.option(
|
|
485
|
+
"--prs <n,...>",
|
|
486
|
+
"optional expected pull request order pin",
|
|
487
|
+
prList
|
|
488
|
+
)
|
|
489
|
+
.action((options: FrontierSetOptions) =>
|
|
490
|
+
runStore(
|
|
491
|
+
program,
|
|
492
|
+
io,
|
|
493
|
+
(store) =>
|
|
494
|
+
store.frontier.set({
|
|
495
|
+
repo: frontierRepo(options),
|
|
496
|
+
prs: options.prs,
|
|
497
|
+
}),
|
|
498
|
+
frontierLine
|
|
499
|
+
)
|
|
500
|
+
);
|
|
501
|
+
leaf(frontier, "show", "show the frontier").action(() =>
|
|
502
|
+
runStore(program, io, (store) => store.frontier.show(), frontierLine)
|
|
503
|
+
);
|
|
504
|
+
|
|
505
|
+
leaf(program, "status", "render status.md and print a summary").action(() =>
|
|
506
|
+
runStore(program, io, (store) => store.status.render(), statusLines)
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
const standing = program
|
|
510
|
+
.command("standing")
|
|
511
|
+
.description("manage standing orders")
|
|
512
|
+
.action(() => requireSubcommand(program));
|
|
513
|
+
leaf(standing, "show", "show standing orders").action(() =>
|
|
514
|
+
runStore(
|
|
515
|
+
program,
|
|
516
|
+
io,
|
|
517
|
+
(store) => store.standing.show(),
|
|
518
|
+
(rows) =>
|
|
519
|
+
compactRows(
|
|
520
|
+
rows,
|
|
521
|
+
(item: StandingLine) => `${item.number}. ${item.line}`,
|
|
522
|
+
"(no standing orders)"
|
|
523
|
+
)
|
|
524
|
+
)
|
|
525
|
+
);
|
|
526
|
+
leaf(standing, "add <line>", "add a standing order").action((line: string) =>
|
|
527
|
+
runStore(
|
|
528
|
+
program,
|
|
529
|
+
io,
|
|
530
|
+
(store) => store.standing.add({ line }),
|
|
531
|
+
(item) => `${item.number}. ${item.line}`
|
|
532
|
+
)
|
|
533
|
+
);
|
|
534
|
+
|
|
535
|
+
program.action(() => requireSubcommand(program));
|
|
536
|
+
return program;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function handleError(error: unknown, program: Command, io: Io): number {
|
|
540
|
+
if (error instanceof CommanderError) {
|
|
541
|
+
return error.exitCode === 0 ? 0 : 1;
|
|
542
|
+
}
|
|
543
|
+
const json = program.opts<GlobalOptions>().json;
|
|
544
|
+
if (error instanceof NotFoundError) {
|
|
545
|
+
const output = error.output;
|
|
546
|
+
if (output === undefined) {
|
|
547
|
+
io.stderr(`error: ${error.message}\n`);
|
|
548
|
+
} else {
|
|
549
|
+
emit(io, json, output.json, () => output.compact);
|
|
550
|
+
}
|
|
551
|
+
return 2;
|
|
552
|
+
}
|
|
553
|
+
io.stderr(`error: ${message(error)}\n`);
|
|
554
|
+
if (error instanceof UsageError) {
|
|
555
|
+
io.stderr(program.helpInformation());
|
|
556
|
+
}
|
|
557
|
+
return 1;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
export async function main(
|
|
561
|
+
argv: readonly string[],
|
|
562
|
+
io: Io = {
|
|
563
|
+
stdout: (value) => process.stdout.write(value),
|
|
564
|
+
stderr: (value) => process.stderr.write(value),
|
|
565
|
+
}
|
|
566
|
+
): Promise<number> {
|
|
567
|
+
const program = createProgram(io);
|
|
568
|
+
try {
|
|
569
|
+
await program.parseAsync(argv, { from: "user" });
|
|
570
|
+
return 0;
|
|
571
|
+
} catch (error) {
|
|
572
|
+
return handleError(error, program, io);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (import.meta.main) {
|
|
577
|
+
process.exitCode = await main(process.argv.slice(2));
|
|
578
|
+
}
|