From 349b08904806a0704babd2a871289277c3250263 Mon Sep 17 00:00:00 2001 From: "Younes (Machine Wisdom)" Date: Thu, 26 Mar 2026 19:05:06 -0400 Subject: [PATCH 1/2] [Bugfix #619] Fix ASPIR prompt reference, porch init in spawnTask, and fragile phases check - codev-skeleton/protocols/aspir/builder-prompt.md: Fix 'Follow the SPIR protocol' to say 'Follow the ASPIR protocol' with correct path - codev/protocols/aspir/builder-prompt.md: Same fix for our own copy - codev-skeleton/protocols/{aspir,spir}/protocol.json: Change has_phases_json check from literal 'grep -q' to 'grep -qE' regex to handle whitespace variants like '"phases" :' - codev/protocols/{aspir,spir}/protocol.json: Same fix for our own copies - packages/codev/src/agent-farm/commands/spawn.ts: Call initPorchInWorktree in spawnTask when hasExplicitProtocol is true (was silently skipped) - Add regression test for all three bugs Co-Authored-By: Claude Sonnet 4.6 --- .../protocols/aspir/builder-prompt.md | 2 +- codev-skeleton/protocols/aspir/protocol.json | 2 +- codev-skeleton/protocols/spir/protocol.json | 2 +- codev/protocols/aspir/builder-prompt.md | 2 +- codev/protocols/aspir/protocol.json | 2 +- codev/protocols/spir/protocol.json | 2 +- .../__tests__/bugfix-619-aspir-prompt.test.ts | 93 +++++++++++++++++++ .../codev/src/agent-farm/commands/spawn.ts | 3 + 8 files changed, 102 insertions(+), 6 deletions(-) create mode 100644 packages/codev/src/agent-farm/__tests__/bugfix-619-aspir-prompt.test.ts diff --git a/codev-skeleton/protocols/aspir/builder-prompt.md b/codev-skeleton/protocols/aspir/builder-prompt.md index 1f932b6d46..73c3ba6071 100644 --- a/codev-skeleton/protocols/aspir/builder-prompt.md +++ b/codev-skeleton/protocols/aspir/builder-prompt.md @@ -27,7 +27,7 @@ You are running in STRICT mode. This means: {{/if}} ## Protocol -Follow the SPIR protocol: `codev/protocols/spir/protocol.md` +Follow the ASPIR protocol: `codev/protocols/aspir/protocol.md` Read and internalize the protocol before starting any work. {{#if spec}} diff --git a/codev-skeleton/protocols/aspir/protocol.json b/codev-skeleton/protocols/aspir/protocol.json index 3c7dbc5567..1277c69934 100644 --- a/codev-skeleton/protocols/aspir/protocol.json +++ b/codev-skeleton/protocols/aspir/protocol.json @@ -51,7 +51,7 @@ }, "checks": { "plan_exists": "test -f codev/plans/${PROJECT_TITLE}.md", - "has_phases_json": "grep -q '\"phases\":' codev/plans/${PROJECT_TITLE}.md", + "has_phases_json": "grep -qE '\"phases\"\\s*:' codev/plans/${PROJECT_TITLE}.md", "min_two_phases": "grep -o '\"id\": *\"[^\"]*\"' codev/plans/${PROJECT_TITLE}.md | wc -l | awk '$1 >= 2 {exit 0} {exit 1}'" }, "next": "implement" diff --git a/codev-skeleton/protocols/spir/protocol.json b/codev-skeleton/protocols/spir/protocol.json index b046252bb1..f2b99be4f4 100644 --- a/codev-skeleton/protocols/spir/protocol.json +++ b/codev-skeleton/protocols/spir/protocol.json @@ -53,7 +53,7 @@ }, "checks": { "plan_exists": "test -f codev/plans/${PROJECT_TITLE}.md", - "has_phases_json": "grep -q '\"phases\":' codev/plans/${PROJECT_TITLE}.md", + "has_phases_json": "grep -qE '\"phases\"\\s*:' codev/plans/${PROJECT_TITLE}.md", "min_two_phases": "grep -o '\"id\": *\"[^\"]*\"' codev/plans/${PROJECT_TITLE}.md | wc -l | awk '$1 >= 2 {exit 0} {exit 1}'" }, "gate": "plan-approval", diff --git a/codev/protocols/aspir/builder-prompt.md b/codev/protocols/aspir/builder-prompt.md index 1f932b6d46..73c3ba6071 100644 --- a/codev/protocols/aspir/builder-prompt.md +++ b/codev/protocols/aspir/builder-prompt.md @@ -27,7 +27,7 @@ You are running in STRICT mode. This means: {{/if}} ## Protocol -Follow the SPIR protocol: `codev/protocols/spir/protocol.md` +Follow the ASPIR protocol: `codev/protocols/aspir/protocol.md` Read and internalize the protocol before starting any work. {{#if spec}} diff --git a/codev/protocols/aspir/protocol.json b/codev/protocols/aspir/protocol.json index 3c7dbc5567..1277c69934 100644 --- a/codev/protocols/aspir/protocol.json +++ b/codev/protocols/aspir/protocol.json @@ -51,7 +51,7 @@ }, "checks": { "plan_exists": "test -f codev/plans/${PROJECT_TITLE}.md", - "has_phases_json": "grep -q '\"phases\":' codev/plans/${PROJECT_TITLE}.md", + "has_phases_json": "grep -qE '\"phases\"\\s*:' codev/plans/${PROJECT_TITLE}.md", "min_two_phases": "grep -o '\"id\": *\"[^\"]*\"' codev/plans/${PROJECT_TITLE}.md | wc -l | awk '$1 >= 2 {exit 0} {exit 1}'" }, "next": "implement" diff --git a/codev/protocols/spir/protocol.json b/codev/protocols/spir/protocol.json index b046252bb1..f2b99be4f4 100644 --- a/codev/protocols/spir/protocol.json +++ b/codev/protocols/spir/protocol.json @@ -53,7 +53,7 @@ }, "checks": { "plan_exists": "test -f codev/plans/${PROJECT_TITLE}.md", - "has_phases_json": "grep -q '\"phases\":' codev/plans/${PROJECT_TITLE}.md", + "has_phases_json": "grep -qE '\"phases\"\\s*:' codev/plans/${PROJECT_TITLE}.md", "min_two_phases": "grep -o '\"id\": *\"[^\"]*\"' codev/plans/${PROJECT_TITLE}.md | wc -l | awk '$1 >= 2 {exit 0} {exit 1}'" }, "gate": "plan-approval", diff --git a/packages/codev/src/agent-farm/__tests__/bugfix-619-aspir-prompt.test.ts b/packages/codev/src/agent-farm/__tests__/bugfix-619-aspir-prompt.test.ts new file mode 100644 index 0000000000..ae6175c096 --- /dev/null +++ b/packages/codev/src/agent-farm/__tests__/bugfix-619-aspir-prompt.test.ts @@ -0,0 +1,93 @@ +/** + * Regression tests for issue #619: + * 1. ASPIR builder-prompt referenced SPIR protocol path + * 2. af spawn --task with --protocol skipped porch init + * 3. has_phases_json check used fragile literal string (now uses -E regex) + */ + +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; + +// ============================================================================ +// Bug 1: ASPIR builder-prompt must reference ASPIR, not SPIR +// ============================================================================ + +describe('ASPIR builder-prompt protocol reference', () => { + const skeletonPrompt = path.resolve( + __dirname, + '../../../../../codev-skeleton/protocols/aspir/builder-prompt.md', + ); + + it('references aspir/protocol.md, not spir/protocol.md', () => { + const content = fs.readFileSync(skeletonPrompt, 'utf-8'); + expect(content).not.toContain('codev/protocols/spir/protocol.md'); + expect(content).toContain('codev/protocols/aspir/protocol.md'); + }); + + it('says "ASPIR protocol", not "SPIR protocol"', () => { + const content = fs.readFileSync(skeletonPrompt, 'utf-8'); + expect(content).not.toMatch(/Follow the SPIR protocol/); + expect(content).toMatch(/Follow the ASPIR protocol/); + }); +}); + +// ============================================================================ +// Bug 2: spawnTask with explicit protocol must call initPorchInWorktree +// ============================================================================ + +describe('spawnTask porch init when --protocol is provided', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('initPorchInWorktree is called when hasExplicitProtocol is true', async () => { + const { initPorchInWorktree } = await import('../commands/spawn-worktree.js'); + // Verify the function is exported (it must be importable for spawnTask to call it) + expect(typeof initPorchInWorktree).toBe('function'); + }); + + it('spawnTask source calls initPorchInWorktree in the hasExplicitProtocol branch', () => { + // Read the spawn.ts source to verify the fix is present + const spawnSrc = path.resolve(__dirname, '../commands/spawn.ts'); + expect(fs.existsSync(spawnSrc)).toBe(true); + const content = fs.readFileSync(spawnSrc, 'utf-8'); + + // The initPorchInWorktree call must appear inside the hasExplicitProtocol block + // (i.e., after the `if (hasExplicitProtocol)` line and before the closing `} else {`) + const hasExplicitBlock = content.match( + /if \(hasExplicitProtocol\)([\s\S]*?)(?=\} else \{)/, + ); + expect(hasExplicitBlock).not.toBeNull(); + expect(hasExplicitBlock![1]).toContain('initPorchInWorktree'); + }); +}); + +// ============================================================================ +// Bug 3: has_phases_json check must use regex (-E) not literal string match +// ============================================================================ + +describe('has_phases_json check uses regex', () => { + const protocolFiles = [ + path.resolve(__dirname, '../../../../../codev-skeleton/protocols/aspir/protocol.json'), + path.resolve(__dirname, '../../../../../codev-skeleton/protocols/spir/protocol.json'), + path.resolve(__dirname, '../../../../../codev/protocols/aspir/protocol.json'), + path.resolve(__dirname, '../../../../../codev/protocols/spir/protocol.json'), + ]; + + for (const file of protocolFiles) { + const label = file.split('codev').pop() ?? file; + + it(`${label}: has_phases_json uses grep -qE (regex-aware)`, () => { + const data = JSON.parse(fs.readFileSync(file, 'utf-8')); + const planPhase = data.phases.find((p: { id: string }) => p.id === 'plan'); + expect(planPhase).toBeDefined(); + const check = planPhase.checks?.has_phases_json as string | undefined; + expect(check).toBeDefined(); + // Must use -E (extended regex) so whitespace variants like `"phases" :` also match + expect(check).toMatch(/grep -qE/); + // Must not use the old fragile literal-only form + expect(check).not.toMatch(/grep -q '/); + }); + } +}); diff --git a/packages/codev/src/agent-farm/commands/spawn.ts b/packages/codev/src/agent-farm/commands/spawn.ts index db37371b30..b603918300 100644 --- a/packages/codev/src/agent-farm/commands/spawn.ts +++ b/packages/codev/src/agent-farm/commands/spawn.ts @@ -424,6 +424,9 @@ async function spawnTask(options: SpawnOptions, config: Config): Promise { }; const prompt = buildPromptFromTemplate(config, protocol, templateContext); builderPrompt = `You are a Builder. Read codev/roles/builder.md for your full role definition.\n${resumeNotice}\n${prompt}`; + if (!options.resume) { + await initPorchInWorktree(worktreePath, protocol, builderId, worktreeName); + } } else { builderPrompt = `You are a Builder. Read codev/roles/builder.md for your full role definition.\n${resumeNotice}\n# Task\n\n${taskDescription}`; } From 6f574de373ecfbb8542b2eea5a37bca829177b1c Mon Sep 17 00:00:00 2001 From: "Younes (Machine Wisdom)" Date: Thu, 26 Mar 2026 19:18:54 -0400 Subject: [PATCH 2/2] Fix pre-existing TS2322 error in AnalyticsView.tsx (introduced by #541) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cast Tooltip formatter to `any` to satisfy recharts' Formatter type — the runtime behavior is unchanged. Co-Authored-By: Claude Sonnet 4.6 --- packages/codev/dashboard/src/components/AnalyticsView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/codev/dashboard/src/components/AnalyticsView.tsx b/packages/codev/dashboard/src/components/AnalyticsView.tsx index 82e39014e0..8b39576ea4 100644 --- a/packages/codev/dashboard/src/components/AnalyticsView.tsx +++ b/packages/codev/dashboard/src/components/AnalyticsView.tsx @@ -83,7 +83,7 @@ function MiniBarChart({ data, dataKey, nameKey, color, formatter }: { v != null ? formatter(v) : '' : undefined} + formatter={formatter ? ((v: unknown) => typeof v === 'number' ? formatter(v) : '') as any : undefined} contentStyle={{ background: 'var(--bg-primary)', border: '1px solid var(--border-color)', fontSize: 11, borderRadius: 4 }} labelStyle={{ color: 'var(--text-primary)' }} itemStyle={{ color: 'var(--text-secondary)' }}