Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions packages/gittensory-miner/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ The package also includes an append-only prediction ledger: `initPredictionLedge
codes, plus the producing `ENGINE_VERSION`) in local SQLite, so a later self-improve pass can score predictions
against realized outcomes. Insert-only. (#4263)

`gittensory-miner manage status` now also folds each tracked repo's current discover/plan/prepare run state
(`run-state.js`) alongside its managed PR rows into a "run portfolio" view — `collectRunPortfolio` /
`renderRunPortfolioTable` — so a repo actively being discovered or planned shows up even with zero PRs yet.
Additive only: the existing `rows` JSON key and PR table are unchanged; `runPortfolio` is a new key printed
after the existing table. A real GUI dashboard surface is out of scope here — `apps/gittensory-miner-ui/` is
Phase 6 of the same roadmap tracker and hasn't been scaffolded yet. (#4279)

## Install

See [`docs/miner-goal-spec.md`](docs/miner-goal-spec.md) for the `.gittensory-miner.yml` field reference and [`.gittensory-miner.yml.example`](../../.gittensory-miner.yml.example) at the repo root.
Expand Down Expand Up @@ -88,6 +95,8 @@ gittensory-miner version
gittensory-miner init [--json]
gittensory-miner status [--json]
gittensory-miner doctor [--json]
gittensory-miner manage status [--json]
gittensory-miner manage poll <owner/repo> <pr#> [--branch <name>] [--json]
```

## Version check
Expand Down
18 changes: 18 additions & 0 deletions packages/gittensory-miner/lib/manage-status.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { EventLedger, LedgerEntry } from "./event-ledger.js";
import type { PortfolioQueueStore, QueueStatus } from "./portfolio-queue.js";
import type { RunState, RunStateStore } from "./run-state.js";

export type ManageStatusRow = {
repoFullName: string;
Expand All @@ -18,6 +19,18 @@ export type ManageStatusSources = {
eventLedger: EventLedger;
};

export type RunPortfolioSources = ManageStatusSources & {
runStateStore: RunStateStore;
};

export type RunPortfolioRow = {
repoFullName: string;
runState: RunState | null;
runStateUpdatedAt: string | null;
prCount: number;
prs: ManageStatusRow[];
};

export type ManageUpdateSnapshot = {
repoFullName: string;
prNumber: number;
Expand All @@ -39,14 +52,19 @@ export function indexLatestManageUpdates(events: LedgerEntry[]): Map<string, Man

export function collectManageStatus(sources: ManageStatusSources): ManageStatusRow[];

export function collectRunPortfolio(sources: RunPortfolioSources): RunPortfolioRow[];

export function renderManageStatusTable(rows: ManageStatusRow[]): string;

export function renderRunPortfolioTable(portfolio: RunPortfolioRow[]): string;

export function parseManageStatusArgs(args?: string[]): { json: boolean } | { error: string };

export function runManageStatus(
args?: string[],
options?: {
initPortfolioQueue?: () => PortfolioQueueStore;
initEventLedger?: () => EventLedger;
initRunStateStore?: () => RunStateStore;
},
): number;
65 changes: 63 additions & 2 deletions packages/gittensory-miner/lib/manage-status.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { initEventLedger } from "./event-ledger.js";
import { initPortfolioQueueStore } from "./portfolio-queue.js";
import { initRunStateStore } from "./run-state.js";

/** Event vocabulary for manage-phase PR snapshots written by manage poll. (#2325) */
export const MANAGE_PR_UPDATE_EVENT = "manage_pr_update";
Expand Down Expand Up @@ -106,6 +107,39 @@ export function collectManageStatus(sources) {
});
}

/**
* Fold each tracked repo's current discover/plan/prepare run state alongside its managed PR rows into one
* "run portfolio" row per repo (#4279). `collectManageStatus` alone is PR-scoped only and never surfaces the
* run-state signal, so a repo actively discovering/planning with zero PRs yet is otherwise invisible. A repo
* appears here if it has EITHER a recorded run state OR at least one managed PR row.
*/
export function collectRunPortfolio(sources) {
const runStateStore = sources?.runStateStore;
if (!runStateStore || typeof runStateStore.listRunStates !== "function") {
throw new Error("invalid_run_state_store");
}
const prsByRepo = new Map();
for (const row of collectManageStatus(sources)) {
const list = prsByRepo.get(row.repoFullName) ?? [];
list.push(row);
prsByRepo.set(row.repoFullName, list);
}
const runStateByRepo = new Map(runStateStore.listRunStates().map((entry) => [entry.repoFullName, entry]));

const repoFullNames = new Set([...prsByRepo.keys(), ...runStateByRepo.keys()]);
return [...repoFullNames].sort((left, right) => left.localeCompare(right)).map((repoFullName) => {
const prs = prsByRepo.get(repoFullName) ?? [];
const runState = runStateByRepo.get(repoFullName);
return {
repoFullName,
runState: runState?.state ?? null,
runStateUpdatedAt: runState?.updatedAt ?? null,
prCount: prs.length,
prs,
};
});
}

