omegon 0.6.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/.gitattributes +3 -0
- package/AGENTS.md +16 -0
- package/LICENSE +15 -0
- package/README.md +289 -0
- package/bin/pi.mjs +30 -0
- package/extensions/00-secrets/index.ts +1126 -0
- package/extensions/01-auth/auth.ts +401 -0
- package/extensions/01-auth/index.ts +289 -0
- package/extensions/auto-compact.ts +42 -0
- package/extensions/bootstrap/deps.ts +291 -0
- package/extensions/bootstrap/index.ts +811 -0
- package/extensions/chronos/chronos.sh +487 -0
- package/extensions/chronos/index.ts +148 -0
- package/extensions/cleave/assessment.ts +754 -0
- package/extensions/cleave/bridge.ts +31 -0
- package/extensions/cleave/conflicts.ts +250 -0
- package/extensions/cleave/dispatcher.ts +808 -0
- package/extensions/cleave/guardrails.ts +426 -0
- package/extensions/cleave/index.ts +3121 -0
- package/extensions/cleave/lifecycle-emitter.ts +20 -0
- package/extensions/cleave/openspec.ts +811 -0
- package/extensions/cleave/planner.ts +260 -0
- package/extensions/cleave/review.ts +579 -0
- package/extensions/cleave/skills.ts +355 -0
- package/extensions/cleave/types.ts +261 -0
- package/extensions/cleave/workspace.ts +861 -0
- package/extensions/cleave/worktree.ts +243 -0
- package/extensions/core-renderers.ts +253 -0
- package/extensions/dashboard/context-gauge.ts +58 -0
- package/extensions/dashboard/file-watch.ts +14 -0
- package/extensions/dashboard/footer.ts +1145 -0
- package/extensions/dashboard/git.ts +185 -0
- package/extensions/dashboard/index.ts +478 -0
- package/extensions/dashboard/memory-audit.ts +34 -0
- package/extensions/dashboard/overlay-data.ts +705 -0
- package/extensions/dashboard/overlay.ts +365 -0
- package/extensions/dashboard/render-utils.ts +54 -0
- package/extensions/dashboard/types.ts +191 -0
- package/extensions/dashboard/uri-helper.ts +45 -0
- package/extensions/debug.ts +69 -0
- package/extensions/defaults.ts +282 -0
- package/extensions/design-tree/dashboard-state.ts +161 -0
- package/extensions/design-tree/design-card.ts +362 -0
- package/extensions/design-tree/index.ts +2130 -0
- package/extensions/design-tree/lifecycle-emitter.ts +41 -0
- package/extensions/design-tree/tree.ts +1607 -0
- package/extensions/design-tree/types.ts +163 -0
- package/extensions/distill.ts +127 -0
- package/extensions/effort/index.ts +395 -0
- package/extensions/effort/tiers.ts +146 -0
- package/extensions/effort/types.ts +105 -0
- package/extensions/lib/git-state.ts +227 -0
- package/extensions/lib/local-models.ts +157 -0
- package/extensions/lib/model-preferences.ts +51 -0
- package/extensions/lib/model-routing.ts +720 -0
- package/extensions/lib/operator-fallback.ts +205 -0
- package/extensions/lib/operator-profile.ts +360 -0
- package/extensions/lib/slash-command-bridge.ts +253 -0
- package/extensions/lib/typebox-helpers.ts +16 -0
- package/extensions/local-inference/index.ts +727 -0
- package/extensions/mcp-bridge/README.md +220 -0
- package/extensions/mcp-bridge/index.ts +951 -0
- package/extensions/mcp-bridge/lib.ts +365 -0
- package/extensions/mcp-bridge/mcp.json +3 -0
- package/extensions/mcp-bridge/package.json +11 -0
- package/extensions/model-budget.ts +752 -0
- package/extensions/offline-driver.ts +403 -0
- package/extensions/openspec/archive-gate.ts +164 -0
- package/extensions/openspec/branch-cleanup.ts +64 -0
- package/extensions/openspec/dashboard-state.ts +50 -0
- package/extensions/openspec/index.ts +1917 -0
- package/extensions/openspec/lifecycle-emitter.ts +65 -0
- package/extensions/openspec/lifecycle-files.ts +70 -0
- package/extensions/openspec/lifecycle.ts +50 -0
- package/extensions/openspec/reconcile.ts +187 -0
- package/extensions/openspec/spec.ts +1385 -0
- package/extensions/openspec/types.ts +98 -0
- package/extensions/project-memory/DESIGN-global-mind.md +198 -0
- package/extensions/project-memory/README.md +202 -0
- package/extensions/project-memory/api-types.ts +382 -0
- package/extensions/project-memory/compaction-policy.ts +29 -0
- package/extensions/project-memory/core.ts +164 -0
- package/extensions/project-memory/embeddings.ts +230 -0
- package/extensions/project-memory/extraction-v2.ts +861 -0
- package/extensions/project-memory/factstore.ts +2177 -0
- package/extensions/project-memory/index.ts +3459 -0
- package/extensions/project-memory/injection-metrics.ts +91 -0
- package/extensions/project-memory/jsonl-io.ts +12 -0
- package/extensions/project-memory/lifecycle.ts +331 -0
- package/extensions/project-memory/migration.ts +293 -0
- package/extensions/project-memory/package.json +9 -0
- package/extensions/project-memory/sci-renderers.ts +7 -0
- package/extensions/project-memory/template.ts +103 -0
- package/extensions/project-memory/triggers.ts +52 -0
- package/extensions/project-memory/types.ts +102 -0
- package/extensions/render/composition/fonts/Inter-Bold.ttf +0 -0
- package/extensions/render/composition/fonts/Inter-Regular.ttf +0 -0
- package/extensions/render/composition/fonts/Tomorrow-Bold.ttf +0 -0
- package/extensions/render/composition/fonts/Tomorrow-Regular.ttf +0 -0
- package/extensions/render/composition/package-lock.json +534 -0
- package/extensions/render/composition/package.json +22 -0
- package/extensions/render/composition/render.mjs +246 -0
- package/extensions/render/composition/test-comp.tsx +87 -0
- package/extensions/render/composition/types.ts +24 -0
- package/extensions/render/excalidraw/UPSTREAM.md +81 -0
- package/extensions/render/excalidraw/elements.ts +764 -0
- package/extensions/render/excalidraw/index.ts +66 -0
- package/extensions/render/excalidraw/types.ts +223 -0
- package/extensions/render/excalidraw-renderer/pyproject.toml +8 -0
- package/extensions/render/excalidraw-renderer/render_excalidraw.py +182 -0
- package/extensions/render/excalidraw-renderer/render_template.html +59 -0
- package/extensions/render/index.ts +830 -0
- package/extensions/render/native-diagrams/index.ts +57 -0
- package/extensions/render/native-diagrams/motifs.ts +542 -0
- package/extensions/render/native-diagrams/raster.ts +8 -0
- package/extensions/render/native-diagrams/scene.ts +75 -0
- package/extensions/render/native-diagrams/spec.ts +204 -0
- package/extensions/render/native-diagrams/svg.ts +116 -0
- package/extensions/sci-ui.ts +304 -0
- package/extensions/session-log.ts +174 -0
- package/extensions/shared-state.ts +146 -0
- package/extensions/spinner-verbs.ts +91 -0
- package/extensions/style.ts +281 -0
- package/extensions/terminal-title.ts +191 -0
- package/extensions/tool-profile/index.ts +291 -0
- package/extensions/tool-profile/profiles.ts +290 -0
- package/extensions/types.d.ts +9 -0
- package/extensions/vault/index.ts +185 -0
- package/extensions/version-check.ts +90 -0
- package/extensions/view/index.ts +859 -0
- package/extensions/view/uri-resolver.ts +148 -0
- package/extensions/web-search/index.ts +182 -0
- package/extensions/web-search/providers.ts +121 -0
- package/extensions/web-ui/index.ts +110 -0
- package/extensions/web-ui/server.ts +265 -0
- package/extensions/web-ui/state.ts +462 -0
- package/extensions/web-ui/static/index.html +145 -0
- package/extensions/web-ui/types.ts +284 -0
- package/package.json +76 -0
- package/prompts/init.md +75 -0
- package/prompts/new-repo.md +54 -0
- package/prompts/oci-login.md +56 -0
- package/prompts/status.md +50 -0
- package/settings.json +4 -0
- package/skills/cleave/SKILL.md +218 -0
- package/skills/git/SKILL.md +209 -0
- package/skills/git/_reference/ci-validation.md +204 -0
- package/skills/oci/SKILL.md +338 -0
- package/skills/openspec/SKILL.md +346 -0
- package/skills/pi-extensions/SKILL.md +191 -0
- package/skills/pi-tui/SKILL.md +517 -0
- package/skills/python/SKILL.md +189 -0
- package/skills/rust/SKILL.md +268 -0
- package/skills/security/SKILL.md +206 -0
- package/skills/style/SKILL.md +264 -0
- package/skills/typescript/SKILL.md +225 -0
- package/skills/vault/SKILL.md +102 -0
- package/themes/alpharius-legacy.json +85 -0
- package/themes/alpharius.conf +59 -0
- package/themes/alpharius.json +88 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared debug logging for Omegon extensions.
|
|
3
|
+
*
|
|
4
|
+
* Output goes to a log file (~/.pi/agent/omegon-debug.log) so it doesn't
|
|
5
|
+
* corrupt the TUI. Tail the file in a separate terminal to watch live:
|
|
6
|
+
* tail -f ~/.pi/agent/omegon-debug.log
|
|
7
|
+
*
|
|
8
|
+
* Controlled by PI_DEBUG environment variable:
|
|
9
|
+
* PI_DEBUG=1 — all extensions
|
|
10
|
+
* PI_DEBUG=dashboard — only dashboard
|
|
11
|
+
* PI_DEBUG=openspec,cleave — comma-separated list
|
|
12
|
+
*
|
|
13
|
+
* Each log line: [HH:mm:ss.SSS scope:tag] {json}
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { appendFileSync, mkdirSync } from "node:fs";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
import { homedir } from "node:os";
|
|
19
|
+
|
|
20
|
+
const PI_DEBUG = process.env.PI_DEBUG ?? "";
|
|
21
|
+
const debugAll = PI_DEBUG === "1" || PI_DEBUG === "*" || PI_DEBUG === "true";
|
|
22
|
+
const debugScopes = new Set(
|
|
23
|
+
debugAll ? [] : PI_DEBUG.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean),
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const LOG_DIR = join(homedir(), ".pi", "agent");
|
|
27
|
+
const LOG_PATH = join(LOG_DIR, "omegon-debug.log");
|
|
28
|
+
let dirEnsured = false;
|
|
29
|
+
|
|
30
|
+
function ensureDir(): void {
|
|
31
|
+
if (dirEnsured) return;
|
|
32
|
+
try {
|
|
33
|
+
mkdirSync(LOG_DIR, { recursive: true });
|
|
34
|
+
} catch {
|
|
35
|
+
// best effort
|
|
36
|
+
}
|
|
37
|
+
dirEnsured = true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isEnabled(scope: string): boolean {
|
|
41
|
+
if (debugAll) return true;
|
|
42
|
+
if (debugScopes.size === 0) return false;
|
|
43
|
+
return debugScopes.has(scope.toLowerCase());
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Path to the debug log file, for display to users. */
|
|
47
|
+
export const DEBUG_LOG_PATH = LOG_PATH;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Log a debug message to the Omegon debug log file.
|
|
51
|
+
*
|
|
52
|
+
* @param scope - Extension name (e.g. "dashboard", "openspec", "cleave")
|
|
53
|
+
* @param tag - Sub-tag for the message (e.g. "render", "emitState", "session_start")
|
|
54
|
+
* @param data - Optional structured data to include
|
|
55
|
+
*/
|
|
56
|
+
export function debug(scope: string, tag: string, data?: Record<string, unknown>): void {
|
|
57
|
+
if (!isEnabled(scope)) return;
|
|
58
|
+
ensureDir();
|
|
59
|
+
const ts = new Date().toISOString().slice(11, 23); // HH:mm:ss.SSS
|
|
60
|
+
const prefix = `[${ts} ${scope}:${tag}]`;
|
|
61
|
+
const line = data && Object.keys(data).length > 0
|
|
62
|
+
? `${prefix} ${JSON.stringify(data)}\n`
|
|
63
|
+
: `${prefix}\n`;
|
|
64
|
+
try {
|
|
65
|
+
appendFileSync(LOG_PATH, line);
|
|
66
|
+
} catch {
|
|
67
|
+
// best effort — don't crash extensions over logging
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* defaults — Auto-configure Omegon defaults on first install
|
|
3
|
+
*
|
|
4
|
+
* - Sets theme to "default" if no theme is configured
|
|
5
|
+
* - Deploys global AGENTS.md to ~/.pi/agent/ for cross-project directives
|
|
6
|
+
*
|
|
7
|
+
* Guards:
|
|
8
|
+
* - Only writes settings/AGENTS.md if not already present or if managed by Omegon
|
|
9
|
+
* - Never overwrites a user-authored AGENTS.md (detected by absence of marker comment)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import * as fs from "node:fs";
|
|
13
|
+
import * as path from "node:path";
|
|
14
|
+
import * as crypto from "node:crypto";
|
|
15
|
+
import type { ExtensionAPI } from "@cwilson613/pi-coding-agent";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the agent directory the same way pi's getAgentDir() does.
|
|
19
|
+
* In standalone mode (PI_CODING_AGENT_DIR set to omegon root) this points
|
|
20
|
+
* directly at the omegon repo, so theme/AGENTS.md deployment becomes
|
|
21
|
+
* identity copies and is skipped by the content-equality guards.
|
|
22
|
+
*/
|
|
23
|
+
const home = process.env.HOME || process.env.USERPROFILE || "~";
|
|
24
|
+
const AGENT_DIR = (() => {
|
|
25
|
+
const env = process.env.PI_CODING_AGENT_DIR;
|
|
26
|
+
if (env) {
|
|
27
|
+
if (env === "~") return home;
|
|
28
|
+
if (env.startsWith("~/")) return path.join(home, env.slice(2));
|
|
29
|
+
return env;
|
|
30
|
+
}
|
|
31
|
+
return path.join(home, ".pi", "agent");
|
|
32
|
+
})();
|
|
33
|
+
|
|
34
|
+
const SETTINGS_PATH = path.join(AGENT_DIR, "settings.json");
|
|
35
|
+
const GLOBAL_AGENTS_PATH = path.join(AGENT_DIR, "AGENTS.md");
|
|
36
|
+
const THEMES_DIR = path.join(AGENT_DIR, "themes");
|
|
37
|
+
|
|
38
|
+
/** Themes shipped with Omegon — deployed to ~/.pi/agent/themes/ */
|
|
39
|
+
const BUNDLED_THEMES = ["alpharius.json"] as const;
|
|
40
|
+
|
|
41
|
+
/** Marker embedded in the deployed AGENTS.md to identify Omegon ownership */
|
|
42
|
+
const PIKIT_MARKER = "<!-- managed by omegon -->";
|
|
43
|
+
const PIKIT_MARKER_LEGACY = "<!-- managed by pi-kit -->"; // legacy — still treated as owned
|
|
44
|
+
|
|
45
|
+
/** Hash file tracks the last content we deployed, so we detect user edits */
|
|
46
|
+
const HASH_PATH = path.join(AGENT_DIR, ".agents-md-hash");
|
|
47
|
+
|
|
48
|
+
/** Path to the template shipped with the Omegon package */
|
|
49
|
+
const TEMPLATE_PATH = path.join(import.meta.dirname, "..", "config", "AGENTS.md");
|
|
50
|
+
|
|
51
|
+
function contentHash(content: string): string {
|
|
52
|
+
return crypto.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Alpharius palette anchor values — must stay in sync with themes/alpharius.json vars.
|
|
57
|
+
* Emitted via OSC 10/11 to clamp the terminal's native fg/bg so that pi-tui's full
|
|
58
|
+
* \x1b[0m line-resets fall through to Alpharius colors rather than the user's terminal theme.
|
|
59
|
+
*
|
|
60
|
+
* OSC 10 = set default foreground color
|
|
61
|
+
* OSC 11 = set default background color
|
|
62
|
+
* OSC 110/111 = restore saved fg/bg (most terminals support this as a reset)
|
|
63
|
+
*
|
|
64
|
+
* Kitty DOES honor OSC 10/11 (confirmed experimentally — overrides conf-based bg at runtime).
|
|
65
|
+
* All modern terminals (Kitty, iTerm2, WezTerm, Alacritty, foot, VTE, xterm) respect these.
|
|
66
|
+
*/
|
|
67
|
+
const ALPHARIUS_FG = "#c4d8e4";
|
|
68
|
+
const ALPHARIUS_BG = "#02030a";
|
|
69
|
+
|
|
70
|
+
function emitOsc10_11(fg: string, bg: string): void {
|
|
71
|
+
process.stdout.write(`\x1b]10;${fg}\x07\x1b]11;${bg}\x07`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function restoreTerminalColors(): void {
|
|
75
|
+
// OSC 110 = restore saved default foreground, OSC 111 = restore saved default background
|
|
76
|
+
process.stdout.write("\x1b]110\x07\x1b]111\x07");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Ownership marker written by scripts/export-kitty-theme.ts into alpharius.conf */
|
|
80
|
+
const KITTY_OWNERSHIP_MARKER = "# Generated from themes/alpharius.json by scripts/export-kitty-theme.ts";
|
|
81
|
+
|
|
82
|
+
/** Installed location — standard Kitty themes directory */
|
|
83
|
+
const KITTY_CONF_PATH = path.join(
|
|
84
|
+
process.env.HOME || process.env.USERPROFILE || "~",
|
|
85
|
+
".config", "kitty", "themes", "alpharius.conf",
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
/** Source of truth shipped with Omegon */
|
|
89
|
+
const KITTY_CONF_SRC = path.join(import.meta.dirname, "..", "themes", "alpharius.conf");
|
|
90
|
+
|
|
91
|
+
let kittyWarnedThisSession = false;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Gently sync alpharius.conf to Kitty's themes directory.
|
|
95
|
+
*
|
|
96
|
+
* Rules:
|
|
97
|
+
* - File missing → install it, notify
|
|
98
|
+
* - File present, owned → update if stale, notify with reload hint
|
|
99
|
+
* - File present, unowned → warn once, never touch
|
|
100
|
+
*/
|
|
101
|
+
function syncKittyTheme(notify: (msg: string, level: "info" | "warning") => void): void {
|
|
102
|
+
try {
|
|
103
|
+
if (!fs.existsSync(KITTY_CONF_SRC)) return;
|
|
104
|
+
const srcContent = fs.readFileSync(KITTY_CONF_SRC, "utf8");
|
|
105
|
+
|
|
106
|
+
if (!fs.existsSync(KITTY_CONF_PATH)) {
|
|
107
|
+
// Not installed — write it
|
|
108
|
+
fs.mkdirSync(path.dirname(KITTY_CONF_PATH), { recursive: true });
|
|
109
|
+
fs.writeFileSync(KITTY_CONF_PATH, srcContent, "utf8");
|
|
110
|
+
notify("Omegon: installed alpharius.conf to Kitty themes — add `include themes/alpharius.conf` to kitty.conf", "info");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const existing = fs.readFileSync(KITTY_CONF_PATH, "utf8");
|
|
115
|
+
|
|
116
|
+
if (!existing.includes(KITTY_OWNERSHIP_MARKER)) {
|
|
117
|
+
// User-authored — don't touch, warn once
|
|
118
|
+
if (!kittyWarnedThisSession) {
|
|
119
|
+
kittyWarnedThisSession = true;
|
|
120
|
+
notify(
|
|
121
|
+
"Omegon: ~/.config/kitty/themes/alpharius.conf exists but wasn't generated by Omegon — skipping update",
|
|
122
|
+
"warning",
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (existing !== srcContent) {
|
|
129
|
+
// Owned and stale — update
|
|
130
|
+
fs.writeFileSync(KITTY_CONF_PATH, srcContent, "utf8");
|
|
131
|
+
notify("Omegon: Kitty theme updated — reload with Ctrl+Shift+F5", "info");
|
|
132
|
+
}
|
|
133
|
+
// else: up to date — no-op
|
|
134
|
+
} catch {
|
|
135
|
+
// Best effort
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export default function (pi: ExtensionAPI) {
|
|
140
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
141
|
+
// --- Terminal color anchoring (OSC 10/11) ---
|
|
142
|
+
// Clamp the terminal's native fg/bg to Alpharius values so that pi-tui's \x1b[0m
|
|
143
|
+
// line-resets (hardcoded in pi-tui applyLineResets) don't bleed through a
|
|
144
|
+
// lighter/different terminal background. OSC 10/11 works on all modern terminals
|
|
145
|
+
// including Kitty (confirmed — overrides conf-based colors at runtime).
|
|
146
|
+
emitOsc10_11(ALPHARIUS_FG, ALPHARIUS_BG);
|
|
147
|
+
|
|
148
|
+
// Belt-and-suspenders: if Kitty remote control is available, also push via @ set-colors.
|
|
149
|
+
// This survives cases where OSC sequences are swallowed by multiplexers.
|
|
150
|
+
try {
|
|
151
|
+
const { execSync } = await import("child_process");
|
|
152
|
+
execSync(`kitty @ set-colors background=${ALPHARIUS_BG} foreground=${ALPHARIUS_FG}`, {
|
|
153
|
+
timeout: 1000, stdio: "ignore",
|
|
154
|
+
});
|
|
155
|
+
} catch { /* not Kitty or remote control disabled — OSC 10/11 covers it */ }
|
|
156
|
+
|
|
157
|
+
// --- Kitty theme sync ---
|
|
158
|
+
if (ctx.hasUI) {
|
|
159
|
+
syncKittyTheme((msg, level) => ctx.ui.notify(msg, level));
|
|
160
|
+
} else {
|
|
161
|
+
syncKittyTheme(() => {}); // silent in non-UI mode (e.g. pi -p children)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// --- Terminal tab title branding ---
|
|
165
|
+
// Replace the core π symbol with Ω in the terminal tab title.
|
|
166
|
+
// This fires after the core title is set, so it overwrites it.
|
|
167
|
+
if (ctx.hasUI) {
|
|
168
|
+
const sessionName = ctx.sessionManager.getSessionName();
|
|
169
|
+
const cwdBasename = path.basename(ctx.cwd);
|
|
170
|
+
const title = sessionName
|
|
171
|
+
? `Ω - ${sessionName} - ${cwdBasename}`
|
|
172
|
+
: `Ω - ${cwdBasename}`;
|
|
173
|
+
ctx.ui.setTitle(title);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// --- Theme default ---
|
|
177
|
+
try {
|
|
178
|
+
const raw = fs.readFileSync(SETTINGS_PATH, "utf8");
|
|
179
|
+
const settings = JSON.parse(raw);
|
|
180
|
+
|
|
181
|
+
let changed = false;
|
|
182
|
+
|
|
183
|
+
// Always enforce alpharius — Omegon is opinionated about its own TUI.
|
|
184
|
+
// Override "default" and absent theme; leave other explicit choices alone.
|
|
185
|
+
if (!settings.theme || settings.theme === "default") {
|
|
186
|
+
settings.theme = "alpharius";
|
|
187
|
+
changed = true;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (changed) {
|
|
191
|
+
fs.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
192
|
+
if (ctx.hasUI) {
|
|
193
|
+
ctx.ui.notify("Omegon: activated alpharius theme (restart pi to apply)", "info");
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
} catch {
|
|
197
|
+
// Best effort
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// --- Theme deployment ---
|
|
201
|
+
// Copy bundled themes to ~/.pi/agent/themes/, overwriting on every session start
|
|
202
|
+
// so updates in the repo propagate automatically.
|
|
203
|
+
try {
|
|
204
|
+
fs.mkdirSync(THEMES_DIR, { recursive: true });
|
|
205
|
+
for (const themeFile of BUNDLED_THEMES) {
|
|
206
|
+
const src = path.join(import.meta.dirname, "..", "themes", themeFile);
|
|
207
|
+
const dst = path.join(THEMES_DIR, themeFile);
|
|
208
|
+
if (!fs.existsSync(src)) continue;
|
|
209
|
+
const srcContent = fs.readFileSync(src, "utf8");
|
|
210
|
+
const dstContent = fs.existsSync(dst) ? fs.readFileSync(dst, "utf8") : null;
|
|
211
|
+
if (srcContent !== dstContent) {
|
|
212
|
+
fs.writeFileSync(dst, srcContent, "utf8");
|
|
213
|
+
if (ctx.hasUI) {
|
|
214
|
+
ctx.ui.notify(`Omegon: updated theme ${themeFile} (restart to apply)`, "info");
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
} catch {
|
|
219
|
+
// Best effort
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// --- Global AGENTS.md deployment ---
|
|
223
|
+
try {
|
|
224
|
+
if (!fs.existsSync(TEMPLATE_PATH)) return;
|
|
225
|
+
fs.mkdirSync(AGENT_DIR, { recursive: true });
|
|
226
|
+
const template = fs.readFileSync(TEMPLATE_PATH, "utf8");
|
|
227
|
+
const deployContent = `${template.trimEnd()}\n\n${PIKIT_MARKER}\n`;
|
|
228
|
+
|
|
229
|
+
if (fs.existsSync(GLOBAL_AGENTS_PATH)) {
|
|
230
|
+
const existing = fs.readFileSync(GLOBAL_AGENTS_PATH, "utf8");
|
|
231
|
+
|
|
232
|
+
if (existing.includes(PIKIT_MARKER) || existing.includes(PIKIT_MARKER_LEGACY)) {
|
|
233
|
+
// We own this file — check if user has edited it since last deploy
|
|
234
|
+
if (existing !== deployContent) {
|
|
235
|
+
const lastHash = fs.existsSync(HASH_PATH) ? fs.readFileSync(HASH_PATH, "utf8").trim() : null;
|
|
236
|
+
const existingHash = contentHash(existing);
|
|
237
|
+
|
|
238
|
+
if (!lastHash) {
|
|
239
|
+
// First run with hash tracking — adopt current content as baseline
|
|
240
|
+
// so we don't overwrite edits made before the hash mechanism existed
|
|
241
|
+
fs.writeFileSync(HASH_PATH, existingHash, "utf8");
|
|
242
|
+
if (ctx.hasUI) {
|
|
243
|
+
ctx.ui.notify(
|
|
244
|
+
"Omegon: AGENTS.md template updated. Changes will apply on next session start.",
|
|
245
|
+
"info",
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
} else if (lastHash !== existingHash) {
|
|
249
|
+
// File was modified externally — warn, don't overwrite
|
|
250
|
+
if (ctx.hasUI) {
|
|
251
|
+
ctx.ui.notify(
|
|
252
|
+
"Omegon: ~/.pi/agent/AGENTS.md has local edits. Remove the omegon marker to keep them, or delete the file to re-deploy.",
|
|
253
|
+
"warning",
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
} else {
|
|
257
|
+
// File matches our last deploy — safe to update
|
|
258
|
+
fs.writeFileSync(GLOBAL_AGENTS_PATH, deployContent, "utf8");
|
|
259
|
+
fs.writeFileSync(HASH_PATH, contentHash(deployContent), "utf8");
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// else: user-authored file (no marker), don't touch it
|
|
264
|
+
} else {
|
|
265
|
+
// No AGENTS.md exists — deploy ours
|
|
266
|
+
fs.writeFileSync(GLOBAL_AGENTS_PATH, deployContent, "utf8");
|
|
267
|
+
fs.writeFileSync(HASH_PATH, contentHash(deployContent), "utf8");
|
|
268
|
+
if (ctx.hasUI) {
|
|
269
|
+
ctx.ui.notify("Omegon: deployed global directives to ~/.pi/agent/AGENTS.md", "info");
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
} catch {
|
|
273
|
+
// Best effort — don't break startup
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
pi.on("session_shutdown", async () => {
|
|
278
|
+
// Restore the terminal's original fg/bg on exit so the user's shell prompt
|
|
279
|
+
// and other programs see their own configured colors again.
|
|
280
|
+
restoreTerminalColors();
|
|
281
|
+
});
|
|
282
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
|
|
4
|
+
import type { ExtensionAPI } from "@cwilson613/pi-coding-agent";
|
|
5
|
+
|
|
6
|
+
import type { DesignNode, DesignTree } from "./types.ts";
|
|
7
|
+
import { getAllOpenQuestions, countAcceptanceCriteria } from "./tree.ts";
|
|
8
|
+
import { sharedState, DASHBOARD_UPDATE_EVENT } from "../shared-state.ts";
|
|
9
|
+
import type { DesignTreeDashboardState } from "../shared-state.ts";
|
|
10
|
+
import type { DesignAssessmentResult, DesignPipelineCounts } from "../dashboard/types.ts";
|
|
11
|
+
import type { DesignSpecBinding } from "../openspec/archive-gate.ts";
|
|
12
|
+
import { debug } from "../debug.ts";
|
|
13
|
+
|
|
14
|
+
/** Read assessment.json from openspec/design/<id>/assessment.json if it exists. */
|
|
15
|
+
function readAssessmentResult(cwd: string, nodeId: string): DesignAssessmentResult | null {
|
|
16
|
+
const assessmentPath = path.join(cwd, "openspec", "design", nodeId, "assessment.json");
|
|
17
|
+
if (!fs.existsSync(assessmentPath)) return null;
|
|
18
|
+
try {
|
|
19
|
+
const raw = JSON.parse(fs.readFileSync(assessmentPath, "utf-8"));
|
|
20
|
+
return {
|
|
21
|
+
outcome: raw.outcome ?? "ambiguous",
|
|
22
|
+
timestamp: raw.timestamp ?? "",
|
|
23
|
+
summary: raw.summary,
|
|
24
|
+
};
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Build archivedIds Set by scanning openspec/design-archive/ exactly once. */
|
|
31
|
+
function buildArchivedIds(cwd: string): Set<string> {
|
|
32
|
+
const archiveDir = path.join(cwd, "openspec", "design-archive");
|
|
33
|
+
const ids = new Set<string>();
|
|
34
|
+
if (!fs.existsSync(archiveDir)) return ids;
|
|
35
|
+
for (const entry of fs.readdirSync(archiveDir, { withFileTypes: true })) {
|
|
36
|
+
if (!entry.isDirectory()) continue;
|
|
37
|
+
const match = entry.name.match(/^\d{4}-\d{2}-\d{2}-(.+)$/);
|
|
38
|
+
if (match) ids.add(match[1]);
|
|
39
|
+
}
|
|
40
|
+
return ids;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Inline binding resolver that uses a pre-built archivedIds set — no extra readdirSync. */
|
|
44
|
+
function resolveBindingInline(cwd: string, nodeId: string, archivedIds: Set<string>): DesignSpecBinding {
|
|
45
|
+
const designDir = path.join(cwd, "openspec", "design", nodeId);
|
|
46
|
+
const active =
|
|
47
|
+
fs.existsSync(designDir) &&
|
|
48
|
+
fs.statSync(designDir).isDirectory() &&
|
|
49
|
+
fs.readdirSync(designDir).length > 0;
|
|
50
|
+
const archived = archivedIds.has(nodeId);
|
|
51
|
+
return { active, archived: archived && !active, missing: !active && !archived };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function emitDesignTreeState(pi: ExtensionAPI, dt: DesignTree, focused: DesignNode | null): void {
|
|
55
|
+
if (dt.nodes.size === 0) return;
|
|
56
|
+
const cwd = process.cwd();
|
|
57
|
+
const allNodes = Array.from(dt.nodes.values());
|
|
58
|
+
// Exclude implemented nodes from the active dashboard view — they're done work.
|
|
59
|
+
// Deferred nodes remain visible: they are future work, not OBE.
|
|
60
|
+
const nodes = allNodes.filter((n) => n.status !== "implemented");
|
|
61
|
+
|
|
62
|
+
// C1 fix: scan design-archive once outside the map loop.
|
|
63
|
+
const archivedIds = buildArchivedIds(cwd);
|
|
64
|
+
|
|
65
|
+
// Compute design pipeline funnel counts across ALL nodes
|
|
66
|
+
const pipelineCounts: DesignPipelineCounts = {
|
|
67
|
+
needsSpec: 0,
|
|
68
|
+
designing: 0,
|
|
69
|
+
decided: 0,
|
|
70
|
+
implementing: 0,
|
|
71
|
+
done: allNodes.filter((n) => n.status === "implemented").length,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const NEUTRAL_SENTINEL: DesignSpecBinding = { active: false, archived: false, missing: false };
|
|
75
|
+
|
|
76
|
+
const enrichedNodes = nodes.map((n) => {
|
|
77
|
+
const isSeedLike = n.status === "seed";
|
|
78
|
+
const isActivePhase = ["exploring", "decided", "implementing"].includes(n.status);
|
|
79
|
+
// W3 fix: deferred/blocked also receive the neutral sentinel (not undefined)
|
|
80
|
+
const isPassive = n.status === "deferred" || n.status === "blocked";
|
|
81
|
+
|
|
82
|
+
// Resolve binding for non-seed nodes
|
|
83
|
+
let designSpec: DesignSpecBinding;
|
|
84
|
+
let acSummary: ReturnType<typeof countAcceptanceCriteria> | null = null;
|
|
85
|
+
let assessmentResult: DesignAssessmentResult | null = null;
|
|
86
|
+
|
|
87
|
+
if (isSeedLike || isPassive) {
|
|
88
|
+
// Seeds/deferred/blocked have no active binding — emit neutral sentinel
|
|
89
|
+
designSpec = NEUTRAL_SENTINEL;
|
|
90
|
+
} else {
|
|
91
|
+
// isActivePhase: exploring, decided, implementing
|
|
92
|
+
// C1 fix: use inline resolver with pre-built archivedIds
|
|
93
|
+
designSpec = resolveBindingInline(cwd, n.id, archivedIds);
|
|
94
|
+
acSummary = countAcceptanceCriteria(n);
|
|
95
|
+
assessmentResult = readAssessmentResult(cwd, n.id);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Accumulate pipeline counts
|
|
99
|
+
// C3 fix: deferred/blocked fall into needsSpec so funnel totals reconcile
|
|
100
|
+
if (n.status === "decided") {
|
|
101
|
+
pipelineCounts.decided++;
|
|
102
|
+
} else if (n.status === "implementing") {
|
|
103
|
+
pipelineCounts.implementing++;
|
|
104
|
+
} else if (n.status === "exploring" || n.status === "seed") {
|
|
105
|
+
const bound = designSpec.active || designSpec.archived;
|
|
106
|
+
if (bound) {
|
|
107
|
+
pipelineCounts.designing++;
|
|
108
|
+
} else {
|
|
109
|
+
pipelineCounts.needsSpec++;
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
// deferred / blocked — no spec yet, count as needsSpec
|
|
113
|
+
pipelineCounts.needsSpec++;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
id: n.id,
|
|
118
|
+
title: n.title,
|
|
119
|
+
status: n.status,
|
|
120
|
+
questionCount: n.open_questions.length,
|
|
121
|
+
filePath: n.filePath,
|
|
122
|
+
branches: n.branches ?? [],
|
|
123
|
+
openspecChange: n.openspec_change ?? null,
|
|
124
|
+
designSpec,
|
|
125
|
+
acSummary,
|
|
126
|
+
assessmentResult,
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const state: DesignTreeDashboardState = {
|
|
131
|
+
// W1 fix: nodeCount reflects ALL nodes so implementedCount/nodeCount ratios are correct
|
|
132
|
+
nodeCount: allNodes.length,
|
|
133
|
+
decidedCount: nodes.filter((n) => n.status === "decided").length,
|
|
134
|
+
exploringCount: nodes.filter((n) => n.status === "exploring" || n.status === "seed").length,
|
|
135
|
+
implementingCount: nodes.filter((n) => n.status === "implementing").length,
|
|
136
|
+
implementedCount: allNodes.filter((n) => n.status === "implemented").length,
|
|
137
|
+
blockedCount: nodes.filter((n) => n.status === "blocked").length,
|
|
138
|
+
deferredCount: nodes.filter((n) => n.status === "deferred").length,
|
|
139
|
+
openQuestionCount: getAllOpenQuestions(dt).length,
|
|
140
|
+
focusedNode: focused
|
|
141
|
+
? {
|
|
142
|
+
id: focused.id,
|
|
143
|
+
title: focused.title,
|
|
144
|
+
status: focused.status,
|
|
145
|
+
questions: [...focused.open_questions],
|
|
146
|
+
branch: focused.branches?.[0],
|
|
147
|
+
branchCount: focused.branches?.length ?? 0,
|
|
148
|
+
filePath: focused.filePath,
|
|
149
|
+
}
|
|
150
|
+
: null,
|
|
151
|
+
nodes: enrichedNodes,
|
|
152
|
+
implementingNodes: nodes
|
|
153
|
+
.filter((n) => n.status === "implementing")
|
|
154
|
+
.map((n) => ({ id: n.id, title: n.title, branch: n.branches?.[0], filePath: n.filePath })),
|
|
155
|
+
designPipeline: pipelineCounts,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
sharedState.designTree = state;
|
|
159
|
+
debug("design-tree", "emitState", { nodeCount: nodes.length, decided: state.decidedCount, exploring: state.exploringCount });
|
|
160
|
+
pi.events.emit(DASHBOARD_UPDATE_EVENT, { source: "design-tree" });
|
|
161
|
+
}
|