Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 3 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ workspace/
# Lock files (auto-generated)
package-lock.json

# Auto-generated source (regenerated by scripts/generate-bundled-defaults.ts)
**/*.generated.ts

# Agent commands and documentation (user-managed)
.agents/
.claude/
Expand Down
2 changes: 2 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export default tseslint.config(
'worktrees/**',
'.claude/worktrees/**',
'.claude/skills/**',
'scripts/**', // Build scripts (not in tsconfig project scope)
'**/*.generated.ts', // Auto-generated source files (content inlined via JSON.stringify)
'**/*.js',
'*.mjs',
'**/*.test.ts',
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
"build": "bun --filter '*' build",
"build:binaries": "bash scripts/build-binaries.sh",
"build:checksums": "bash scripts/checksums.sh",
"generate:bundled": "bun run scripts/generate-bundled-defaults.ts",
"check:bundled": "bun run scripts/generate-bundled-defaults.ts --check",
"test": "bun --filter '*' --parallel test",
"test:watch": "bun --filter @archon/server test:watch",
"type-check": "bun --filter '*' type-check",
Expand All @@ -25,7 +27,7 @@
"build:web": "bun --filter @archon/web build",
"dev:docs": "bun --filter @archon/docs-web dev",
"build:docs": "bun --filter @archon/docs-web build",
"validate": "bun run type-check && bun run lint --max-warnings 0 && bun run format:check && bun run test",
"validate": "bun run check:bundled && bun run type-check && bun run lint --max-warnings 0 && bun run format:check && bun run test",
"prepare": "husky",
"setup-auth": "bun --filter @archon/server setup-auth"
},
Expand Down
80 changes: 80 additions & 0 deletions packages/workflows/src/defaults/bundled-defaults.generated.ts

Large diffs are not rendered by default.

114 changes: 40 additions & 74 deletions packages/workflows/src/defaults/bundled-defaults.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@
import { describe, it, expect } from 'bun:test';
import { readdirSync } from 'fs';
import { join } from 'path';
import { isBinaryBuild, BUNDLED_COMMANDS, BUNDLED_WORKFLOWS } from './bundled-defaults';

// Resolve the on-disk defaults directories relative to this test file so the
// tests work regardless of cwd. From packages/workflows/src/defaults go up
// four levels to the repo root, then into .archon/.
const REPO_ROOT = join(import.meta.dir, '..', '..', '..', '..');
const COMMANDS_DIR = join(REPO_ROOT, '.archon/commands/defaults');
const WORKFLOWS_DIR = join(REPO_ROOT, '.archon/workflows/defaults');

function listNames(dir: string, extensions: readonly string[]): string[] {
return readdirSync(dir)
.filter(f => extensions.some(ext => f.endsWith(ext)))
.map(f => {
const ext = extensions.find(e => f.endsWith(e))!;
return f.slice(0, -ext.length);
})
.sort();
}

