@opsee/cli 0.11.9

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 (85) hide show
  1. package/README.md +1962 -0
  2. package/bin/opsee.js +28 -0
  3. package/package.json +40 -0
  4. package/skills/README.md +3 -0
  5. package/skills/to-issues/SKILL.md +92 -0
  6. package/skills/to-issues/agents/openai.yaml +5 -0
  7. package/skills/to-spec/SKILL.md +79 -0
  8. package/skills/to-spec/agents/openai.yaml +5 -0
  9. package/skills/wayfinder/SKILL.md +138 -0
  10. package/skills/wayfinder/agents/openai.yaml +5 -0
  11. package/src/args.ts +676 -0
  12. package/src/cli.ts +341 -0
  13. package/src/commands/account.ts +121 -0
  14. package/src/commands/deps.ts +11 -0
  15. package/src/commands/foreman-control.ts +242 -0
  16. package/src/commands/foreman-debug.ts +131 -0
  17. package/src/commands/foreman-plan.ts +213 -0
  18. package/src/commands/foreman-service.ts +186 -0
  19. package/src/commands/foreman-up.ts +165 -0
  20. package/src/commands/foreman-views.ts +398 -0
  21. package/src/commands/foreman.ts +465 -0
  22. package/src/commands/init.ts +176 -0
  23. package/src/commands/initiative.ts +192 -0
  24. package/src/commands/login.ts +24 -0
  25. package/src/commands/whoami.ts +15 -0
  26. package/src/foreman/account-store.ts +96 -0
  27. package/src/foreman/account.ts +474 -0
  28. package/src/foreman/claude-worker-adapter.ts +412 -0
  29. package/src/foreman/codex-worker-adapter.ts +472 -0
  30. package/src/foreman/completion-report.ts +153 -0
  31. package/src/foreman/core/context.ts +169 -0
  32. package/src/foreman/core/defects.ts +280 -0
  33. package/src/foreman/core/exec.ts +20 -0
  34. package/src/foreman/core/gates.ts +493 -0
  35. package/src/foreman/core/handoff.ts +163 -0
  36. package/src/foreman/core/install.ts +109 -0
  37. package/src/foreman/core/learnings.ts +368 -0
  38. package/src/foreman/core/outbox-tracker.ts +192 -0
  39. package/src/foreman/core/pin.ts +226 -0
  40. package/src/foreman/core/plan-context.ts +238 -0
  41. package/src/foreman/core/process-table.ts +535 -0
  42. package/src/foreman/core/reconcile.ts +227 -0
  43. package/src/foreman/core/report.ts +60 -0
  44. package/src/foreman/core/run.ts +2836 -0
  45. package/src/foreman/core/scheduler.ts +244 -0
  46. package/src/foreman/core/summary.ts +166 -0
  47. package/src/foreman/core/text.ts +97 -0
  48. package/src/foreman/core/transcripts.ts +38 -0
  49. package/src/foreman/core/triage.ts +138 -0
  50. package/src/foreman/core/verifier.ts +800 -0
  51. package/src/foreman/core/views.ts +940 -0
  52. package/src/foreman/core/work-contract.ts +152 -0
  53. package/src/foreman/core/workspace.ts +335 -0
  54. package/src/foreman/fake-handoff.ts +33 -0
  55. package/src/foreman/fake-learnings.ts +26 -0
  56. package/src/foreman/fake-remote-api.ts +70 -0
  57. package/src/foreman/fake-tracker-adapter.ts +355 -0
  58. package/src/foreman/fake-worker-adapter.ts +221 -0
  59. package/src/foreman/host.ts +75 -0
  60. package/src/foreman/local-dir.ts +28 -0
  61. package/src/foreman/opsee-tracker-adapter.ts +612 -0
  62. package/src/foreman/process-group.ts +160 -0
  63. package/src/foreman/remote-api.ts +283 -0
  64. package/src/foreman/run-recipe.ts +274 -0
  65. package/src/foreman/service-unit.ts +257 -0
  66. package/src/foreman/tracker-adapter.ts +298 -0
  67. package/src/foreman/triage-draft.ts +40 -0
  68. package/src/foreman/vendor.ts +23 -0
  69. package/src/foreman/verdict.ts +120 -0
  70. package/src/foreman/worker-adapter.ts +177 -0
  71. package/src/foreman/worker-process.ts +488 -0
  72. package/src/identity.ts +49 -0
  73. package/src/index.ts +3 -0
  74. package/src/init/managed.ts +84 -0
  75. package/src/init/mcp-config.ts +77 -0
  76. package/src/init/paths.ts +16 -0
  77. package/src/init/pointer-block.ts +45 -0
  78. package/src/init/project.ts +22 -0
  79. package/src/init/prompt.ts +45 -0
  80. package/src/init/run-recipe-config.ts +133 -0
  81. package/src/init/skills.ts +38 -0
  82. package/src/init/text.ts +22 -0
  83. package/src/init/tracker-doc.ts +106 -0
  84. package/src/opsee-config.ts +116 -0
  85. package/templates/issue-tracker.md +162 -0
