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
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"plugins": [
{
"name": "genie",
"version": "4.260522.8",
"version": "4.260522.9",
"source": "./plugins/genie",
"description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, wish them into plans, make with parallel agents, ship as one team. A coding genie that grows with your project."
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@automagik/genie",
"version": "4.260522.8",
"version": "4.260522.9",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Block release bump until SHA drift is resolved.

4.260522.9 should not be promoted while Binary SHA Drift is failing; this breaks release integrity checks tied to binarySha256 in this manifest. Update pinned SHA(s) and fix the unknown asset-prefix handling before merging this version bump.

🧰 Tools
🪛 GitHub Actions: Binary SHA Drift / drift-check

[error] SHA drift detected for pinned binarySha256 asset (message: "SHA drift for $asset"). Upstream binary SHA256 does not match the pinned value in package.json.


[warning] Unknown asset prefix '$asset' — skipping drift check for this asset (extend drift check to support it).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@package.json` at line 3, The version bump in package.json ("version":
"4.260522.9") must be blocked until Binary SHA Drift is resolved; revert or hold
this version change and update the pinned binarySha256 entries in the manifest
to match the actual build artifacts, and fix the unknown asset-prefix handling
code that generates incorrect asset names so the computed SHAs match
expectations; locate references to "version" in package.json, the manifest field
binarySha256, and the asset-prefix normalization logic (search for
functions/variables handling asset-prefix or asset name generation) and correct
the prefix handling, re-run the SHA computation, and only then promote the
version bump.

"description": "Collaborative terminal toolkit for human + AI workflows. NOTE: npm distribution discontinued 2026-05-09 — install via `curl -fsSL https://raw.githubusercontent.com/automagik-dev/genie/main/install.sh | bash` (cosign + SLSA verified). See https://automagik.dev/genie/release-process",
"type": "module",
"bin": {
Expand Down
2 changes: 1 addition & 1 deletion plugins/genie/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "genie",
"version": "4.260522.8",
"version": "4.260522.9",
"description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, turn them into wishes, execute with /work, validate with /review, and ship as one team.",
"author": {
"name": "Namastex Labs"
Expand Down
2 changes: 1 addition & 1 deletion plugins/genie/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "genie-plugin",
"version": "4.260522.8",
"version": "4.260522.9",
"private": true,
"description": "Runtime dependencies for genie bundled CLIs",
"type": "module",
Expand Down
11 changes: 11 additions & 0 deletions src/genie-commands/__tests__/install.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const {
isPgservePm2ManagedStatus,
isPgserveReadyStatus,
isPgserveOnlinePm2,
isReusableCanonicalPm2Process,
} = _internals;

describe('install.sh release verifier bootstrap', () => {
Expand Down Expand Up @@ -118,6 +119,16 @@ describe('install._internals — canonical-stack constants', () => {
// separately in install-env.test.ts (loads the module with env set).
expect(HARDENED_DEFAULTS.maxMemory).toMatch(/^\d+G$/);
});

test('only an online PM2 Genie entry with a live pid is reusable', () => {
expect(isReusableCanonicalPm2Process({ name: 'Genie', pid: 123, pm2_env: { status: 'online' } })).toBe(true);
expect(isReusableCanonicalPm2Process({ name: 'Genie', pid: 0, pm2_env: { status: 'online' } })).toBe(false);
expect(isReusableCanonicalPm2Process({ name: 'Genie', pid: 123, pm2_env: { status: 'waiting restart' } })).toBe(
false,
);
expect(isReusableCanonicalPm2Process({ name: 'Genie', pid: 123, pm2_env: { status: 'errored' } })).toBe(false);
expect(isReusableCanonicalPm2Process(null)).toBe(false);
});
});

describe('buildEcosystemConfigSource — pm2 ecosystem config locked down', () => {
Expand Down
5 changes: 3 additions & 2 deletions src/genie-commands/__tests__/update.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,10 +296,11 @@ describe('formatVerifyBanner', () => {
expect(lines.some((l) => l.includes('no-restart'))).toBe(true);
});

test('health-unreachable surfaces probe endpoint + pm2 fix', () => {
test('health-unreachable surfaces probe endpoint + PM2 recreate fix', () => {
const lines = formatVerifyBanner({ kind: 'health-unreachable', endpoint: 'doctor --json' });
expect(lines.some((l) => l.includes('unreachable'))).toBe(true);
expect(lines.some((l) => l.includes('pm2 restart Genie'))).toBe(true);
expect(lines.some((l) => l.includes('pm2 restart Genie --update-env'))).toBe(true);
expect(lines.some((l) => l.includes('pm2 delete Genie && genie install'))).toBe(true);
});

test('daemon-stale-inode banner surfaces pid, cwd, and pm2 restart remediation', () => {
Expand Down
38 changes: 31 additions & 7 deletions src/genie-commands/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ interface Pm2Process {
pm2_env?: { status?: string };
}

function isReusableCanonicalPm2Process(process: Pm2Process | null): boolean {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The parameter name process shadows the global process object in Node.js. While TypeScript allows this, it is a common source of confusion and potential bugs if someone later tries to access global properties like process.env or process.exit() within this function. Consider renaming it to pm2Process or proc to improve maintainability and clarity.

Suggested change
function isReusableCanonicalPm2Process(process: Pm2Process | null): boolean {
function isReusableCanonicalPm2Process(pm2Process: Pm2Process | null): boolean {
if (!pm2Process) return false;
if (pm2Process.pm2_env?.status !== 'online') return false;
return typeof pm2Process.pid === 'number' && pm2Process.pid > 0;
}

if (!process) return false;
if (process.pm2_env?.status !== 'online') return false;
return typeof process.pid === 'number' && process.pid > 0;
}

function pm2GetProcess(name: string): Pm2Process | null {
try {
const out = execFileSync('pm2', ['jlist'], {
Expand Down Expand Up @@ -154,17 +160,21 @@ function pm2GetAnyProcess(names: readonly string[]): Pm2Process | null {
* a manual cleanup to do (`pm2 delete <legacy-name>`) but do not abort
* install over it.
*/
function deletePm2Process(name: string): void {
execFileSync('pm2', ['delete', name], {
encoding: 'utf8',
timeout: 10_000,
stdio: ['ignore', 'pipe', 'pipe'],
});
}

function removeLegacyPm2Entries(log: (msg: string) => void = () => {}): string[] {
const removed: string[] = [];
for (const legacyName of LEGACY_PM2_PROCESS_NAMES) {
const existing = pm2GetProcess(legacyName);
if (!existing) continue;
try {
execFileSync('pm2', ['delete', legacyName], {
encoding: 'utf8',
timeout: 10_000,
stdio: ['ignore', 'pipe', 'pipe'],
});
deletePm2Process(legacyName);
log(`removed legacy pm2 entry "${legacyName}" (renamed to "${PM2_PROCESS_NAME}")`);
removed.push(legacyName);
} catch (err) {
Expand Down Expand Up @@ -570,10 +580,23 @@ export async function installCommand(options: InstallOptions = {}): Promise<void
// Step 3 — pm2-supervise the canonical Genie service.
const existing = pm2GetProcess(PM2_PROCESS_NAME);
if (existing) {
if (isReusableCanonicalPm2Process(existing)) {
ok(
`already installed (pm2 process "${PM2_PROCESS_NAME}", status=${existing.pm2_env?.status ?? 'unknown'}). Use \`pm2 delete ${PM2_PROCESS_NAME} && genie install\` to refresh the env (e.g. to pick up a new canonical pgserve URL).`,
);
return;
}

const status = existing.pm2_env?.status ?? 'unknown';
ok(
`already installed (pm2 process "${PM2_PROCESS_NAME}", status=${existing.pm2_env?.status ?? 'unknown'}). Use \`pm2 delete ${PM2_PROCESS_NAME} && genie install\` to refresh the env (e.g. to pick up a new canonical pgserve URL).`,
`pm2 process "${PM2_PROCESS_NAME}" exists but is not reusable (status=${status}, pid=${existing.pid ?? 'none'}); recreating`,
);
return;
try {
deletePm2Process(PM2_PROCESS_NAME);
} catch (err) {
const reason = err instanceof Error ? err.message : String(err);
fail(`pm2 process "${PM2_PROCESS_NAME}" is unhealthy and pm2 delete failed: ${reason}`);
}
}

ensureLogsDir();
Expand Down Expand Up @@ -641,4 +664,5 @@ export const _internals = {
isPgservePm2ManagedStatus,
isPgserveReadyStatus,
isPgserveOnlinePm2,
isReusableCanonicalPm2Process,
};
2 changes: 1 addition & 1 deletion src/genie-commands/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1062,7 +1062,7 @@ export function formatVerifyBanner(result: VerifyResult): string[] {
case 'health-unreachable':
lines.push(`${colorize('\x1b[31m', '\x1b[0m', '✖')} Genie server unreachable (probe: ${result.endpoint})`);
lines.push(
`${colorize('\x1b[2m', '\x1b[0m', ' fix: pm2 restart Genie (or `genie install` if not yet supervised)')}`,
`${colorize('\x1b[2m', '\x1b[0m', ' fix: pm2 restart Genie --update-env (if PM2 says "waiting restart": pm2 delete Genie && genie install)')}`,
);
break;
case 'daemon-stale-inode': {
Expand Down
Loading