describe('bundled-defaults', () => {
describe('isBinaryBuild', () => {
it('should return false in dev/test mode', () => {
Expand All @@ -12,57 +31,35 @@ describe('bundled-defaults', () => {
});
});

describe('BUNDLED_COMMANDS', () => {
it('should have all expected default commands', () => {
const expectedCommands = [
'archon-assist',
'archon-code-review-agent',
'archon-comment-quality-agent',
'archon-create-pr',
'archon-docs-impact-agent',
'archon-error-handling-agent',
'archon-implement-issue',
'archon-implement-review-fixes',
'archon-implement',
'archon-investigate-issue',
'archon-pr-review-scope',
'archon-ralph-prd',
'archon-resolve-merge-conflicts',
'archon-sync-pr-with-main',
'archon-synthesize-review',
'archon-test-coverage-agent',
'archon-validate-pr-code-review-feature',
'archon-validate-pr-code-review-main',
'archon-validate-pr-e2e-feature',
'archon-validate-pr-e2e-main',
'archon-validate-pr-report',
];
describe('bundle completeness', () => {
// These assertions are the canary for bundle drift: if someone adds a
// default file without regenerating bundled-defaults.generated.ts, the
// bundle is missing in compiled binaries (see #979 context). The generator
// is `scripts/generate-bundled-defaults.ts`, and `bun run check:bundled`
// verifies the generated file is up to date in CI.

for (const cmd of expectedCommands) {
expect(BUNDLED_COMMANDS).toHaveProperty(cmd);
}
it('BUNDLED_COMMANDS contains every .md file in .archon/commands/defaults/', () => {
const onDisk = listNames(COMMANDS_DIR, ['.md']);
const bundled = Object.keys(BUNDLED_COMMANDS).sort();
expect(bundled).toEqual(onDisk);
});

expect(Object.keys(BUNDLED_COMMANDS)).toHaveLength(21);
it('BUNDLED_WORKFLOWS contains every .yaml/.yml file in .archon/workflows/defaults/', () => {
const onDisk = listNames(WORKFLOWS_DIR, ['.yaml', '.yml']);
const bundled = Object.keys(BUNDLED_WORKFLOWS).sort();
expect(bundled).toEqual(onDisk);
});
});

describe('BUNDLED_COMMANDS', () => {
it('should have non-empty content for all commands', () => {
for (const [name, content] of Object.entries(BUNDLED_COMMANDS)) {
for (const [, content] of Object.entries(BUNDLED_COMMANDS)) {
expect(content).toBeDefined();
expect(typeof content).toBe('string');
expect(content.length).toBeGreaterThan(0);
// Commands should have meaningful content (at least some markdown)
expect(content.length).toBeGreaterThan(50);
}
});

it('should have markdown content format', () => {
// Commands are markdown files, should have typical markdown patterns
for (const [name, content] of Object.entries(BUNDLED_COMMANDS)) {
// Should contain some text (not just whitespace)
expect(content.trim().length).toBeGreaterThan(0);
}
});

it('archon-pr-review-scope should read .pr-number before other discovery', () => {
const content = BUNDLED_COMMANDS['archon-pr-review-scope'];
expect(content).toContain('$ARTIFACTS_DIR/.pr-number');
Expand All @@ -76,36 +73,10 @@ describe('bundled-defaults', () => {
});

describe('BUNDLED_WORKFLOWS', () => {
it('should have all expected default workflows', () => {
const expectedWorkflows = [
'archon-assist',
'archon-comprehensive-pr-review',
'archon-create-issue',
'archon-feature-development',
'archon-fix-github-issue',
'archon-resolve-conflicts',
'archon-smart-pr-review',
'archon-validate-pr',
'archon-remotion-generate',
'archon-interactive-prd',
'archon-piv-loop',
'archon-adversarial-dev',
'archon-workflow-builder',
];

for (const wf of expectedWorkflows) {
expect(BUNDLED_WORKFLOWS).toHaveProperty(wf);
}

expect(Object.keys(BUNDLED_WORKFLOWS)).toHaveLength(13);
});

it('should have non-empty content for all workflows', () => {
for (const [name, content] of Object.entries(BUNDLED_WORKFLOWS)) {
for (const [, content] of Object.entries(BUNDLED_WORKFLOWS)) {
expect(content).toBeDefined();
expect(typeof content).toBe('string');
expect(content.length).toBeGreaterThan(0);
// Workflows should have meaningful YAML content
expect(content.length).toBeGreaterThan(50);
}
});
Expand All @@ -120,15 +91,10 @@ describe('bundled-defaults', () => {
});

it('should have valid YAML structure', () => {
// Workflows are YAML files, should parse without error
for (const [name, content] of Object.entries(BUNDLED_WORKFLOWS)) {
// Should contain 'name:' as all workflows require a name field
for (const [, content] of Object.entries(BUNDLED_WORKFLOWS)) {
expect(content).toContain('name:');
// Should contain 'description:' as all workflows require description
expect(content).toContain('description:');
// Should contain nodes: (with optional loop: inside nodes)
const hasNodes = content.includes('nodes:');
expect(hasNodes).toBe(true);
expect(content.includes('nodes:')).toBe(true);
}
});
});
Expand Down
116 changes: 18 additions & 98 deletions packages/workflows/src/defaults/bundled-defaults.ts
Original file line number Diff line number Diff line change
@@ -1,108 +1,28 @@
/**
* Bundled default commands and workflows for binary distribution
* Bundled default commands and workflows for binary distribution.
*
* These static imports are resolved at compile time and embedded into the binary.
* When running as a standalone binary (without Bun), these provide the default
* commands and workflows without needing filesystem access to the source repo.
* Content lives in `bundled-defaults.generated.ts`, which is regenerated from
* `.archon/{commands,workflows}/defaults/` by `scripts/generate-bundled-defaults.ts`.
* This file is the hand-written facade: it re-exports the records and defines
* the binary-detection helper.
*
* Import syntax uses `with { type: 'text' }` to import file contents as strings.
* Why two files:
* - Generated file is pure data — never hand-edited, diff on PRs shows
* exactly which defaults changed.
* - Facade keeps the documented `isBinaryBuild()` wrapper in a file that
* humans own.
*
* Why inline strings (and not `import X from '...file.md' with { type: 'text' }`)?
* - Node cannot load `type: 'text'` import attributes — it's Bun-specific.
* Using plain string literals keeps `@archon/workflows` importable from
* both runtimes, which removes SDK blocker #2.
* - Bun still embeds the data at compile time when building the CLI binary,
* so runtime behavior is unchanged.
*/

import { BUNDLED_IS_BINARY } from '@archon/paths';

// =============================================================================
// Default Commands (21 total)
// =============================================================================

import archonAssistCmd from '../../../../.archon/commands/defaults/archon-assist.md' with { type: 'text' };
import archonCodeReviewAgentCmd from '../../../../.archon/commands/defaults/archon-code-review-agent.md' with { type: 'text' };
import archonCommentQualityAgentCmd from '../../../../.archon/commands/defaults/archon-comment-quality-agent.md' with { type: 'text' };
import archonCreatePrCmd from '../../../../.archon/commands/defaults/archon-create-pr.md' with { type: 'text' };
import archonDocsImpactAgentCmd from '../../../../.archon/commands/defaults/archon-docs-impact-agent.md' with { type: 'text' };
import archonErrorHandlingAgentCmd from '../../../../.archon/commands/defaults/archon-error-handling-agent.md' with { type: 'text' };
import archonImplementIssueCmd from '../../../../.archon/commands/defaults/archon-implement-issue.md' with { type: 'text' };
import archonImplementReviewFixesCmd from '../../../../.archon/commands/defaults/archon-implement-review-fixes.md' with { type: 'text' };
import archonImplementCmd from '../../../../.archon/commands/defaults/archon-implement.md' with { type: 'text' };
import archonInvestigateIssueCmd from '../../../../.archon/commands/defaults/archon-investigate-issue.md' with { type: 'text' };
import archonPrReviewScopeCmd from '../../../../.archon/commands/defaults/archon-pr-review-scope.md' with { type: 'text' };
import archonRalphPrdCmd from '../../../../.archon/commands/defaults/archon-ralph-prd.md' with { type: 'text' };
import archonResolveMergeConflictsCmd from '../../../../.archon/commands/defaults/archon-resolve-merge-conflicts.md' with { type: 'text' };
import archonSyncPrWithMainCmd from '../../../../.archon/commands/defaults/archon-sync-pr-with-main.md' with { type: 'text' };
import archonSynthesizeReviewCmd from '../../../../.archon/commands/defaults/archon-synthesize-review.md' with { type: 'text' };
import archonTestCoverageAgentCmd from '../../../../.archon/commands/defaults/archon-test-coverage-agent.md' with { type: 'text' };
import archonValidatePrCodeReviewFeatureCmd from '../../../../.archon/commands/defaults/archon-validate-pr-code-review-feature.md' with { type: 'text' };
import archonValidatePrCodeReviewMainCmd from '../../../../.archon/commands/defaults/archon-validate-pr-code-review-main.md' with { type: 'text' };
import archonValidatePrE2eFeatureCmd from '../../../../.archon/commands/defaults/archon-validate-pr-e2e-feature.md' with { type: 'text' };
import archonValidatePrE2eMainCmd from '../../../../.archon/commands/defaults/archon-validate-pr-e2e-main.md' with { type: 'text' };
import archonValidatePrReportCmd from '../../../../.archon/commands/defaults/archon-validate-pr-report.md' with { type: 'text' };

// =============================================================================
// Default Workflows (13 total)
// =============================================================================

import archonAssistWf from '../../../../.archon/workflows/defaults/archon-assist.yaml' with { type: 'text' };
import archonComprehensivePrReviewWf from '../../../../.archon/workflows/defaults/archon-comprehensive-pr-review.yaml' with { type: 'text' };
import archonCreateIssueWf from '../../../../.archon/workflows/defaults/archon-create-issue.yaml' with { type: 'text' };
import archonFeatureDevelopmentWf from '../../../../.archon/workflows/defaults/archon-feature-development.yaml' with { type: 'text' };
import archonFixGithubIssueWf from '../../../../.archon/workflows/defaults/archon-fix-github-issue.yaml' with { type: 'text' };
import archonResolveConflictsWf from '../../../../.archon/workflows/defaults/archon-resolve-conflicts.yaml' with { type: 'text' };
import archonSmartPrReviewWf from '../../../../.archon/workflows/defaults/archon-smart-pr-review.yaml' with { type: 'text' };
import archonValidatePrWf from '../../../../.archon/workflows/defaults/archon-validate-pr.yaml' with { type: 'text' };
import archonRemotionGenerateWf from '../../../../.archon/workflows/defaults/archon-remotion-generate.yaml' with { type: 'text' };
import archonInteractivePrdWf from '../../../../.archon/workflows/defaults/archon-interactive-prd.yaml' with { type: 'text' };
import archonPivLoopWf from '../../../../.archon/workflows/defaults/archon-piv-loop.yaml' with { type: 'text' };
import archonAdversarialDevWf from '../../../../.archon/workflows/defaults/archon-adversarial-dev.yaml' with { type: 'text' };
import archonWorkflowBuilderWf from '../../../../.archon/workflows/defaults/archon-workflow-builder.yaml' with { type: 'text' };

// =============================================================================
// Exports
// =============================================================================

/**
* Bundled default commands - filename (without extension) -> content
*/
export const BUNDLED_COMMANDS: Record<string, string> = {
'archon-assist': archonAssistCmd,
'archon-code-review-agent': archonCodeReviewAgentCmd,
'archon-comment-quality-agent': archonCommentQualityAgentCmd,
'archon-create-pr': archonCreatePrCmd,
'archon-docs-impact-agent': archonDocsImpactAgentCmd,
'archon-error-handling-agent': archonErrorHandlingAgentCmd,
'archon-implement-issue': archonImplementIssueCmd,
'archon-implement-review-fixes': archonImplementReviewFixesCmd,
'archon-implement': archonImplementCmd,
'archon-investigate-issue': archonInvestigateIssueCmd,
'archon-pr-review-scope': archonPrReviewScopeCmd,
'archon-ralph-prd': archonRalphPrdCmd,
'archon-resolve-merge-conflicts': archonResolveMergeConflictsCmd,
'archon-sync-pr-with-main': archonSyncPrWithMainCmd,
'archon-synthesize-review': archonSynthesizeReviewCmd,
'archon-test-coverage-agent': archonTestCoverageAgentCmd,
'archon-validate-pr-code-review-feature': archonValidatePrCodeReviewFeatureCmd,
'archon-validate-pr-code-review-main': archonValidatePrCodeReviewMainCmd,
'archon-validate-pr-e2e-feature': archonValidatePrE2eFeatureCmd,
'archon-validate-pr-e2e-main': archonValidatePrE2eMainCmd,
'archon-validate-pr-report': archonValidatePrReportCmd,
};

/**
* Bundled default workflows - filename (without extension) -> content
*/
export const BUNDLED_WORKFLOWS: Record<string, string> = {
'archon-assist': archonAssistWf,
'archon-comprehensive-pr-review': archonComprehensivePrReviewWf,
'archon-create-issue': archonCreateIssueWf,
'archon-feature-development': archonFeatureDevelopmentWf,
'archon-fix-github-issue': archonFixGithubIssueWf,
'archon-resolve-conflicts': archonResolveConflictsWf,
'archon-smart-pr-review': archonSmartPrReviewWf,
'archon-validate-pr': archonValidatePrWf,
'archon-remotion-generate': archonRemotionGenerateWf,
'archon-interactive-prd': archonInteractivePrdWf,
'archon-piv-loop': archonPivLoopWf,
'archon-adversarial-dev': archonAdversarialDevWf,
'archon-workflow-builder': archonWorkflowBuilderWf,
};
export { BUNDLED_COMMANDS, BUNDLED_WORKFLOWS } from './bundled-defaults.generated';

/**
* Check if the current process is running as a compiled binary (not via Bun CLI).
Expand Down
6 changes: 6 additions & 0 deletions scripts/build-binaries.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ OUTFILE="${OUTFILE:-}"

echo "Building Archon CLI v${VERSION} (commit: ${GIT_COMMIT})"

# Regenerate bundled defaults from .archon/{commands,workflows}/defaults/ so the
# compiled binary always embeds the current on-disk contents. CI also runs
# `bun run check:bundled` to catch committed drift.
echo "Regenerating bundled defaults..."
bun run scripts/generate-bundled-defaults.ts

# Update build-time constants in source before compiling.
# The file is restored via an EXIT trap so the dev tree is never left dirty,
# even if `bun build --compile` fails mid-way. See GitHub issue #979.
Expand Down
Loading
Loading