@@ -0,0 +1,138 @@
1
+ /**
2
+ * The Triage turn (see ../../../CONTEXT.md, spec story 23): a short unattended turn the Foreman runs
3
+ * on a Ready Task whose work contract has a Goal and Acceptance Criteria but no Verification
4
+ * section, to draft one before dispatching. The Foreman drafts rather than blocks, so a legacy Task
5
+ * does not stall the fleet and the gap stays visible as a comment on the Task.
6
+ *
7
+ * The turn goes through the same Worker Adapter as any other, in the Task's Workspace so the Worker
8
+ * can read the repository for the real commands, with a small turn cap. Its Completion Report
9
+ * carries the draft in `summary` (triage-draft.ts says why). This module builds the prompt and
10
+ * drains the turn; run.ts records the outcome, since the Run Record write is the commit (ADR-0009).
11
+ */
12
+ import type { Account } from "../account.js";
13
+ import type { TrackerTask } from "../tracker-adapter.js";
14
+ import type { AdapterEvent, WorkerAdapter } from "../worker-adapter.js";
15
+ import { tail } from "./install.js";
16
+ import type { WorkContract } from "./work-contract.js";
17
+
18
+ /** Reading a few files and writing one section needs a handful of agentic turns, not a Worker's
19
+ * budget; the Run's own `maxTurns` lowers this, never raises it. */
20
+ export const TRIAGE_MAX_TURNS = 10;
21
+
22
+ /**
23
+ * What a Triage turn's attempt-event summary starts with, so a reader can tell it from the
24
+ * implementer turn that carries the same attempt number.
25
+ *
26
+ * The Triage turn runs *before* the dispatch event that fixes the implementer's number, so it is
27
+ * given the number the implementer is about to take (`attempt = priorDispatches + 1`, run.ts) —
28
+ * several other components match on that numbering (the readiness report, the Verifier's
29
+ * attribution, the Defect dedup, `foreman review`), so the implementer's number is what must stay
30
+ * intact. That leaves one attempt number carrying two attempt events, triage first, and the two
31
+ * places that settle by number (`settledOutcome` in core/reconcile.ts, `finishSettlement` in
32
+ * core/run.ts) both take the *first* match. Reading the triage turn's success as the implementer
33
+ * attempt's settlement drops a live row and blocks a Task whose Worker was still running — exactly
34
+ * what Reconcile exists to prevent. So the triage event is tagged, and those two skip it.
35
+ *
36
+ * `retryableFailuresOf` deliberately does *not* skip it: a Triage turn that hit a rate limit is a
37
+ * turn the vendor refused, and it spends the retry cap like any other.
38
+ */
39
+ export const TRIAGE_MARKER = "[triage]";
40
+
41
+ /** Whether an attempt event's summary is a Triage turn's rather than an implementer turn's. */
42
+ export function isTriageSummary(summary: string): boolean {
43
+ return summary.startsWith(TRIAGE_MARKER);
44
+ }
45
+
46
+ export function triagePrompt(task: Pick<TrackerTask, "identifier" | "title">, contract: WorkContract, workspace: { path: string }): string {
47
+ return [
48
+ `You are a Worker run unattended by the Foreman for a Triage turn on Task ${task.identifier}: ${task.title}.`,
49
+ "",
50
+ "The Task's work contract has a Goal and Acceptance Criteria but no Verification section. Draft that section.",
51
+ `Do not implement the Task and do not change any file: your working directory ${workspace.path} is the repository, read it to find the real commands and, if the Task touches a UI, the real routes.`,
52
+ "",
53
+ `## Goal\n\n${contract.sections.Goal ?? "(none)"}`,
54
+ "",
55
+ `## Acceptance Criteria\n\n${contract.sections["Acceptance Criteria"] ?? "(none)"}`,
56
+ "",
57
+ "## What to draft",
58
+ "",
59
+ "A Verification section: how a Verifier who never sees the implementer's transcript proves the work from the outside.",
60
+ "Give the exact commands to run (tests, lint, typecheck, build, whatever this repository uses) and, for anything with a UI,",
61
+ "the user journey to exercise in a browser: how to start the app, which pages to open, what to do there, and what must be visible.",
62
+ "Write it as markdown with no heading line, in the repository's own vocabulary, concrete enough to follow without asking.",
63
+ "",
64
+ "## Finishing",
65
+ "",
66
+ "End the turn with a Completion Report: outcome `done`, and the drafted Verification section verbatim as the summary",
67
+ "(nothing else in the summary); leave decisions, tried, blockers and proposedLearnings empty. Report `blocked`, with the",
68
+ "reason as a blocker, only if the Goal and Acceptance Criteria are too thin to draft from.",
69
+ ].join("\n");
70
+ }
71
+
72
+ export interface TriageTurnRequest {
73
+ worker: WorkerAdapter;
74
+ account: Account;
75
+ task: Pick<TrackerTask, "identifier" | "title">;
76
+ contract: WorkContract;
77
+ workspace: { path: string };
78
+ maxTurns?: number;
79
+ stallTimeoutMs?: number;
80
+ log: (line: string) => void;
81
+ now: () => number;
82
+ }
83
+
84
+ export interface DrainedTurn {
85
+ terminal: Extract<AdapterEvent, { type: "completed" | "failed" }>;
86
+ durationMs: number;
87
+ outputTail: string;
88
+ /** The vendor's rate-limit report, when one came (story 30). A Triage turn is a turn on the
89
+ * Account like any other, so a limit it hit Pauses the Account; the terminal event alone does not
90
+ * carry the reset the vendor named, which is what decides how long. */
91
+ rateLimit?: { resetAt?: string; message: string };
92
+ }
93
+
94
+ /** Runs one Triage turn to its terminal event, logging progress the way the dispatch loop does. */
95
+ export async function driveTriageTurn(req: TriageTurnRequest): Promise<DrainedTurn> {
96
+ const maxTurns = Math.min(TRIAGE_MAX_TURNS, req.maxTurns ?? TRIAGE_MAX_TURNS);
97
+ const started = req.now();
98
+ const handle = req.worker.launch({
99
+ account: req.account,
100
+ cwd: req.workspace.path,
101
+ prompt: triagePrompt(req.task, req.contract, req.workspace),
102
+ maxTurns,
103
+ stallTimeoutMs: req.stallTimeoutMs,
104
+ });
105
+ req.log(`triage: ${req.account.vendor} turn on ${req.task.identifier} in ${req.workspace.path} (at most ${maxTurns} turns)`);
106
+ const output: string[] = [];
107
+ let terminal: DrainedTurn["terminal"] | undefined;
108
+ let rateLimit: DrainedTurn["rateLimit"];
109
+ for await (const event of handle.events) {
110
+ switch (event.type) {
111
+ case "started":
112
+ req.log(`triage: session ${event.sessionId}`);
113
+ break;
114
+ case "output":
115
+ for (const line of event.text.split("\n")) {
116
+ output.push(line);
117
+ req.log(` | ${line}`);
118
+ }
119
+ break;
120
+ case "tool":
121
+ req.log(` tool ${event.name}`);
122
+ break;
123
+ case "rate_limited":
124
+ rateLimit = { resetAt: event.resetAt, message: event.message };
125
+ req.log(`triage: rate limited: ${event.message}${event.resetAt ? ` (resets ${event.resetAt})` : ""}`);
126
+ break;
127
+ case "stalled":
128
+ req.log(`triage: stalled, no output for ${event.silentMs}ms`);
129
+ break;
130
+ case "completed":
131
+ case "failed":
132
+ terminal = event;
133
+ break;
134
+ }
135
+ }
136
+ if (!terminal) throw new Error("Worker Adapter contract violated: the event stream ended without a terminal event");
137
+ return { terminal, durationMs: req.now() - started, outputTail: tail(output.join("\n")), rateLimit };
138
+ }