function display(value) {
if (value === null || value === undefined) return "-";
return String(value);
Expand Down Expand Up @@ -140,6 +174,27 @@ export function renderManageStatusTable(rows) {
return [header, ...lines].join("\n");
}

/** One row per tracked repo (run state + PR count), the compact companion to {@link renderManageStatusTable}'s
* per-PR detail (#4279). */
export function renderRunPortfolioTable(portfolio) {
if (!Array.isArray(portfolio) || portfolio.length === 0) return "no tracked repos";
const header = [
"repo".padEnd(24),
"run-state".padEnd(12),
"updated".padEnd(20),
"prs".padStart(4),
].join(" ");
const lines = portfolio.map((entry) =>
[
entry.repoFullName.padEnd(24),
display(entry.runState).padEnd(12),
display(entry.runStateUpdatedAt).padEnd(20),
String(entry.prCount).padStart(4),
].join(" "),
);
return [header, ...lines].join("\n");
}

export function parseManageStatusArgs(args = []) {
for (const token of args) {
if (token === "--json") continue;
Expand All @@ -158,18 +213,24 @@ export function runManageStatus(args = [], options = {}) {

const ownsPortfolioQueue = options.initPortfolioQueue === undefined;
const ownsEventLedger = options.initEventLedger === undefined;
const ownsRunStateStore = options.initRunStateStore === undefined;
const portfolioQueue = (options.initPortfolioQueue ?? initPortfolioQueueStore)();
const eventLedger = (options.initEventLedger ?? initEventLedger)();
const runStateStore = (options.initRunStateStore ?? initRunStateStore)();
try {
const rows = collectManageStatus({ portfolioQueue, eventLedger });
const runPortfolio = collectRunPortfolio({ portfolioQueue, eventLedger, runStateStore });
if (parsed.json) {
console.log(JSON.stringify({ rows }, null, 2));
// Additive only (#4279): `rows` keeps its existing shape unchanged; `runPortfolio` is a new key so an
// existing consumer parsing this JSON for `rows` alone sees byte-identical output.
console.log(JSON.stringify({ rows, runPortfolio }, null, 2));
} else {
console.log(renderManageStatusTable(rows));
console.log(`${renderManageStatusTable(rows)}\n\n${renderRunPortfolioTable(runPortfolio)}`);
}
return 0;
} finally {
if (ownsPortfolioQueue) portfolioQueue.close();
if (ownsEventLedger) eventLedger.close();
if (ownsRunStateStore) runStateStore.close();
}
}
9 changes: 9 additions & 0 deletions packages/gittensory-miner/lib/run-state.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,17 @@ export type RunStateWrite = {
updatedAt: string;
};

export type RunStateRow = {
repoFullName: string;
state: RunState;
updatedAt: string;
};

export type RunStateStore = {
dbPath: string;
getRunState(repoFullName: string): RunState | null;
setRunState(repoFullName: string, state: RunState): RunStateWrite;
listRunStates(): RunStateRow[];
close(): void;
};

Expand All @@ -23,4 +30,6 @@ export function getRunState(repoFullName: string): RunState | null;

export function setRunState(repoFullName: string, state: RunState): RunStateWrite;

export function listRunStates(): RunStateRow[];

export function closeDefaultRunStateStore(): void;
14 changes: 14 additions & 0 deletions packages/gittensory-miner/lib/run-state.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ export function initRunStateStore(dbPath = resolveRunStateDbPath()) {
state = excluded.state,
updated_at = excluded.updated_at
`);
const listStatement = db.prepare(
"SELECT repo_full_name, state, updated_at FROM miner_run_state ORDER BY repo_full_name",
);

return {
dbPath: resolvedPath,
Expand All @@ -86,6 +89,13 @@ export function initRunStateStore(dbPath = resolveRunStateDbPath()) {
setStatement.run(normalizedRepo, normalizedState, updatedAt);
return { repoFullName: normalizedRepo, state: normalizedState, updatedAt };
},
/** Every repo with a recorded run state, across the whole store — the per-repo discover/plan/prepare
* signal a "run portfolio" view folds alongside managed PR rows (#4279). */
listRunStates() {
return listStatement.all()
.filter((row) => runStateSet.has(row.state))
.map((row) => ({ repoFullName: row.repo_full_name, state: row.state, updatedAt: row.updated_at }));
},
close() {
db.close();
},
Expand All @@ -105,6 +115,10 @@ export function setRunState(repoFullName, state) {
return getDefaultRunStateStore().setRunState(repoFullName, state);
}

export function listRunStates() {
return getDefaultRunStateStore().listRunStates();
}

export function closeDefaultRunStateStore() {
if (!defaultRunStateStore) return;
defaultRunStateStore.close();
Expand Down
Loading